aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.devcontainer/devcontainer.json4
-rw-r--r--.dockerignore9
-rw-r--r--.eslintrc.cjs3
-rw-r--r--.github/workflows/pull-compliance.yml8
-rw-r--r--.github/workflows/pull-db-tests.yml2
-rw-r--r--.github/workflows/pull-e2e-tests.yml2
-rw-r--r--.github/workflows/release-nightly.yml2
-rw-r--r--.github/workflows/release-tag-rc.yml2
-rw-r--r--.github/workflows/release-tag-version.yml2
-rw-r--r--.gitignore12
-rw-r--r--.golangci.yml8
-rw-r--r--.ignore3
-rw-r--r--CHANGELOG.md423
-rw-r--r--Dockerfile4
-rw-r--r--Dockerfile.rootless5
-rw-r--r--Makefile49
-rw-r--r--SECURITY.md54
-rw-r--r--assets/go-licenses.json16
-rw-r--r--build.go11
-rw-r--r--build/generate-bindata.go83
-rw-r--r--cmd/actions.go10
-rw-r--r--cmd/admin.go43
-rw-r--r--cmd/admin_auth.go15
-rw-r--r--cmd/admin_auth_ldap.go88
-rw-r--r--cmd/admin_auth_ldap_test.go71
-rw-r--r--cmd/admin_auth_oauth.go62
-rw-r--r--cmd/admin_auth_oauth_test.go333
-rw-r--r--cmd/admin_auth_smtp.go (renamed from cmd/admin_auth_stmp.go)66
-rw-r--r--cmd/admin_auth_smtp_test.go271
-rw-r--r--cmd/admin_regenerate.go14
-rw-r--r--cmd/admin_user.go12
-rw-r--r--cmd/admin_user_change_password.go64
-rw-r--r--cmd/admin_user_change_password_test.go91
-rw-r--r--cmd/admin_user_create.go164
-rw-r--r--cmd/admin_user_create_test.go7
-rw-r--r--cmd/admin_user_delete.go67
-rw-r--r--cmd/admin_user_delete_test.go111
-rw-r--r--cmd/admin_user_generate_access_token.go8
-rw-r--r--cmd/admin_user_list.go8
-rw-r--r--cmd/admin_user_must_change_password.go55
-rw-r--r--cmd/admin_user_must_change_password_test.go78
-rw-r--r--cmd/cert.go128
-rw-r--r--cmd/cert_test.go123
-rw-r--r--cmd/cmd.go25
-rw-r--r--cmd/cmd_test.go38
-rw-r--r--cmd/docs.go18
-rw-r--r--cmd/doctor.go48
-rw-r--r--cmd/doctor_convert.go10
-rw-r--r--cmd/doctor_test.go13
-rw-r--r--cmd/dump.go36
-rw-r--r--cmd/dump_repo.go34
-rw-r--r--cmd/embedded.go37
-rw-r--r--cmd/generate.go13
-rw-r--r--cmd/hook.go28
-rw-r--r--cmd/keys.go10
-rw-r--r--cmd/mailer.go12
-rw-r--r--cmd/main.go61
-rw-r--r--cmd/main_test.go31
-rw-r--r--cmd/manager.go30
-rw-r--r--cmd/manager_logging.go52
-rw-r--r--cmd/migrate.go9
-rw-r--r--cmd/migrate_storage.go57
-rw-r--r--cmd/restore_repo.go8
-rw-r--r--cmd/serv.go98
-rw-r--r--cmd/web.go32
-rw-r--r--cmd/web_graceful.go6
-rw-r--r--contrib/backport/backport.go56
-rw-r--r--contrib/environment-to-ini/environment-to-ini.go9
-rw-r--r--custom/conf/app.example.ini38
-rw-r--r--flake.lock6
-rw-r--r--go.mod69
-rw-r--r--go.sum131
-rw-r--r--main.go2
-rw-r--r--models/actions/run.go32
-rw-r--r--models/actions/run_job.go8
-rw-r--r--models/actions/run_job_list.go25
-rw-r--r--models/actions/run_job_status_test.go6
-rw-r--r--models/actions/run_list.go33
-rw-r--r--models/actions/runner.go18
-rw-r--r--models/actions/status.go9
-rw-r--r--models/actions/task.go14
-rw-r--r--models/actions/task_list.go4
-rw-r--r--models/actions/utils.go19
-rw-r--r--models/activities/action.go12
-rw-r--r--models/activities/notification_list.go20
-rw-r--r--models/activities/repo_activity.go5
-rw-r--r--models/activities/statistic.go3
-rw-r--r--models/activities/user_heatmap.go2
-rw-r--r--models/asymkey/gpg_key_add.go2
-rw-r--r--models/asymkey/gpg_key_verify.go2
-rw-r--r--models/asymkey/ssh_key_parse.go2
-rw-r--r--models/asymkey/ssh_key_verify.go2
-rw-r--r--models/auth/access_token_scope.go7
-rw-r--r--models/auth/auth_token.go2
-rw-r--r--models/auth/oauth2.go8
-rw-r--r--models/db/context.go13
-rw-r--r--models/db/name.go7
-rw-r--r--models/db/sql_postgres_with_schema.go4
-rw-r--r--models/dbfs/dbfile.go20
-rw-r--r--models/fixtures/action_artifact.yml36
-rw-r--r--models/fixtures/action_run.yml67
-rw-r--r--models/fixtures/action_run_job.yml60
-rw-r--r--models/fixtures/action_runner.yml11
-rw-r--r--models/fixtures/action_task.yml60
-rw-r--r--models/fixtures/branch.yml12
-rw-r--r--models/fixtures/commit_status.yml5
-rw-r--r--models/fixtures/email_address.yml2
-rw-r--r--models/fixtures/hook_task.yml2
-rw-r--r--models/git/branch.go2
-rw-r--r--models/git/commit_status.go85
-rw-r--r--models/git/commit_status_summary.go20
-rw-r--r--models/git/commit_status_test.go93
-rw-r--r--models/git/lfs.go2
-rw-r--r--models/git/protected_branch.go4
-rw-r--r--models/issues/comment.go20
-rw-r--r--models/issues/comment_code.go5
-rw-r--r--models/issues/comment_list.go35
-rw-r--r--models/issues/issue_label.go1
-rw-r--r--models/issues/issue_list.go45
-rw-r--r--models/issues/issue_search.go8
-rw-r--r--models/issues/issue_stats.go5
-rw-r--r--models/issues/issue_test.go7
-rw-r--r--models/issues/issue_update.go145
-rw-r--r--models/issues/pull.go6
-rw-r--r--models/issues/pull_list.go3
-rw-r--r--models/issues/pull_test.go55
-rw-r--r--models/issues/review_list.go2
-rw-r--r--models/issues/stopwatch.go167
-rw-r--r--models/issues/stopwatch_test.go61
-rw-r--r--models/issues/tracked_time.go5
-rw-r--r--models/migrations/base/db.go2
-rw-r--r--models/migrations/base/tests.go5
-rw-r--r--models/migrations/v1_10/v100.go2
-rw-r--r--models/migrations/v1_10/v101.go2
-rw-r--r--models/migrations/v1_10/v88.go2
-rw-r--r--models/migrations/v1_10/v89.go2
-rw-r--r--models/migrations/v1_10/v90.go2
-rw-r--r--models/migrations/v1_10/v91.go2
-rw-r--r--models/migrations/v1_10/v92.go2
-rw-r--r--models/migrations/v1_10/v93.go2
-rw-r--r--models/migrations/v1_10/v94.go2
-rw-r--r--models/migrations/v1_10/v95.go2
-rw-r--r--models/migrations/v1_10/v96.go2
-rw-r--r--models/migrations/v1_10/v97.go2
-rw-r--r--models/migrations/v1_10/v98.go2
-rw-r--r--models/migrations/v1_10/v99.go2
-rw-r--r--models/migrations/v1_11/v102.go2
-rw-r--r--models/migrations/v1_11/v103.go2
-rw-r--r--models/migrations/v1_11/v104.go2
-rw-r--r--models/migrations/v1_11/v105.go2
-rw-r--r--models/migrations/v1_11/v106.go2
-rw-r--r--models/migrations/v1_11/v107.go2
-rw-r--r--models/migrations/v1_11/v108.go2
-rw-r--r--models/migrations/v1_11/v109.go2
-rw-r--r--models/migrations/v1_11/v110.go2
-rw-r--r--models/migrations/v1_11/v111.go9
-rw-r--r--models/migrations/v1_11/v112.go6
-rw-r--r--models/migrations/v1_11/v113.go2
-rw-r--r--models/migrations/v1_11/v114.go2
-rw-r--r--models/migrations/v1_11/v115.go4
-rw-r--r--models/migrations/v1_11/v116.go2
-rw-r--r--models/migrations/v1_12/v117.go2
-rw-r--r--models/migrations/v1_12/v118.go2
-rw-r--r--models/migrations/v1_12/v119.go2
-rw-r--r--models/migrations/v1_12/v120.go2
-rw-r--r--models/migrations/v1_12/v121.go2
-rw-r--r--models/migrations/v1_12/v122.go2
-rw-r--r--models/migrations/v1_12/v123.go2
-rw-r--r--models/migrations/v1_12/v124.go2
-rw-r--r--models/migrations/v1_12/v125.go2
-rw-r--r--models/migrations/v1_12/v126.go2
-rw-r--r--models/migrations/v1_12/v127.go2
-rw-r--r--models/migrations/v1_12/v128.go2
-rw-r--r--models/migrations/v1_12/v129.go2
-rw-r--r--models/migrations/v1_12/v130.go2
-rw-r--r--models/migrations/v1_12/v131.go2
-rw-r--r--models/migrations/v1_12/v132.go2
-rw-r--r--models/migrations/v1_12/v133.go2
-rw-r--r--models/migrations/v1_12/v134.go2
-rw-r--r--models/migrations/v1_12/v135.go2
-rw-r--r--models/migrations/v1_12/v136.go2
-rw-r--r--models/migrations/v1_12/v137.go2
-rw-r--r--models/migrations/v1_12/v138.go2
-rw-r--r--models/migrations/v1_12/v139.go2
-rw-r--r--models/migrations/v1_13/v140.go9
-rw-r--r--models/migrations/v1_13/v141.go2
-rw-r--r--models/migrations/v1_13/v142.go2
-rw-r--r--models/migrations/v1_13/v143.go2
-rw-r--r--models/migrations/v1_13/v144.go2
-rw-r--r--models/migrations/v1_13/v145.go2
-rw-r--r--models/migrations/v1_13/v146.go2
-rw-r--r--models/migrations/v1_13/v147.go2
-rw-r--r--models/migrations/v1_13/v148.go2
-rw-r--r--models/migrations/v1_13/v149.go2
-rw-r--r--models/migrations/v1_13/v150.go2
-rw-r--r--models/migrations/v1_13/v151.go2
-rw-r--r--models/migrations/v1_13/v152.go2
-rw-r--r--models/migrations/v1_13/v153.go2
-rw-r--r--models/migrations/v1_13/v154.go2
-rw-r--r--models/migrations/v1_14/main_test.go2
-rw-r--r--models/migrations/v1_14/v155.go2
-rw-r--r--models/migrations/v1_14/v156.go2
-rw-r--r--models/migrations/v1_14/v157.go13
-rw-r--r--models/migrations/v1_14/v158.go2
-rw-r--r--models/migrations/v1_14/v159.go2
-rw-r--r--models/migrations/v1_14/v160.go2
-rw-r--r--models/migrations/v1_14/v161.go2
-rw-r--r--models/migrations/v1_14/v162.go2
-rw-r--r--models/migrations/v1_14/v163.go2
-rw-r--r--models/migrations/v1_14/v164.go2
-rw-r--r--models/migrations/v1_14/v165.go12
-rw-r--r--models/migrations/v1_14/v166.go2
-rw-r--r--models/migrations/v1_14/v167.go2
-rw-r--r--models/migrations/v1_14/v168.go2
-rw-r--r--models/migrations/v1_14/v169.go2
-rw-r--r--models/migrations/v1_14/v170.go2
-rw-r--r--models/migrations/v1_14/v171.go2
-rw-r--r--models/migrations/v1_14/v172.go2
-rw-r--r--models/migrations/v1_14/v173.go2
-rw-r--r--models/migrations/v1_14/v174.go2
-rw-r--r--models/migrations/v1_14/v175.go2
-rw-r--r--models/migrations/v1_14/v176.go2
-rw-r--r--models/migrations/v1_14/v176_test.go2
-rw-r--r--models/migrations/v1_14/v177.go2
-rw-r--r--models/migrations/v1_14/v177_test.go2
-rw-r--r--models/migrations/v1_15/main_test.go2
-rw-r--r--models/migrations/v1_15/v178.go2
-rw-r--r--models/migrations/v1_15/v179.go2
-rw-r--r--models/migrations/v1_15/v180.go2
-rw-r--r--models/migrations/v1_15/v181.go2
-rw-r--r--models/migrations/v1_15/v181_test.go2
-rw-r--r--models/migrations/v1_15/v182.go2
-rw-r--r--models/migrations/v1_15/v182_test.go2
-rw-r--r--models/migrations/v1_15/v183.go2
-rw-r--r--models/migrations/v1_15/v184.go2
-rw-r--r--models/migrations/v1_15/v185.go2
-rw-r--r--models/migrations/v1_15/v186.go2
-rw-r--r--models/migrations/v1_15/v187.go2
-rw-r--r--models/migrations/v1_15/v188.go2
-rw-r--r--models/migrations/v1_16/main_test.go2
-rw-r--r--models/migrations/v1_16/v189.go2
-rw-r--r--models/migrations/v1_16/v189_test.go2
-rw-r--r--models/migrations/v1_16/v190.go2
-rw-r--r--models/migrations/v1_16/v191.go2
-rw-r--r--models/migrations/v1_16/v192.go2
-rw-r--r--models/migrations/v1_16/v193.go2
-rw-r--r--models/migrations/v1_16/v193_test.go2
-rw-r--r--models/migrations/v1_16/v194.go2
-rw-r--r--models/migrations/v1_16/v195.go2
-rw-r--r--models/migrations/v1_16/v195_test.go2
-rw-r--r--models/migrations/v1_16/v196.go2
-rw-r--r--models/migrations/v1_16/v197.go2
-rw-r--r--models/migrations/v1_16/v198.go2
-rw-r--r--models/migrations/v1_16/v199.go2
-rw-r--r--models/migrations/v1_16/v200.go2
-rw-r--r--models/migrations/v1_16/v201.go2
-rw-r--r--models/migrations/v1_16/v202.go2
-rw-r--r--models/migrations/v1_16/v203.go2
-rw-r--r--models/migrations/v1_16/v204.go2
-rw-r--r--models/migrations/v1_16/v205.go2
-rw-r--r--models/migrations/v1_16/v206.go2
-rw-r--r--models/migrations/v1_16/v207.go2
-rw-r--r--models/migrations/v1_16/v208.go2
-rw-r--r--models/migrations/v1_16/v209.go2
-rw-r--r--models/migrations/v1_16/v210.go2
-rw-r--r--models/migrations/v1_16/v210_test.go2
-rw-r--r--models/migrations/v1_17/main_test.go2
-rw-r--r--models/migrations/v1_17/v211.go2
-rw-r--r--models/migrations/v1_17/v212.go2
-rw-r--r--models/migrations/v1_17/v213.go2
-rw-r--r--models/migrations/v1_17/v214.go2
-rw-r--r--models/migrations/v1_17/v215.go2
-rw-r--r--models/migrations/v1_17/v216.go2
-rw-r--r--models/migrations/v1_17/v217.go2
-rw-r--r--models/migrations/v1_17/v218.go2
-rw-r--r--models/migrations/v1_17/v219.go2
-rw-r--r--models/migrations/v1_17/v220.go2
-rw-r--r--models/migrations/v1_17/v221.go2
-rw-r--r--models/migrations/v1_17/v221_test.go2
-rw-r--r--models/migrations/v1_17/v222.go2
-rw-r--r--models/migrations/v1_17/v223.go2
-rw-r--r--models/migrations/v1_18/main_test.go2
-rw-r--r--models/migrations/v1_18/v224.go2
-rw-r--r--models/migrations/v1_18/v225.go2
-rw-r--r--models/migrations/v1_18/v226.go2
-rw-r--r--models/migrations/v1_18/v227.go2
-rw-r--r--models/migrations/v1_18/v228.go2
-rw-r--r--models/migrations/v1_18/v229.go2
-rw-r--r--models/migrations/v1_18/v229_test.go2
-rw-r--r--models/migrations/v1_18/v230.go2
-rw-r--r--models/migrations/v1_18/v230_test.go2
-rw-r--r--models/migrations/v1_19/main_test.go2
-rw-r--r--models/migrations/v1_19/v231.go2
-rw-r--r--models/migrations/v1_19/v232.go2
-rw-r--r--models/migrations/v1_19/v233.go2
-rw-r--r--models/migrations/v1_19/v233_test.go2
-rw-r--r--models/migrations/v1_19/v234.go2
-rw-r--r--models/migrations/v1_19/v235.go2
-rw-r--r--models/migrations/v1_19/v236.go2
-rw-r--r--models/migrations/v1_19/v237.go2
-rw-r--r--models/migrations/v1_19/v238.go2
-rw-r--r--models/migrations/v1_19/v239.go2
-rw-r--r--models/migrations/v1_19/v240.go2
-rw-r--r--models/migrations/v1_19/v241.go2
-rw-r--r--models/migrations/v1_19/v242.go2
-rw-r--r--models/migrations/v1_19/v243.go2
-rw-r--r--models/migrations/v1_20/main_test.go2
-rw-r--r--models/migrations/v1_20/v244.go2
-rw-r--r--models/migrations/v1_20/v245.go2
-rw-r--r--models/migrations/v1_20/v246.go2
-rw-r--r--models/migrations/v1_20/v247.go2
-rw-r--r--models/migrations/v1_20/v248.go2
-rw-r--r--models/migrations/v1_20/v249.go2
-rw-r--r--models/migrations/v1_20/v250.go2
-rw-r--r--models/migrations/v1_20/v251.go2
-rw-r--r--models/migrations/v1_20/v252.go2
-rw-r--r--models/migrations/v1_20/v253.go2
-rw-r--r--models/migrations/v1_20/v254.go2
-rw-r--r--models/migrations/v1_20/v255.go2
-rw-r--r--models/migrations/v1_20/v256.go2
-rw-r--r--models/migrations/v1_20/v257.go2
-rw-r--r--models/migrations/v1_20/v258.go2
-rw-r--r--models/migrations/v1_20/v259.go4
-rw-r--r--models/migrations/v1_20/v259_test.go2
-rw-r--r--models/migrations/v1_21/main_test.go2
-rw-r--r--models/migrations/v1_21/v260.go2
-rw-r--r--models/migrations/v1_21/v261.go2
-rw-r--r--models/migrations/v1_21/v262.go2
-rw-r--r--models/migrations/v1_21/v263.go2
-rw-r--r--models/migrations/v1_21/v264.go2
-rw-r--r--models/migrations/v1_21/v265.go2
-rw-r--r--models/migrations/v1_21/v266.go2
-rw-r--r--models/migrations/v1_21/v267.go2
-rw-r--r--models/migrations/v1_21/v268.go2
-rw-r--r--models/migrations/v1_21/v269.go2
-rw-r--r--models/migrations/v1_21/v270.go2
-rw-r--r--models/migrations/v1_21/v271.go3
-rw-r--r--models/migrations/v1_21/v272.go3
-rw-r--r--models/migrations/v1_21/v273.go3
-rw-r--r--models/migrations/v1_21/v274.go3
-rw-r--r--models/migrations/v1_21/v275.go2
-rw-r--r--models/migrations/v1_21/v276.go2
-rw-r--r--models/migrations/v1_21/v277.go2
-rw-r--r--models/migrations/v1_21/v278.go2
-rw-r--r--models/migrations/v1_21/v279.go2
-rw-r--r--models/migrations/v1_22/main_test.go2
-rw-r--r--models/migrations/v1_22/v280.go2
-rw-r--r--models/migrations/v1_22/v281.go2
-rw-r--r--models/migrations/v1_22/v282.go2
-rw-r--r--models/migrations/v1_22/v283.go2
-rw-r--r--models/migrations/v1_22/v283_test.go2
-rw-r--r--models/migrations/v1_22/v284.go3
-rw-r--r--models/migrations/v1_22/v285.go2
-rw-r--r--models/migrations/v1_22/v286.go2
-rw-r--r--models/migrations/v1_22/v286_test.go2
-rw-r--r--models/migrations/v1_22/v287.go2
-rw-r--r--models/migrations/v1_22/v287_test.go2
-rw-r--r--models/migrations/v1_22/v288.go2
-rw-r--r--models/migrations/v1_22/v289.go2
-rw-r--r--models/migrations/v1_22/v290.go2
-rw-r--r--models/migrations/v1_22/v291.go2
-rw-r--r--models/migrations/v1_22/v292.go2
-rw-r--r--models/migrations/v1_22/v293.go2
-rw-r--r--models/migrations/v1_22/v293_test.go2
-rw-r--r--models/migrations/v1_22/v294.go2
-rw-r--r--models/migrations/v1_22/v294_test.go5
-rw-r--r--models/migrations/v1_22/v295.go2
-rw-r--r--models/migrations/v1_22/v296.go2
-rw-r--r--models/migrations/v1_22/v297.go2
-rw-r--r--models/migrations/v1_22/v298.go2
-rw-r--r--models/migrations/v1_23/main_test.go2
-rw-r--r--models/migrations/v1_23/v299.go2
-rw-r--r--models/migrations/v1_23/v300.go2
-rw-r--r--models/migrations/v1_23/v301.go2
-rw-r--r--models/migrations/v1_23/v302.go7
-rw-r--r--models/migrations/v1_23/v302_test.go51
-rw-r--r--models/migrations/v1_23/v303.go2
-rw-r--r--models/migrations/v1_23/v304.go7
-rw-r--r--models/migrations/v1_23/v304_test.go40
-rw-r--r--models/migrations/v1_23/v305.go2
-rw-r--r--models/migrations/v1_23/v306.go2
-rw-r--r--models/migrations/v1_23/v307.go2
-rw-r--r--models/migrations/v1_23/v308.go2
-rw-r--r--models/migrations/v1_23/v309.go2
-rw-r--r--models/migrations/v1_23/v310.go2
-rw-r--r--models/migrations/v1_23/v311.go2
-rw-r--r--models/migrations/v1_24/v312.go2
-rw-r--r--models/migrations/v1_24/v313.go2
-rw-r--r--models/migrations/v1_24/v314.go2
-rw-r--r--models/migrations/v1_24/v315.go2
-rw-r--r--models/migrations/v1_24/v316.go2
-rw-r--r--models/migrations/v1_24/v317.go2
-rw-r--r--models/migrations/v1_24/v318.go2
-rw-r--r--models/migrations/v1_24/v319.go2
-rw-r--r--models/migrations/v1_24/v320.go2
-rw-r--r--models/migrations/v1_6/v70.go2
-rw-r--r--models/migrations/v1_6/v71.go2
-rw-r--r--models/migrations/v1_6/v72.go2
-rw-r--r--models/migrations/v1_7/v73.go2
-rw-r--r--models/migrations/v1_7/v74.go2
-rw-r--r--models/migrations/v1_7/v75.go2
-rw-r--r--models/migrations/v1_8/v76.go2
-rw-r--r--models/migrations/v1_8/v77.go2
-rw-r--r--models/migrations/v1_8/v78.go2
-rw-r--r--models/migrations/v1_8/v79.go2
-rw-r--r--models/migrations/v1_8/v80.go2
-rw-r--r--models/migrations/v1_8/v81.go2
-rw-r--r--models/migrations/v1_9/v82.go2
-rw-r--r--models/migrations/v1_9/v83.go2
-rw-r--r--models/migrations/v1_9/v84.go2
-rw-r--r--models/migrations/v1_9/v85.go2
-rw-r--r--models/migrations/v1_9/v86.go2
-rw-r--r--models/migrations/v1_9/v87.go2
-rw-r--r--models/organization/org.go5
-rw-r--r--models/organization/org_list.go21
-rw-r--r--models/organization/org_list_test.go41
-rw-r--r--models/organization/org_test.go21
-rw-r--r--models/organization/team_repo.go33
-rw-r--r--models/organization/team_repo_test.go2
-rw-r--r--models/packages/container/search.go12
-rw-r--r--models/packages/descriptor.go6
-rw-r--r--models/packages/nuget/search.go2
-rw-r--r--models/packages/package_file.go5
-rw-r--r--models/packages/package_property.go18
-rw-r--r--models/packages/package_version.go36
-rw-r--r--models/perm/access/repo_permission.go43
-rw-r--r--models/perm/access/repo_permission_test.go46
-rw-r--r--models/project/column_test.go2
-rw-r--r--models/project/project.go4
-rw-r--r--models/pull/review_state.go5
-rw-r--r--models/renderhelper/commit_checker.go2
-rw-r--r--models/renderhelper/repo_comment.go31
-rw-r--r--models/renderhelper/repo_comment_test.go7
-rw-r--r--models/repo/org_repo.go31
-rw-r--r--models/repo/pushmirror_test.go2
-rw-r--r--models/repo/release.go63
-rw-r--r--models/repo/repo.go29
-rw-r--r--models/repo/repo_list.go40
-rw-r--r--models/repo/repo_list_test.go86
-rw-r--r--models/repo/repo_test.go15
-rw-r--r--models/repo/repo_unit.go6
-rw-r--r--models/repo/transfer.go4
-rw-r--r--models/repo/update.go14
-rw-r--r--models/repo/upload.go2
-rw-r--r--models/unit/unit.go15
-rw-r--r--models/user/avatar.go3
-rw-r--r--models/user/badge.go2
-rw-r--r--models/user/email_address_test.go8
-rw-r--r--models/user/search.go4
-rw-r--r--models/user/user.go36
-rw-r--r--models/user/user_list.go5
-rw-r--r--models/user/user_test.go98
-rw-r--r--models/webhook/webhook.go2
-rw-r--r--models/webhook/webhook_test.go2
-rw-r--r--modules/actions/workflows.go109
-rw-r--r--modules/actions/workflows_test.go18
-rw-r--r--modules/assetfs/embed.go375
-rw-r--r--modules/assetfs/embed_test.go98
-rw-r--r--modules/assetfs/layered.go4
-rw-r--r--modules/auth/httpauth/httpauth.go47
-rw-r--r--modules/auth/httpauth/httpauth_test.go43
-rw-r--r--modules/auth/openid/discovery_cache_test.go7
-rw-r--r--modules/auth/password/hash/common.go2
-rw-r--r--modules/auth/password/password.go2
-rw-r--r--modules/auth/password/password_test.go2
-rw-r--r--modules/auth/password/pwn/pwn.go2
-rw-r--r--modules/avatar/identicon/block.go4
-rw-r--r--modules/avatar/identicon/identicon.go2
-rw-r--r--modules/base/tool.go16
-rw-r--r--modules/base/tool_test.go19
-rw-r--r--modules/cache/cache.go2
-rw-r--r--modules/cache/cache_redis.go2
-rw-r--r--modules/cache/cache_twoqueue.go2
-rw-r--r--modules/cache/string_cache.go2
-rw-r--r--modules/charset/charset.go2
-rw-r--r--modules/charset/charset_test.go2
-rw-r--r--modules/commitstatus/commit_status.go (renamed from modules/structs/commit_status.go)60
-rw-r--r--modules/commitstatus/commit_status_test.go201
-rw-r--r--modules/fileicon/entry.go10
-rw-r--r--modules/fileicon/material.go3
-rw-r--r--modules/fileicon/material_test.go8
-rw-r--r--modules/git/attribute/checker.go7
-rw-r--r--modules/git/attribute/checker_test.go12
-rw-r--r--modules/git/blame.go40
-rw-r--r--modules/git/blob.go64
-rw-r--r--modules/git/cmdverb.go36
-rw-r--r--modules/git/command.go13
-rw-r--r--modules/git/commit.go15
-rw-r--r--modules/git/commit_info_nogogit.go40
-rw-r--r--modules/git/commit_reader.go132
-rw-r--r--modules/git/commit_sha256_test.go6
-rw-r--r--modules/git/commit_test.go12
-rw-r--r--modules/git/diff_test.go2
-rw-r--r--modules/git/error.go16
-rw-r--r--modules/git/foreachref/format.go2
-rw-r--r--modules/git/hook.go8
-rw-r--r--modules/git/key.go15
-rw-r--r--modules/git/languagestats/language_stats_nogogit.go14
-rw-r--r--modules/git/last_commit_cache.go2
-rw-r--r--modules/git/log_name_status.go5
-rw-r--r--modules/git/ref.go4
-rw-r--r--modules/git/repo.go5
-rw-r--r--modules/git/repo_commit.go21
-rw-r--r--modules/git/repo_gpg.go12
-rw-r--r--modules/git/repo_index.go2
-rw-r--r--modules/git/repo_stats.go7
-rw-r--r--modules/git/repo_tag.go6
-rw-r--r--modules/git/repo_tree.go11
-rw-r--r--modules/git/tree.go2
-rw-r--r--modules/git/tree_blob_nogogit.go36
-rw-r--r--modules/git/tree_entry.go86
-rw-r--r--modules/git/tree_entry_common_test.go76
-rw-r--r--modules/git/tree_entry_gogit.go10
-rw-r--r--modules/git/tree_entry_mode.go4
-rw-r--r--modules/git/tree_entry_nogogit.go4
-rw-r--r--modules/git/tree_entry_test.go47
-rw-r--r--modules/git/tree_gogit.go3
-rw-r--r--modules/git/tree_test.go2
-rw-r--r--modules/globallock/globallock_test.go2
-rw-r--r--modules/graceful/manager_windows.go3
-rw-r--r--modules/hostmatcher/hostmatcher.go11
-rw-r--r--modules/htmlutil/html.go6
-rw-r--r--modules/htmlutil/html_test.go9
-rw-r--r--modules/httpcache/httpcache.go2
-rw-r--r--modules/indexer/code/bleve/token/path/path.go2
-rw-r--r--modules/indexer/code/git.go4
-rw-r--r--modules/indexer/code/search.go2
-rw-r--r--modules/indexer/issues/indexer.go2
-rw-r--r--modules/indexer/issues/internal/tests/tests.go31
-rw-r--r--modules/issue/template/template.go8
-rw-r--r--modules/json/json.go3
-rw-r--r--modules/label/label.go14
-rw-r--r--modules/label/parser.go4
-rw-r--r--modules/lfs/pointer.go13
-rw-r--r--modules/lfs/pointer_scanner_gogit.go2
-rw-r--r--modules/lfstransfer/backend/util.go1
-rw-r--r--modules/log/event_format.go6
-rw-r--r--modules/log/flags.go2
-rw-r--r--modules/log/level_test.go6
-rw-r--r--modules/log/logger.go2
-rw-r--r--modules/markup/common/footnote.go8
-rw-r--r--modules/markup/console/console.go38
-rw-r--r--modules/markup/console/console_test.go32
-rw-r--r--modules/markup/html.go16
-rw-r--r--modules/markup/html_commit.go2
-rw-r--r--modules/markup/html_issue_test.go19
-rw-r--r--modules/markup/html_link.go4
-rw-r--r--modules/markup/html_node.go30
-rw-r--r--modules/markup/html_test.go4
-rw-r--r--modules/markup/markdown/markdown.go5
-rw-r--r--modules/markup/markdown/markdown_test.go23
-rw-r--r--modules/markup/markdown/math/block_renderer.go6
-rw-r--r--modules/markup/markdown/math/inline_renderer.go2
-rw-r--r--modules/markup/markdown/meta_test.go8
-rw-r--r--modules/markup/markdown/transform_blockquote.go2
-rw-r--r--modules/markup/markdown/transform_codespan.go2
-rw-r--r--modules/markup/markdown/transform_heading.go4
-rw-r--r--modules/markup/mdstripper/mdstripper.go2
-rw-r--r--modules/markup/renderer.go12
-rw-r--r--modules/markup/sanitizer_default.go9
-rw-r--r--modules/markup/sanitizer_default_test.go2
-rw-r--r--modules/migration/schemas_bindata.go24
-rw-r--r--modules/migration/schemas_static.go15
-rw-r--r--modules/optional/option.go6
-rw-r--r--modules/optional/serialization_test.go2
-rw-r--r--modules/options/options_bindata.go17
-rw-r--r--modules/options/options_dynamic.go (renamed from modules/options/dynamic.go)0
-rw-r--r--modules/options/static.go14
-rw-r--r--modules/packages/container/const.go (renamed from models/packages/container/const.go)2
-rw-r--r--modules/packages/container/metadata.go26
-rw-r--r--modules/packages/container/metadata_test.go5
-rw-r--r--modules/packages/content_store.go3
-rw-r--r--modules/packages/npm/creator.go4
-rw-r--r--modules/packages/npm/metadata.go2
-rw-r--r--modules/packages/nuget/metadata.go90
-rw-r--r--modules/packages/nuget/metadata_test.go96
-rw-r--r--modules/packages/nuget/symbol_extractor.go8
-rw-r--r--modules/packages/nuget/symbol_extractor_test.go6
-rw-r--r--modules/packages/rubygems/marshal.go2
-rw-r--r--modules/packages/swift/metadata.go2
-rw-r--r--modules/private/serv.go10
-rw-r--r--modules/public/public.go16
-rw-r--r--modules/public/public_bindata.go17
-rw-r--r--modules/public/public_dynamic.go (renamed from modules/public/serve_dynamic.go)0
-rw-r--r--modules/public/serve_static.go24
-rw-r--r--modules/queue/base_levelqueue_common.go2
-rw-r--r--modules/queue/base_redis.go2
-rw-r--r--modules/queue/base_test.go2
-rw-r--r--modules/queue/manager.go5
-rw-r--r--modules/queue/workerqueue_test.go20
-rw-r--r--modules/repository/branch.go9
-rw-r--r--modules/repository/commits_test.go2
-rw-r--r--modules/repository/init.go2
-rw-r--r--modules/repository/repo.go133
-rw-r--r--modules/reqctx/datastore.go5
-rw-r--r--modules/setting/config_env.go2
-rw-r--r--modules/setting/config_env_test.go3
-rw-r--r--modules/setting/config_provider.go2
-rw-r--r--modules/setting/git_test.go10
-rw-r--r--modules/setting/indexer.go2
-rw-r--r--modules/setting/log.go4
-rw-r--r--modules/setting/markup.go4
-rw-r--r--modules/setting/mirror.go6
-rw-r--r--modules/setting/repository.go6
-rw-r--r--modules/setting/security.go2
-rw-r--r--modules/setting/ssh.go26
-rw-r--r--modules/setting/storage.go14
-rw-r--r--modules/ssh/init.go7
-rw-r--r--modules/ssh/ssh.go2
-rw-r--r--modules/structs/admin_user.go7
-rw-r--r--modules/structs/commit_status_test.go174
-rw-r--r--modules/structs/git_blob.go3
-rw-r--r--modules/structs/hook.go21
-rw-r--r--modules/structs/issue.go2
-rw-r--r--modules/structs/issue_tracked_time.go5
-rw-r--r--modules/structs/org.go2
-rw-r--r--modules/structs/release.go1
-rw-r--r--modules/structs/repo.go6
-rw-r--r--modules/structs/repo_actions.go46
-rw-r--r--modules/structs/repo_branch.go1
-rw-r--r--modules/structs/repo_file.go83
-rw-r--r--modules/structs/status.go38
-rw-r--r--modules/structs/user.go8
-rw-r--r--modules/structs/user_email.go1
-rw-r--r--modules/structs/user_gpgkey.go4
-rw-r--r--modules/structs/user_key.go4
-rw-r--r--modules/templates/eval/eval_test.go2
-rw-r--r--modules/templates/helper.go48
-rw-r--r--modules/templates/htmlrenderer.go6
-rw-r--r--modules/templates/scopedtmpl/scopedtmpl.go38
-rw-r--r--modules/templates/static.go22
-rw-r--r--modules/templates/templates_bindata.go17
-rw-r--r--modules/templates/templates_dynamic.go (renamed from modules/templates/dynamic.go)0
-rw-r--r--modules/templates/util_date_legacy.go23
-rw-r--r--modules/templates/util_date_test.go16
-rw-r--r--modules/templates/util_json.go4
-rw-r--r--modules/templates/util_render.go70
-rw-r--r--modules/templates/util_render_legacy.go53
-rw-r--r--modules/templates/util_render_test.go139
-rw-r--r--modules/test/utils.go10
-rw-r--r--modules/testlogger/testlogger.go2
-rw-r--r--modules/timeutil/executable.go50
-rw-r--r--modules/typesniffer/typesniffer.go65
-rw-r--r--modules/typesniffer/typesniffer_test.go16
-rw-r--r--modules/util/error.go4
-rw-r--r--modules/util/map.go13
-rw-r--r--modules/util/map_test.go26
-rw-r--r--modules/util/remove.go6
-rw-r--r--modules/util/rotatingfilewriter/writer_test.go2
-rw-r--r--modules/util/string.go23
-rw-r--r--modules/validation/helpers.go8
-rw-r--r--modules/validation/helpers_test.go5
-rw-r--r--modules/web/middleware/binding.go2
-rw-r--r--modules/web/router.go4
-rw-r--r--modules/web/router_path.go41
-rw-r--r--modules/web/router_test.go99
-rw-r--r--modules/web/routing/logger.go5
-rw-r--r--modules/webhook/type.go2
-rw-r--r--modules/zstd/zstd_test.go8
-rw-r--r--options/fileicon/material-icon-rules.json91
-rw-r--r--options/fileicon/material-icon-svgs.json356
-rw-r--r--options/locale/TRANSLATORS1
-rw-r--r--options/locale/locale_cs-CZ.ini22
-rw-r--r--options/locale/locale_de-DE.ini98
-rw-r--r--options/locale/locale_el-GR.ini21
-rw-r--r--options/locale/locale_en-US.ini79
-rw-r--r--options/locale/locale_es-ES.ini21
-rw-r--r--options/locale/locale_fa-IR.ini13
-rw-r--r--options/locale/locale_fi-FI.ini9
-rw-r--r--options/locale/locale_fr-FR.ini82
-rw-r--r--options/locale/locale_ga-IE.ini74
-rw-r--r--options/locale/locale_hu-HU.ini9
-rw-r--r--options/locale/locale_id-ID.ini8
-rw-r--r--options/locale/locale_is-IS.ini10
-rw-r--r--options/locale/locale_it-IT.ini15
-rw-r--r--options/locale/locale_ja-JP.ini62
-rw-r--r--options/locale/locale_ko-KR.ini9
-rw-r--r--options/locale/locale_lv-LV.ini21
-rw-r--r--options/locale/locale_nl-NL.ini12
-rw-r--r--options/locale/locale_pl-PL.ini13
-rw-r--r--options/locale/locale_pt-BR.ini20
-rw-r--r--options/locale/locale_pt-PT.ini82
-rw-r--r--options/locale/locale_ru-RU.ini21
-rw-r--r--options/locale/locale_si-LK.ini13
-rw-r--r--options/locale/locale_sk-SK.ini7
-rw-r--r--options/locale/locale_sv-SE.ini10
-rw-r--r--options/locale/locale_tr-TR.ini139
-rw-r--r--options/locale/locale_uk-UA.ini2473
-rw-r--r--options/locale/locale_zh-CN.ini1230
-rw-r--r--options/locale/locale_zh-HK.ini8
-rw-r--r--options/locale/locale_zh-TW.ini22
-rw-r--r--package-lock.json2536
-rw-r--r--package.json80
-rw-r--r--public/assets/img/svg/gitea-chef.svg2
-rw-r--r--public/assets/img/svg/gitea-codecommit.svg2
-rw-r--r--public/assets/img/svg/gitea-debian.svg2
-rw-r--r--public/assets/img/svg/gitea-gitbucket.svg2
-rw-r--r--public/assets/img/svg/gitea-gitlab.svg2
-rw-r--r--public/assets/img/svg/gitea-google.svg2
-rw-r--r--public/assets/img/svg/gitea-maven.svg2
-rw-r--r--public/assets/img/svg/gitea-microsoftonline.svg2
-rw-r--r--public/assets/img/svg/gitea-npm.svg2
-rw-r--r--public/assets/img/svg/gitea-onedev.svg2
-rw-r--r--public/assets/img/svg/gitea-openid.svg2
-rw-r--r--public/assets/img/svg/gitea-rubygems.svg2
-rw-r--r--public/assets/img/svg/gitea-swift.svg2
-rw-r--r--public/assets/img/svg/gitea-vagrant.svg2
-rw-r--r--public/assets/img/svg/octicon-pause.svg1
-rw-r--r--public/assets/img/svg/octicon-sparkle.svg1
-rw-r--r--routers/api/actions/artifacts_utils.go2
-rw-r--r--routers/api/packages/alpine/alpine.go4
-rw-r--r--routers/api/packages/api.go242
-rw-r--r--routers/api/packages/arch/arch.go2
-rw-r--r--routers/api/packages/cargo/cargo.go7
-rw-r--r--routers/api/packages/chef/chef.go2
-rw-r--r--routers/api/packages/composer/composer.go7
-rw-r--r--routers/api/packages/conan/conan.go2
-rw-r--r--routers/api/packages/conda/conda.go28
-rw-r--r--routers/api/packages/container/auth.go2
-rw-r--r--routers/api/packages/container/blob.go37
-rw-r--r--routers/api/packages/container/container.go122
-rw-r--r--routers/api/packages/container/manifest.go329
-rw-r--r--routers/api/packages/cran/cran.go2
-rw-r--r--routers/api/packages/debian/debian.go6
-rw-r--r--routers/api/packages/generic/generic.go2
-rw-r--r--routers/api/packages/goproxy/goproxy.go2
-rw-r--r--routers/api/packages/helm/helm.go2
-rw-r--r--routers/api/packages/maven/maven.go2
-rw-r--r--routers/api/packages/npm/npm.go4
-rw-r--r--routers/api/packages/nuget/api_v2.go46
-rw-r--r--routers/api/packages/nuget/nuget.go6
-rw-r--r--routers/api/packages/pub/pub.go2
-rw-r--r--routers/api/packages/pypi/pypi.go2
-rw-r--r--routers/api/packages/rpm/rpm.go4
-rw-r--r--routers/api/packages/rubygems/rubygems.go50
-rw-r--r--routers/api/packages/rubygems/rubygems_test.go41
-rw-r--r--routers/api/packages/swift/swift.go2
-rw-r--r--routers/api/packages/vagrant/vagrant.go2
-rw-r--r--routers/api/v1/admin/action.go93
-rw-r--r--routers/api/v1/admin/org.go4
-rw-r--r--routers/api/v1/admin/repo.go2
-rw-r--r--routers/api/v1/admin/user.go16
-rw-r--r--routers/api/v1/admin/user_badge.go6
-rw-r--r--routers/api/v1/api.go83
-rw-r--r--routers/api/v1/misc/markup.go4
-rw-r--r--routers/api/v1/misc/signing.go78
-rw-r--r--routers/api/v1/org/action.go102
-rw-r--r--routers/api/v1/org/block.go6
-rw-r--r--routers/api/v1/org/member.go10
-rw-r--r--routers/api/v1/org/org.go14
-rw-r--r--routers/api/v1/org/team.go6
-rw-r--r--routers/api/v1/repo/action.go325
-rw-r--r--routers/api/v1/repo/blob.go2
-rw-r--r--routers/api/v1/repo/branch.go10
-rw-r--r--routers/api/v1/repo/collaborators.go6
-rw-r--r--routers/api/v1/repo/commits.go36
-rw-r--r--routers/api/v1/repo/file.go414
-rw-r--r--routers/api/v1/repo/issue.go11
-rw-r--r--routers/api/v1/repo/issue_comment.go18
-rw-r--r--routers/api/v1/repo/issue_dependency.go10
-rw-r--r--routers/api/v1/repo/issue_stopwatch.go56
-rw-r--r--routers/api/v1/repo/issue_subscription.go4
-rw-r--r--routers/api/v1/repo/issue_tracked_time.go2
-rw-r--r--routers/api/v1/repo/migrate.go2
-rw-r--r--routers/api/v1/repo/patch.go68
-rw-r--r--routers/api/v1/repo/pull.go10
-rw-r--r--routers/api/v1/repo/release.go4
-rw-r--r--routers/api/v1/repo/repo.go2
-rw-r--r--routers/api/v1/repo/status.go11
-rw-r--r--routers/api/v1/repo/wiki.go12
-rw-r--r--routers/api/v1/shared/action.go187
-rw-r--r--routers/api/v1/shared/runners.go45
-rw-r--r--routers/api/v1/swagger/repo.go34
-rw-r--r--routers/api/v1/user/action.go94
-rw-r--r--routers/api/v1/user/app.go6
-rw-r--r--routers/api/v1/user/block.go6
-rw-r--r--routers/api/v1/user/follower.go14
-rw-r--r--routers/api/v1/user/gpg_key.go2
-rw-r--r--routers/api/v1/user/key.go2
-rw-r--r--routers/api/v1/user/repo.go6
-rw-r--r--routers/api/v1/user/star.go2
-rw-r--r--routers/api/v1/user/user.go8
-rw-r--r--routers/api/v1/user/watch.go2
-rw-r--r--routers/api/v1/utils/hook.go102
-rw-r--r--routers/api/v1/utils/hook_test.go82
-rw-r--r--routers/api/v1/utils/main_test.go21
-rw-r--r--routers/common/markup.go6
-rw-r--r--routers/common/pagetmpl.go28
-rw-r--r--routers/install/install.go14
-rw-r--r--routers/private/hook_post_receive.go26
-rw-r--r--routers/private/hook_verification_test.go4
-rw-r--r--routers/private/serv.go8
-rw-r--r--routers/web/admin/auths.go2
-rw-r--r--routers/web/admin/config.go4
-rw-r--r--routers/web/admin/diagnosis.go8
-rw-r--r--routers/web/admin/notice.go5
-rw-r--r--routers/web/admin/orgs.go2
-rw-r--r--routers/web/admin/packages.go5
-rw-r--r--routers/web/admin/users.go13
-rw-r--r--routers/web/auth/auth.go14
-rw-r--r--routers/web/auth/oauth.go8
-rw-r--r--routers/web/auth/oauth2_provider.go29
-rw-r--r--routers/web/auth/openid.go5
-rw-r--r--routers/web/devtest/devtest.go2
-rw-r--r--routers/web/explore/code.go10
-rw-r--r--routers/web/explore/org.go2
-rw-r--r--routers/web/explore/repo.go3
-rw-r--r--routers/web/explore/user.go4
-rw-r--r--routers/web/feed/branch.go2
-rw-r--r--routers/web/feed/convert.go4
-rw-r--r--routers/web/home.go4
-rw-r--r--routers/web/misc/markup.go2
-rw-r--r--routers/web/misc/misc.go2
-rw-r--r--routers/web/org/home.go2
-rw-r--r--routers/web/org/members.go5
-rw-r--r--routers/web/org/org_labels.go36
-rw-r--r--routers/web/org/projects.go5
-rw-r--r--routers/web/org/setting.go107
-rw-r--r--routers/web/org/teams.go15
-rw-r--r--routers/web/repo/actions/actions.go10
-rw-r--r--routers/web/repo/actions/view.go115
-rw-r--r--routers/web/repo/branch.go9
-rw-r--r--routers/web/repo/cherry_pick.go193
-rw-r--r--routers/web/repo/commit.go24
-rw-r--r--routers/web/repo/compare.go8
-rw-r--r--routers/web/repo/editor.go1051
-rw-r--r--routers/web/repo/editor_apply_patch.go51
-rw-r--r--routers/web/repo/editor_cherry_pick.go86
-rw-r--r--routers/web/repo/editor_error.go82
-rw-r--r--routers/web/repo/editor_fork.go31
-rw-r--r--routers/web/repo/editor_preview.go41
-rw-r--r--routers/web/repo/editor_test.go79
-rw-r--r--routers/web/repo/editor_uploader.go61
-rw-r--r--routers/web/repo/editor_util.go110
-rw-r--r--routers/web/repo/fork.go42
-rw-r--r--routers/web/repo/githttp.go8
-rw-r--r--routers/web/repo/issue.go16
-rw-r--r--routers/web/repo/issue_comment.go34
-rw-r--r--routers/web/repo/issue_label.go36
-rw-r--r--routers/web/repo/issue_label_test.go94
-rw-r--r--routers/web/repo/issue_list.go2
-rw-r--r--routers/web/repo/issue_new.go5
-rw-r--r--routers/web/repo/issue_stopwatch.go40
-rw-r--r--routers/web/repo/issue_view.go20
-rw-r--r--routers/web/repo/milestone.go5
-rw-r--r--routers/web/repo/packages.go5
-rw-r--r--routers/web/repo/patch.go126
-rw-r--r--routers/web/repo/projects.go5
-rw-r--r--routers/web/repo/pull.go20
-rw-r--r--routers/web/repo/release.go15
-rw-r--r--routers/web/repo/repo.go4
-rw-r--r--routers/web/repo/setting/lfs.go14
-rw-r--r--routers/web/repo/setting/protected_branch.go3
-rw-r--r--routers/web/repo/setting/protected_tag.go3
-rw-r--r--routers/web/repo/setting/setting.go31
-rw-r--r--routers/web/repo/setting/settings_test.go24
-rw-r--r--routers/web/repo/setting/webhook.go1
-rw-r--r--routers/web/repo/treelist.go3
-rw-r--r--routers/web/repo/view.go65
-rw-r--r--routers/web/repo/view_file.go391
-rw-r--r--routers/web/repo/view_home.go38
-rw-r--r--routers/web/repo/view_readme.go74
-rw-r--r--routers/web/repo/wiki.go193
-rw-r--r--routers/web/repo/wiki_test.go9
-rw-r--r--routers/web/shared/actions/runners.go10
-rw-r--r--routers/web/shared/label/label.go26
-rw-r--r--routers/web/shared/packages/packages.go10
-rw-r--r--routers/web/shared/secrets/secrets.go4
-rw-r--r--routers/web/shared/user/header.go9
-rw-r--r--routers/web/user/code.go10
-rw-r--r--routers/web/user/home.go13
-rw-r--r--routers/web/user/home_test.go2
-rw-r--r--routers/web/user/notification.go20
-rw-r--r--routers/web/user/package.go71
-rw-r--r--routers/web/user/profile.go39
-rw-r--r--routers/web/user/search.go2
-rw-r--r--routers/web/user/setting/profile.go9
-rw-r--r--routers/web/web.go59
-rw-r--r--services/actions/cleanup.go124
-rw-r--r--services/actions/clear_tasks.go6
-rw-r--r--services/actions/commit_status.go18
-rw-r--r--services/actions/context.go43
-rw-r--r--services/actions/interface.go4
-rw-r--r--services/actions/job_emitter.go22
-rw-r--r--services/actions/notifier.go41
-rw-r--r--services/actions/notifier_helper.go72
-rw-r--r--services/actions/schedule_tasks.go1
-rw-r--r--services/actions/workflow.go180
-rw-r--r--services/agit/agit.go27
-rw-r--r--services/asymkey/commit.go67
-rw-r--r--services/asymkey/commit_test.go54
-rw-r--r--services/asymkey/sign.go167
-rw-r--r--services/auth/basic.go15
-rw-r--r--services/auth/oauth2.go7
-rw-r--r--services/auth/source/ldap/source_authenticate.go2
-rw-r--r--services/auth/source/ldap/source_search.go4
-rw-r--r--services/auth/source/ldap/source_sync.go7
-rw-r--r--services/auth/source/oauth2/urlmapping.go10
-rw-r--r--services/context/api.go14
-rw-r--r--services/context/base_form.go4
-rw-r--r--services/context/context.go9
-rw-r--r--services/context/pagination.go8
-rw-r--r--services/context/permission.go7
-rw-r--r--services/context/private.go9
-rw-r--r--services/context/repo.go142
-rw-r--r--services/context/upload/upload.go23
-rw-r--r--services/convert/convert.go246
-rw-r--r--services/convert/pull.go22
-rw-r--r--services/convert/pull_test.go7
-rw-r--r--services/convert/repository.go6
-rw-r--r--services/convert/status.go38
-rw-r--r--services/doctor/actions.go2
-rw-r--r--services/doctor/dbconsistency.go3
-rw-r--r--services/doctor/paths.go5
-rw-r--r--services/doctor/repository.go4
-rw-r--r--services/feed/feed_test.go2
-rw-r--r--services/forms/org.go6
-rw-r--r--services/forms/repo_form.go124
-rw-r--r--services/forms/repo_form_editor.go57
-rw-r--r--services/forms/user_form_test.go23
-rw-r--r--services/git/commit.go10
-rw-r--r--services/gitdiff/csv.go10
-rw-r--r--services/gitdiff/gitdiff.go56
-rw-r--r--services/gitdiff/gitdiff_test.go6
-rw-r--r--services/gitdiff/submodule_test.go1
-rw-r--r--services/issue/assignee.go4
-rw-r--r--services/issue/issue.go92
-rw-r--r--services/issue/issue_test.go6
-rw-r--r--services/issue/status.go4
-rw-r--r--services/lfs/locks.go10
-rw-r--r--services/lfs/server.go24
-rw-r--r--services/mailer/mail_test.go2
-rw-r--r--services/mailer/sender/message_test.go4
-rw-r--r--services/migrations/codecommit.go15
-rw-r--r--services/migrations/gitea_uploader.go6
-rw-r--r--services/migrations/github.go4
-rw-r--r--services/migrations/migrate.go8
-rw-r--r--services/mirror/mirror_pull.go4
-rw-r--r--services/notify/notifier.go2
-rw-r--r--services/notify/notify.go29
-rw-r--r--services/notify/null.go3
-rw-r--r--services/oauth2_provider/access_token.go13
-rw-r--r--services/oauth2_provider/additional_scopes_test.go2
-rw-r--r--services/oauth2_provider/init.go2
-rw-r--r--services/oauth2_provider/jwtsigningkey.go2
-rw-r--r--services/oauth2_provider/token.go2
-rw-r--r--services/org/team_test.go6
-rw-r--r--services/org/user.go3
-rw-r--r--services/packages/arch/vercmp.go9
-rw-r--r--services/packages/cargo/index.go2
-rw-r--r--services/packages/cleanup/cleanup.go211
-rw-r--r--services/packages/container/cleanup.go4
-rw-r--r--services/packages/container/common.go31
-rw-r--r--services/packages/packages.go30
-rw-r--r--services/packages/rpm/repository.go4
-rw-r--r--services/pull/commit_status.go106
-rw-r--r--services/pull/commit_status_test.go101
-rw-r--r--services/pull/merge.go50
-rw-r--r--services/pull/merge_prepare.go6
-rw-r--r--services/pull/merge_squash.go14
-rw-r--r--services/pull/merge_test.go25
-rw-r--r--services/pull/pull.go8
-rw-r--r--services/pull/reviewer.go2
-rw-r--r--services/pull/update.go45
-rw-r--r--services/repository/adopt.go13
-rw-r--r--services/repository/adopt_test.go2
-rw-r--r--services/repository/avatar.go6
-rw-r--r--services/repository/branch.go5
-rw-r--r--services/repository/check.go2
-rw-r--r--services/repository/commitstatus/commitstatus.go8
-rw-r--r--services/repository/create.go18
-rw-r--r--services/repository/create_test.go2
-rw-r--r--services/repository/delete.go18
-rw-r--r--services/repository/files/content.go226
-rw-r--r--services/repository/files/content_test.go190
-rw-r--r--services/repository/files/diff.go2
-rw-r--r--services/repository/files/file.go25
-rw-r--r--services/repository/files/file_test.go27
-rw-r--r--services/repository/files/patch.go1
-rw-r--r--services/repository/files/temp_repo.go17
-rw-r--r--services/repository/files/tree.go8
-rw-r--r--services/repository/files/update.go311
-rw-r--r--services/repository/files/upload.go231
-rw-r--r--services/repository/fork.go16
-rw-r--r--services/repository/fork_test.go5
-rw-r--r--services/repository/generate.go34
-rw-r--r--services/repository/generate_test.go24
-rw-r--r--services/repository/gitgraph/graph_models.go6
-rw-r--r--services/repository/gitgraph/graph_test.go25
-rw-r--r--services/repository/init.go7
-rw-r--r--services/repository/merge_upstream.go7
-rw-r--r--services/repository/migrate.go10
-rw-r--r--services/repository/push.go44
-rw-r--r--services/repository/repository.go87
-rw-r--r--services/repository/template.go4
-rw-r--r--services/repository/transfer.go8
-rw-r--r--services/user/update.go39
-rw-r--r--services/user/update_test.go12
-rw-r--r--services/user/user.go5
-rw-r--r--services/versioned_migration/migration.go2
-rw-r--r--services/webhook/dingtalk.go6
-rw-r--r--services/webhook/discord.go8
-rw-r--r--services/webhook/feishu.go42
-rw-r--r--services/webhook/feishu_test.go6
-rw-r--r--services/webhook/general.go31
-rw-r--r--services/webhook/matrix.go6
-rw-r--r--services/webhook/msteams.go14
-rw-r--r--services/webhook/notifier.go165
-rw-r--r--services/webhook/packagist.go4
-rw-r--r--services/webhook/payloader.go6
-rw-r--r--services/webhook/slack.go6
-rw-r--r--services/webhook/telegram.go8
-rw-r--r--services/webhook/webhook_test.go4
-rw-r--r--services/webhook/wechatwork.go6
-rw-r--r--services/webtheme/webtheme.go8
-rw-r--r--services/wiki/wiki.go6
-rw-r--r--services/wiki/wiki_test.go4
-rw-r--r--tailwind.config.js3
-rw-r--r--templates/admin/auth/edit.tmpl19
-rw-r--r--templates/admin/packages/list.tmpl19
-rw-r--r--templates/admin/repo/list.tmpl21
-rw-r--r--templates/admin/user/edit.tmpl6
-rw-r--r--templates/admin/user/view.tmpl2
-rw-r--r--templates/base/head_navbar.tmpl55
-rw-r--r--templates/base/head_navbar_icons.tmpl25
-rw-r--r--templates/devtest/flex-list.tmpl2
-rw-r--r--templates/explore/repos.tmpl4
-rw-r--r--templates/home.tmpl12
-rw-r--r--templates/org/header.tmpl2
-rw-r--r--templates/org/home.tmpl4
-rw-r--r--templates/org/member/members.tmpl4
-rw-r--r--templates/org/settings/delete.tmpl35
-rw-r--r--templates/org/settings/navbar.tmpl3
-rw-r--r--templates/org/settings/options.tmpl178
-rw-r--r--templates/org/settings/options_dangerzone.tmpl93
-rw-r--r--templates/org/team/members.tmpl2
-rw-r--r--templates/org/team/sidebar.tmpl2
-rw-r--r--templates/org/team/teams.tmpl2
-rw-r--r--templates/package/content/container.tmpl44
-rw-r--r--templates/package/content/pypi.tmpl2
-rw-r--r--templates/package/shared/view.tmpl13
-rw-r--r--templates/post-install.tmpl2
-rw-r--r--templates/projects/list.tmpl13
-rw-r--r--templates/projects/view.tmpl4
-rw-r--r--templates/repo/actions/runs_list.tmpl45
-rw-r--r--templates/repo/actions/view_component.tmpl2
-rw-r--r--templates/repo/actions/workflow_dispatch_inputs.tmpl3
-rw-r--r--templates/repo/blame.tmpl2
-rw-r--r--templates/repo/branch/list.tmpl22
-rw-r--r--templates/repo/commit_page.tmpl8
-rw-r--r--templates/repo/commit_status.tmpl3
-rw-r--r--templates/repo/commits_list.tmpl6
-rw-r--r--templates/repo/commits_list_small.tmpl4
-rw-r--r--templates/repo/diff/box.tmpl2
-rw-r--r--templates/repo/diff/compare.tmpl2
-rw-r--r--templates/repo/editor/cherry_pick.tmpl11
-rw-r--r--templates/repo/editor/commit_form.tmpl23
-rw-r--r--templates/repo/editor/common_breadcrumb.tmpl16
-rw-r--r--templates/repo/editor/common_top.tmpl6
-rw-r--r--templates/repo/editor/delete.tmpl4
-rw-r--r--templates/repo/editor/edit.tmpl73
-rw-r--r--templates/repo/editor/fork.tmpl18
-rw-r--r--templates/repo/editor/patch.tmpl7
-rw-r--r--templates/repo/editor/upload.tmpl20
-rw-r--r--templates/repo/empty.tmpl2
-rw-r--r--templates/repo/forks.tmpl14
-rw-r--r--templates/repo/graph.tmpl39
-rw-r--r--templates/repo/graph/commits.tmpl9
-rw-r--r--templates/repo/graph/div.tmpl10
-rw-r--r--templates/repo/header.tmpl10
-rw-r--r--templates/repo/home_sidebar_bottom.tmpl6
-rw-r--r--templates/repo/issue/card.tmpl17
-rw-r--r--templates/repo/issue/filter_item_label.tmpl2
-rw-r--r--templates/repo/issue/filter_list.tmpl4
-rw-r--r--templates/repo/issue/labels/label_edit_modal.tmpl5
-rw-r--r--templates/repo/issue/labels/label_list.tmpl6
-rw-r--r--templates/repo/issue/milestones.tmpl14
-rw-r--r--templates/repo/issue/search.tmpl1
-rw-r--r--templates/repo/issue/sidebar/assignee_list.tmpl2
-rw-r--r--templates/repo/issue/sidebar/label_list.tmpl2
-rw-r--r--templates/repo/issue/sidebar/reviewer_list.tmpl2
-rw-r--r--templates/repo/issue/sidebar/stopwatch_timetracker.tmpl4
-rw-r--r--templates/repo/issue/sidebar/wip_switch.tmpl2
-rw-r--r--templates/repo/issue/view_content/attachments.tmpl2
-rw-r--r--templates/repo/issue/view_content/comments.tmpl5
-rw-r--r--templates/repo/issue/view_content/pull_merge_box.tmpl2
-rw-r--r--templates/repo/issue/view_title.tmpl2
-rw-r--r--templates/repo/latest_commit.tmpl4
-rw-r--r--templates/repo/migrate/migrate.tmpl2
-rw-r--r--templates/repo/pulls/fork.tmpl2
-rw-r--r--templates/repo/release/list.tmpl10
-rw-r--r--templates/repo/release/new.tmpl14
-rw-r--r--templates/repo/settings/branches.tmpl15
-rw-r--r--templates/repo/settings/collaboration.tmpl43
-rw-r--r--templates/repo/settings/deploy_keys.tmpl13
-rw-r--r--templates/repo/settings/lfs_file.tmpl4
-rw-r--r--templates/repo/settings/options.tmpl2
-rw-r--r--templates/repo/settings/webhook/history.tmpl2
-rw-r--r--templates/repo/settings/webhook/settings.tmpl12
-rw-r--r--templates/repo/tag/list.tmpl85
-rw-r--r--templates/repo/tag/name.tmpl2
-rw-r--r--templates/repo/unicode_escape_prompt.tmpl10
-rw-r--r--templates/repo/view_content.tmpl6
-rw-r--r--templates/repo/view_file.tmpl90
-rw-r--r--templates/repo/view_list.tmpl5
-rw-r--r--templates/repo/wiki/new.tmpl2
-rw-r--r--templates/repo/wiki/view.tmpl33
-rw-r--r--templates/shared/issuelist.tmpl15
-rw-r--r--templates/shared/repo/list.tmpl (renamed from templates/explore/repo_list.tmpl)8
-rw-r--r--templates/shared/repo/search.tmpl (renamed from templates/shared/repo_search.tmpl)0
-rw-r--r--templates/shared/secrets/add_list.tmpl26
-rw-r--r--templates/shared/user/profile_big_avatar.tmpl2
-rw-r--r--templates/swagger/ui.tmpl1
-rw-r--r--templates/swagger/v1_json.tmpl1346
-rw-r--r--templates/user/auth/signup_inner.tmpl3
-rw-r--r--templates/user/dashboard/feeds.tmpl4
-rw-r--r--templates/user/notification/notification_div.tmpl2
-rw-r--r--templates/user/notification/notification_subscriptions.tmpl4
-rw-r--r--templates/user/profile.tmpl8
-rw-r--r--templates/user/settings/applications.tmpl2
-rw-r--r--templates/user/settings/organization.tmpl2
-rw-r--r--templates/user/settings/profile.tmpl2
-rw-r--r--tests/e2e/e2e_test.go2
-rw-r--r--tests/integration/actions_delete_run_test.go181
-rw-r--r--tests/integration/actions_runner_test.go8
-rw-r--r--tests/integration/actions_trigger_test.go180
-rw-r--r--tests/integration/admin_user_test.go42
-rw-r--r--tests/integration/api_actions_delete_run_test.go98
-rw-r--r--tests/integration/api_actions_runner_test.go34
-rw-r--r--tests/integration/api_helper_for_declarative_test.go2
-rw-r--r--tests/integration/api_issue_test.go12
-rw-r--r--tests/integration/api_packages_chef_test.go8
-rw-r--r--tests/integration/api_packages_container_test.go54
-rw-r--r--tests/integration/api_packages_debian_test.go2
-rw-r--r--tests/integration/api_packages_goproxy_test.go2
-rw-r--r--tests/integration/api_packages_maven_test.go2
-rw-r--r--tests/integration/api_packages_nuget_test.go97
-rw-r--r--tests/integration/api_packages_rpm_test.go8
-rw-r--r--tests/integration/api_packages_test.go24
-rw-r--r--tests/integration/api_repo_archive_test.go17
-rw-r--r--tests/integration/api_repo_file_create_test.go15
-rw-r--r--tests/integration/api_repo_file_delete_test.go28
-rw-r--r--tests/integration/api_repo_file_update_test.go9
-rw-r--r--tests/integration/api_repo_get_contents_list_test.go26
-rw-r--r--tests/integration/api_repo_get_contents_test.go138
-rw-r--r--tests/integration/api_repo_languages_test.go5
-rw-r--r--tests/integration/api_repo_lfs_test.go7
-rw-r--r--tests/integration/api_repo_license_test.go6
-rw-r--r--tests/integration/api_repo_test.go2
-rw-r--r--tests/integration/api_repo_variables_test.go8
-rw-r--r--tests/integration/api_user_variables_test.go8
-rw-r--r--tests/integration/auth_ldap_test.go5
-rw-r--r--tests/integration/change_default_branch_test.go97
-rw-r--r--tests/integration/cmd_keys_test.go17
-rw-r--r--tests/integration/editor_test.go626
-rw-r--r--tests/integration/empty_repo_test.go52
-rw-r--r--tests/integration/ephemeral_actions_runner_deletion_test.go77
-rw-r--r--tests/integration/git_general_test.go49
-rw-r--r--tests/integration/git_misc_test.go102
-rw-r--r--tests/integration/git_push_test.go14
-rw-r--r--tests/integration/gpg_ssh_git_test.go (renamed from tests/integration/gpg_git_test.go)35
-rw-r--r--tests/integration/html_helper.go2
-rw-r--r--tests/integration/integration_test.go5
-rw-r--r--tests/integration/issue_test.go42
-rw-r--r--tests/integration/lfs_view_test.go9
-rw-r--r--tests/integration/links_test.go69
-rw-r--r--tests/integration/org_count_test.go4
-rw-r--r--tests/integration/org_test.go35
-rw-r--r--tests/integration/project_test.go2
-rw-r--r--tests/integration/pull_compare_test.go7
-rw-r--r--tests/integration/pull_merge_test.go11
-rw-r--r--tests/integration/pull_status_test.go29
-rw-r--r--tests/integration/release_test.go6
-rw-r--r--tests/integration/repo_commits_test.go49
-rw-r--r--tests/integration/repo_fork_test.go6
-rw-r--r--tests/integration/repo_merge_upstream_test.go32
-rw-r--r--tests/integration/repo_test.go19
-rw-r--r--tests/integration/repo_webhook_test.go857
-rw-r--r--tests/integration/repofiles_change_test.go189
-rw-r--r--tests/integration/setting_test.go6
-rw-r--r--tests/integration/signup_test.go11
-rw-r--r--tests/integration/ssh_key_test.go2
-rw-r--r--tests/integration/timetracking_test.go10
-rw-r--r--tests/integration/user_test.go2
-rw-r--r--tests/integration/webfinger_test.go7
-rw-r--r--tests/integration/workflow_run_api_check_test.go167
-rw-r--r--tests/test_utils.go3
-rwxr-xr-xtools/generate-svg.js1
-rwxr-xr-xtools/lint-go-gopls.sh2
-rw-r--r--web_src/css/actions.css1
-rw-r--r--web_src/css/base.css72
-rw-r--r--web_src/css/editor/combomarkdowneditor.css64
-rw-r--r--web_src/css/features/expander.css96
-rw-r--r--web_src/css/features/gitgraph.css71
-rw-r--r--web_src/css/features/projects.css27
-rw-r--r--web_src/css/features/tribute.css32
-rw-r--r--web_src/css/form.css1
-rw-r--r--web_src/css/home.css4
-rw-r--r--web_src/css/index.css5
-rw-r--r--web_src/css/markup/content.css21
-rw-r--r--web_src/css/modules/animations.css3
-rw-r--r--web_src/css/modules/breadcrumb.css6
-rw-r--r--web_src/css/modules/dimmer.css2
-rw-r--r--web_src/css/modules/label.css80
-rw-r--r--web_src/css/modules/list.css1
-rw-r--r--web_src/css/modules/navbar.css17
-rw-r--r--web_src/css/modules/toast.css2
-rw-r--r--web_src/css/repo.css142
-rw-r--r--web_src/css/repo/file-view.css92
-rw-r--r--web_src/css/repo/home.css1
-rw-r--r--web_src/css/repo/issue-card.css10
-rw-r--r--web_src/css/repo/issue-label.css25
-rw-r--r--web_src/css/repo/linebutton.css16
-rw-r--r--web_src/css/repo/packages.css25
-rw-r--r--web_src/css/repo/release-tag.css10
-rw-r--r--web_src/css/repo/wiki.css4
-rw-r--r--web_src/css/shared/flex-list.css13
-rw-r--r--web_src/css/themes/theme-gitea-dark.css1
-rw-r--r--web_src/css/themes/theme-gitea-light.css1
-rw-r--r--web_src/fomantic/build/components/dropdown.js5
-rw-r--r--web_src/fomantic/build/components/modal.js8
-rw-r--r--web_src/js/bootstrap.ts3
-rw-r--r--web_src/js/components/ActivityHeatmap.vue4
-rw-r--r--web_src/js/components/ContextPopup.vue12
-rw-r--r--web_src/js/components/DashboardRepoList.vue40
-rw-r--r--web_src/js/components/DiffCommitSelector.vue6
-rw-r--r--web_src/js/components/DiffFileTree.vue2
-rw-r--r--web_src/js/components/DiffFileTreeItem.vue4
-rw-r--r--web_src/js/components/PullRequestMergeForm.vue40
-rw-r--r--web_src/js/components/RepoActionView.vue5
-rw-r--r--web_src/js/components/RepoActivityTopAuthors.vue8
-rw-r--r--web_src/js/components/RepoCodeFrequency.vue12
-rw-r--r--web_src/js/components/RepoContributors.vue2
-rw-r--r--web_src/js/components/RepoRecentCommits.vue10
-rw-r--r--web_src/js/components/ViewFileTree.vue50
-rw-r--r--web_src/js/components/ViewFileTreeItem.vue102
-rw-r--r--web_src/js/components/ViewFileTreeStore.ts45
-rw-r--r--web_src/js/features/common-button.test.ts14
-rw-r--r--web_src/js/features/common-button.ts67
-rw-r--r--web_src/js/features/common-fetch-action.ts96
-rw-r--r--web_src/js/features/common-issue-list.ts6
-rw-r--r--web_src/js/features/comp/ComboMarkdownEditor.ts16
-rw-r--r--web_src/js/features/comp/ConfirmModal.ts37
-rw-r--r--web_src/js/features/comp/EditorUpload.test.ts12
-rw-r--r--web_src/js/features/comp/EditorUpload.ts23
-rw-r--r--web_src/js/features/comp/LabelEdit.ts4
-rw-r--r--web_src/js/features/comp/SearchUserBox.ts2
-rw-r--r--web_src/js/features/comp/TextExpander.ts1
-rw-r--r--web_src/js/features/copycontent.ts14
-rw-r--r--web_src/js/features/dropzone.ts6
-rw-r--r--web_src/js/features/emoji.ts6
-rw-r--r--web_src/js/features/file-view.ts76
-rw-r--r--web_src/js/features/install.ts2
-rw-r--r--web_src/js/features/repo-code.ts13
-rw-r--r--web_src/js/features/repo-editor.ts62
-rw-r--r--web_src/js/features/repo-graph.ts143
-rw-r--r--web_src/js/features/repo-issue-list.ts14
-rw-r--r--web_src/js/features/repo-issue.ts27
-rw-r--r--web_src/js/features/repo-new.ts2
-rw-r--r--web_src/js/features/repo-projects.ts3
-rw-r--r--web_src/js/features/repo-wiki.ts3
-rw-r--r--web_src/js/features/stopwatch.ts2
-rw-r--r--web_src/js/features/tribute.ts13
-rw-r--r--web_src/js/globals.d.ts5
-rw-r--r--web_src/js/index.ts5
-rw-r--r--web_src/js/markup/anchors.ts25
-rw-r--r--web_src/js/markup/html2markdown.ts8
-rw-r--r--web_src/js/markup/mermaid.ts3
-rw-r--r--web_src/js/modules/fomantic/base.ts8
-rw-r--r--web_src/js/modules/fomantic/dropdown.ts17
-rw-r--r--web_src/js/modules/fomantic/modal.ts34
-rw-r--r--web_src/js/modules/tippy.ts30
-rw-r--r--web_src/js/modules/toast.ts31
-rw-r--r--web_src/js/render/pdf.ts17
-rw-r--r--web_src/js/render/plugin.ts10
-rw-r--r--web_src/js/render/plugins/3d-viewer.ts60
-rw-r--r--web_src/js/render/plugins/pdf-viewer.ts20
-rw-r--r--web_src/js/svg.ts3
-rw-r--r--web_src/js/utils/dom.test.ts6
-rw-r--r--web_src/js/utils/dom.ts83
-rw-r--r--web_src/js/utils/html.test.ts8
-rw-r--r--web_src/js/utils/html.ts32
1282 files changed, 23635 insertions, 14673 deletions
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index ab30e1789d..104f1cfeed 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -4,10 +4,10 @@
"features": {
// installs nodejs into container
"ghcr.io/devcontainers/features/node:1": {
- "version": "20"
+ "version": "lts"
},
"ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
- "ghcr.io/devcontainers-contrib/features/poetry:2": {},
+ "ghcr.io/devcontainers-extra/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.12"
},
diff --git a/.dockerignore b/.dockerignore
index 94aca6b8d3..843f12a7be 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -36,15 +36,6 @@ _testmain.go
coverage.all
cpu.out
-/modules/migration/bindata.go
-/modules/migration/bindata.go.hash
-/modules/options/bindata.go
-/modules/options/bindata.go.hash
-/modules/public/bindata.go
-/modules/public/bindata.go.hash
-/modules/templates/bindata.go
-/modules/templates/bindata.go.hash
-
*.db
*.log
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index f9e1050240..57c6b19600 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -91,6 +91,7 @@ module.exports = {
plugins: ['@vitest/eslint-plugin'],
globals: vitestPlugin.environments.env.globals,
rules: {
+ 'github/unescaped-html-literal': [0],
'@vitest/consistent-test-filename': [0],
'@vitest/consistent-test-it': [0],
'@vitest/expect-expect': [0],
@@ -423,7 +424,7 @@ module.exports = {
'github/no-useless-passive': [2],
'github/prefer-observers': [2],
'github/require-passive-events': [2],
- 'github/unescaped-html-literal': [0],
+ 'github/unescaped-html-literal': [2],
'grouped-accessor-pairs': [2],
'guard-for-in': [0],
'id-blacklist': [0],
diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml
index 64090d6490..f6720bf2f6 100644
--- a/.github/workflows/pull-compliance.yml
+++ b/.github/workflows/pull-compliance.yml
@@ -37,7 +37,7 @@ jobs:
python-version: "3.12"
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: pip install poetry
@@ -66,7 +66,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend
@@ -137,7 +137,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend
@@ -186,7 +186,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend
diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml
index a3fd8ca621..55c2d2bf5e 100644
--- a/.github/workflows/pull-db-tests.yml
+++ b/.github/workflows/pull-db-tests.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
services:
pgsql:
- image: postgres:12
+ image: postgres:14
env:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml
index 87e931117c..cc3fbd9c34 100644
--- a/.github/workflows/pull-e2e-tests.yml
+++ b/.github/workflows/pull-e2e-tests.yml
@@ -25,7 +25,7 @@ jobs:
check-latest: true
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend frontend deps-backend
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index 2558a16a71..c2cc14f771 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -22,7 +22,7 @@ jobs:
check-latest: true
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend
diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml
index 37b3ff57d2..c9c15c31a0 100644
--- a/.github/workflows/release-tag-rc.yml
+++ b/.github/workflows/release-tag-rc.yml
@@ -23,7 +23,7 @@ jobs:
check-latest: true
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend
diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml
index 4250623da0..ae717c7cec 100644
--- a/.github/workflows/release-tag-version.yml
+++ b/.github/workflows/release-tag-version.yml
@@ -27,7 +27,7 @@ jobs:
check-latest: true
- uses: actions/setup-node@v4
with:
- node-version: 22
+ node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend
diff --git a/.gitignore b/.gitignore
index 272ea2b5ed..0791a17c71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,14 +42,10 @@ _testmain.go
coverage.all
cpu.out
-/modules/migration/bindata.go
-/modules/migration/bindata.go.hash
-/modules/options/bindata.go
-/modules/options/bindata.go.hash
-/modules/public/bindata.go
-/modules/public/bindata.go.hash
-/modules/templates/bindata.go
-/modules/templates/bindata.go.hash
+/modules/migration/bindata.*
+/modules/options/bindata.*
+/modules/public/bindata.*
+/modules/templates/bindata.*
*.db
*.log
diff --git a/.golangci.yml b/.golangci.yml
index c176d2115c..70efd288ff 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -45,6 +45,10 @@ linters:
desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system
+ nolintlint:
+ allow-unused: false
+ require-explanation: true
+ require-specific: true
gocritic:
disabled-checks:
- ifElseChain
@@ -83,6 +87,10 @@ linters:
- name: unreachable-code
- name: var-declaration
- name: var-naming
+ arguments:
+ - [] # AllowList - do not remove as args for the rule are positional and won't work without lists first
+ - [] # DenyList
+ - - skip-package-name-checks: true # supress errors from underscore in migration packages
staticcheck:
checks:
- all
diff --git a/.ignore b/.ignore
index 5b96dabd38..29912ad5c3 100644
--- a/.ignore
+++ b/.ignore
@@ -1,9 +1,6 @@
*.min.css
*.min.js
/assets/*.json
-/modules/options/bindata.go
-/modules/public/bindata.go
-/modules/templates/bindata.go
/options/gitignore
/options/license
/public/assets
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca2e67929c..b72ac4849a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,429 @@ This changelog goes through 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.com).
+## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
+
+* BREAKING
+ * Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
+ * Improve log format (#33814)
+ * Fix markdown render behaviors (#34122)
+ * Add package version api endpoints (#34173)
+
+* FEATURES
+ * Enforce two-factor auth (2FA: TOTP or WebAuthn) (#34187)
+ * Add fullscreen mode as a more efficient operation way to view projects (#34081)
+ * Add anonymous access support for private/unlisted repositories (#34051)
+ * Support public code/issue access for private repositories (#33127)
+ * Add middleware for request prioritization (#33951)
+ * Add cli flags LDAP group configuration (#33933)
+ * Add file tree to file view page (#32721)
+ * Add material icons for file list (#33837)
+ * Artifacts download api for artifact actions v4 (#33510)
+ * Support choose email when creating a commit via web UI (#33432)
+ * Add basic auth support to rss/atom feeds (#33371)
+ * Add sorting by exclusive labels (issue priority) (#33206)
+ * Add sub issue list support (#32940)
+ * Private README.md for organization (#32872)
+ * Email option to embed images as base64 instead of link (#32061)
+ * Option to delay conflict checking of old pull requests until page view (#27779)
+ * Worktime tracking for the organization level (#19808)
+
+* PERFORMANCE
+ * Add cache for common package queries (#22491)
+ * Move issue pin to an standalone table for querying performance (#33452)
+ * Improve commits list performance to reduce unnecessary database queries (#33528)
+ * Optimize total count of feed when loading activities in user dashboard. (#33841)
+ * Optimize heatmap query (#33853)
+ * Only use prev and next buttons for pagination on user dashboard (#33981)
+ * Improve pull request list API performance (#34052)
+ * Cache GPG keys, emails and users when list commits (#34086)
+ * Refactor Git Attribute & performance optimization (#34154)
+ * Performance optimization for tags synchronization (#34355) #34522
+
+* ENHANCEMENTS
+ * Code
+ * Display when a release attachment was uploaded (#34261)
+ * Support creating relative link to raw path in markdown (#34105)
+ * Improve code block readability and isolate copy button (#34009)
+ * Improve repository commit view (#33877)
+ * Full-file syntax highlighting for diff pages (#33766)
+ * Clone repository with Tea CLI (#33725)
+ * Improve sync fork behavior (#33319)
+ * Make git clone URL could use current signed-in user (#33091)
+ * Add submodule diff links (#33097)
+ * Link to tree views of submodules if possible (#33424)
+ * Only keep popular licenses (#33832)
+ * De-emphasize signed commits (#31160)
+
+ * Actions
+ * Add flat-square action badge style (#34062)
+ * Update action status badge layout (#34018)
+ * Download actions job logs from API (#33858)
+ * Always show the "rerun" button for action jobs (#33692)
+ * Add auto-expanding running actions step (#30058)
+ * Update status check for all supported on.pull_request.types in Gitea (#33117)
+ * Workflow_dispatch use workflow from trigger branch (#33098)
+ * Add action auto-scroll (#30057)
+ * Add workflow_job webhook (#33694)
+ * Add a button editing action secret (#34462)
+
+ * Pull Request
+ * Auto expand "New PR" form (#33971)
+ * Mark parent directory as viewed when all files are viewed (#33958)
+ * Show info about maintainers are allowed to edit a PR (#33738)
+ * Automerge supports deleting branch automatically after merging (#32343)
+ * Add additional command hints for PowerShell & CMD (#33548)
+
+ * Issues
+ * Allow filtering issues by any assignee (#33343)
+ * Show warning on navigation if currently editing comment or title (#32920)
+ * Make tracked time representation display as hours (#33315)
+ * Add No Results Prompt Message on Issue List Page (#33699)
+ * Add sort option recentclose for issues and pulls (#34525) #34539
+
+ * Packages
+ * Link to nuget dependencies (#26554)
+ * Add composor source field (#33502)
+
+ * Administration
+ * Improve navbar: add "admin" tip, add "active" style (#32927)
+ * Add a option "--user-type bot" to admin user create, improve role display (#27885)
+ * Improve admin user view page (#33735)
+ * Support performance trace (#32973)
+ * Change pprof labels to be prometheus compatible (#32865)
+ * Allow admins and org owners to change org member public status (#28294)
+ * Optimize the installation page (#32994)
+ * Make public URL generation configurable (#34250)
+ * Add a --fullname arg to gitea admin user create. (#34241)
+
+ * Others
+ * Improve oauth2 error handling (#33969)
+ * Fail mirroring more gracefully (#34002)
+ * Align User Details Page Header Layout with Design Specifications (#34192)
+ * Webhook add X-Gitea-Hook-Installation-Target-Type Header (#33752)
+ * Optimize the dashboard (#32990)
+ * Improve button layout on small screens (#33633)
+ * Add cropping support when modifying the user/org/repo avatar (#33498)
+ * Make ROOT_URL support using request Host header (#32564)
+ * Add `show more` organizations icon in user's profile (#32986)
+ * Introduce `--page-space-bottom` at 64px (#30692)
+ * Improve theme display (#30671)
+ * Add alphabetical project sorting (#33504)
+ * Add global lock for migrations to make upgrade more safe with multiple replications (#33706)
+ * Add descriptions for private repo public access settings and improve the UI (#34057)
+
+* API
+ * Actions Runner rest api (#33873)
+ * Inclusion of rename organization api (#33303)
+ * Add API to support link package to repository and unlink it (#33481)
+ * Add API endpoint to request contents of multiple files simultaniously (#34139)
+ * Actions artifacts API list/download check status upload confirmed (#34273)
+ * Add API routes to lock and unlock issues (#34165)
+ * Fix some user name usages (#33689)
+ * Allow filtering /repos/{owner}/{repo}/pulls by target base branch queryparam (#33684)
+ * Improve swagger generation (#33664)
+ * Support Ephemeral action runners (#33570)
+ * Support workflow event dispatch via API (#33545)
+ * Support workflow event dispatch via API (#32059)
+ * Added Description Field for Secrets and Variables (#33526)
+ * Reject star-related requests if stars are disabled (#33208)
+ * Let API create and edit system webhooks, attempt 2 (#33180)
+ * Use `Project-URL` metadata field to get a PyPI package's homepage URL (#33089)
+ * Add `last_committer_date` and `last_author_date` for file contents API (#32921)
+
+* REFACTORS
+ * Remove context from git struct (#33793)
+ * Refactor admin/common.ts (#33788)
+ * Refactor repo-settings.ts (#33785)
+ * Refactor repo-issue.ts (#33784)
+ * Small refactor to reduce unnecessary database queries and remove duplicated functions (#33779)
+ * Refactor initRepoBranchTagSelector to use new init framework (#33776)
+ * Refactor buttons to use new init framework (#33774)
+ * Refactor markup and pdf-viewer to use new init framework (#33772)
+ * Refactor error system (#33771)
+ * Refactor mail code (#33768)
+ * Update TypeScript types (#33799)
+ * Refactor older tests to use testify (#33140)
+ * Move notifywatch to service layer (#33825)
+ * Decouple context from repository related structs (#33823)
+ * Remove context from mail struct (#33811)
+ * Refactor dropdown ellipsis (#34123)
+ * Refactor functions to reduce repopath expose (#33892)
+ * Refactor repo-diff.ts (#33746)
+ * Refactor web route handler (#33488)
+ * Refactor user & avatar (#33433)
+ * Refactor user package (#33423)
+ * Refactor decouple context from migration structs (#33399)
+ * Refactor context flash msg and global variables (#33375)
+ * Refactor response writer & access logger (#33323)
+ * Refactor ref type (#33242)
+ * Refactor context repository (#33202)
+ * Refactor legacy JS (#33115)
+ * Refactor legacy line-number and scroll code (#33094)
+ * Refactor env var related code (#33075)
+ * Move SetMerged to service layer (#33045)
+ * Merge updatecommentattachment functions (#33044)
+ * Refactor pull-request compare&create page (#33071)
+ * Refactor repo-new.ts (#33070)
+ * Refactor pagination (#33037)
+ * Refactor tests (#33021)
+ * Refactor markup render to fix various path problems (#34114)
+ * Refactor Branch struct in package modules/git (#33980)
+ * Don't create duplicated functions for code repositories and wiki repositories (#33924)
+ * Move git references checking to gitrepo packages to reduce expose of repository path (#33891)
+ * Refactor cache-control (#33861)
+ * Decouple diff stats query from actual diffing (#33810)
+ * Move part of updating protected branch logic to service layer (#33742)
+ * Decouple Batch from git.Repository to simplify usage without requiring the creation of a Repository struct. (#34001)
+ * Refactor tmpl and blob_excerpt (#32967)
+ * Refactor template & test related code (#32938)
+ * Refactor db package and remove unnecessary `DumpTables` (#32930)
+ * Refactor pprof labels and process desc (#32909)
+ * Refactor repo-projects.ts (#32892)
+ * Refactor getpatch/getdiff functions and remove unnecessary fallback (#32817)
+ * Uniform all temporary directories and allow customizing temp path (#32352)
+ * Remove context from retry downloader (#33871)
+ * Refactor global init code and add more comments (#33755)
+ * Remove some unnecessary template helpers (#33069)
+ * Move and rename UpdateRepository (#34136)
+ * Move hooks function to gitrepo and reduce expose repopath (#33890)
+ * Add abstraction layer to delete repository from disk (#33879)
+ * Add abstraction layer to check if the repository exists on disk (#33874)
+ * Move ParseCommitWithSSHSignature to service layer (#34087)
+ * Move duplicated functions (#33977)
+ * Extract code to their own functions for push update (#33944)
+ * Move gitgraph from modules to services layer (#33527)
+ * Move commits signature and verify functions to service layers (#33605)
+ * Use `CloseIssue` and `ReopenIssue` instead of `ChangeStatus` (#32467)
+ * Refactor arch route handlers (#32993)
+ * Refactor "string truncate" (#32984)
+ * Refactor arch route handlers (#32972)
+ * Clarify path param naming (#32969)
+ * Refactor request context (#32956)
+ * Move some errors to their own sub packages (#32880)
+ * Move RepoTransfer from models to models/repo sub package (#32506)
+ * Move delete deploy keys into service layer (#32201)
+ * Refactor webhook events (#33337)
+ * Move some Actions related functions from `routers` to `services` (#33280)
+ * Refactor RefName (#33234)
+ * Refactor context RefName and RepoAssignment (#33226)
+ * Refactor repository transfer (#33211)
+ * Refactor error system (#33626)
+ * Refactor error system (#33610)
+ * Refactor package (routes and error handling, npm peer dependency) (#33111)
+ * Use test context in tests and new loop system in benchmarks (#33648)
+ * Some small refactors (#33144)
+ * Simplify context ref name (#33267)
+
+* BUGFIXES
+ * Fix some dropdown problems on the issue sidebar (#34308) #34327
+ * Do not return archive download URLs in API if downloads are disabled (#34324) #34338
+ * Fix LFS files being editable in web UI (#34356) #34362
+ * Fix only text/* being viewable in web UI (#34374) #34378
+ * Fix LFS file not stored in LFS when uploaded/edited via API or web UI (#34367)
+ * Grey out expired artifact on Artifacts list (#34314) #34404
+ * Fix incorrect divergence cache after switching default branch (#34370) #34406
+ * Refactor commit message rendering and fix bugs (#34412) #34414
+ * Merge and tweak markup editor expander CSS (#34409) #34415
+ * Fix GetUsersByEmails (#34423) #34425
+ * Only git operations should update last changed of a repository (#34388) #34427
+ * Fix comment textarea scroll issue in Firefox (#34438) #34446
+ * Fix repo broken check (#34444) #34452
+ * Fix remove org user failure on mssql (#34449) #34453
+ * Fix Workflow run Not Found page (#34459) #34466
+ * When updating comment, if the content is the same, just return and not update the database (#34422) #34464
+ * Fix project board view (#34470) #34475
+ * Fix get / delete runner to use consistent http 404 and 500 status (#34480) #34488
+ * Fix url validation in webhook add/edit API (#34492) #34496
+ * Fix edithook api can not update package, status and workflow_job events (#34495) #34499
+ * Fix ephemeral runner deletion (#34447) #34513
+ * Don't display error log when .git-blame-ignore-revs doesn't exist (#34457)
+ * Only allow admins to rename default/protected branches (#33276)
+ * Improve "lock conversation" UI (#34207)
+ * Fix incorrect file links (#34189)
+ * Optimize Overflow Menu (#34183)
+ * Check user/org repo limit instead of doer (#34147)
+ * Make markdown render match GitHub's behavior (#34129)
+ * Fix team permission (#34128)
+ * Correctly handle submodule view and avoid throwing 500 error (#34121)
+ * Fix users being able bypass limits with repo transfers (#34031)
+ * Avoid creating unnecessary temporary cat file sub process (#33942)
+ * Refactor organization menu (#33928)
+ * Fix various Fomantic UI and htmx problems (#33851)
+ * Fix 500 error when error occurred in migration page (#33256)
+ * Validate that the tag doesn't exist when creating a tag via the web (#33241)
+ * Add missed transaction on setmerged (#33079)
+ * Rework create/fork/adopt/generate repository to make sure resources will be cleanup once failed (#31035)
+ * Valid email address should only start with alphanumeric (#28174)
+ * Fix webhook url (#34186)
+ * Fix "toAbsoluteLocaleDate" test when system locale is not en-US (#33939)
+ * Fix file name could not be searched if the file was not a text file when using the Bleve indexer (#33959)
+ * Fix cannot delete runners via the modal dialog (#33895)
+ * Fix unpin hint on the pinned pull requests (#33207)
+ * Fix parentCommit invalid memory address or nil pointer dereference. (#33204)
+ * Fix comment header padding (#33377)
+ * Fix some migration and repo name problems (#33986)
+ * Fix various trivial frontend problems (#34263)
+ * Fix Set Email Preference dropdown and button placement (#34255)
+ * Fix quoted replies incorrectly render user input as part of the quote (#34216)
+ * Fix button alignments and remove unnecessary styles (#34206)
+ * Restore form inputs on organization create error (#34201)
+ * Try to fix ACME (3rd) (#33807)
+ * Fix incorrect ref "blob" (#33240)
+ * Fix dynamic content loading init problem (#33748)
+ * Fix git empty check and HEAD request (#33690)
+ * Fix Untranslated Text on Actions Page (#33635)
+ * Fix issue label delete incorrect labels webhook payload (#34575)
+ * Fix incorrect page navigation with up and down arrow on last item of dashboard repos (#34570)
+ * Fix/improve avatar sync from LDAP (#34573)
+ * Fix some trivial problems (#34579)
+ * Retain issue sort type when a keyword search is introduced (#34559)
+ * Always use an empty line to separate the commit message and trailer (#34512)
+ * Fix line-button issue after file selection in file tree (#34574)
+ * Fix doctor deleting orphaned issues attachments (#34142)
+ * Add webhook assigning test and fix possible bug (#34420)
+ * Fix possible nil description of pull request when migrating from CodeCommit (#34541)
+ * Refactor commit reader (#34542)
+ * Fix possible pull request broken when leave the page immediately after clicking the update button #34509
+ * Ignore "Close" error when uploading container blob (#34620)
+ * Fix missed merge commit sha and time when migrating from codecommit (#34645)
+ * Fix GetUsersByEmails (#34643)
+ * Misc CSS fixes (#34638)
+ * Add codecommit to supported services in api docs (#34626)
+ * Validate hex colors when creating/editing labels (#34623)
+ * Fix possible pull request broken when leave the page immediately after clicking the update button (#34509)
+ * Fix margin issue in markup paragraph rendering (#34599)
+ * Fix migration pull request title too long (#34577)
+ * Fix footnote jump behavior on the issue page. (#34621)
+ * Fix "oras" OCI client compatibility (#34666)
+ * Fix last admin check when syncing users (#34649)
+ * Fix skip paths check on tag push events in workflows (#34602) #34670
+
+* MISC
+
+ * Bump to alpine 3.22 (#34613)
+ * Make pull request and issue history more compact (#34588)
+ * Run integration tests against postgres 14 (#34514) #34536
+ * Enable addtional linters (#34085)
+ * Enable testifylint rules (#34075)
+ * Enable staticcheck QFxxxx rules (#34064)
+ * Improve Actions test (#32883)
+ * Drop fomantic build (#33845)
+ * Go1.24 (#33562)
+ * Run yamllint with strict mode, fix issue (#33551)
+ * Disable cron task to update license (#33486)
+ * Optimize makefile help information generation (#33390)
+ * Convert github.com/xanzy/go-gitlab into gitlab.com/gitlab-org/api/client-go (#33126)
+ * Add missed changelogs (#33649)
+ * Update .changelog file to add performance label group (#33472)
+ * Add missing POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES in app.example.ini (#33363)
+ * Update README screenshots (#33347)
+ * Update unrs-resolver (#34279)
+ * Update go&js dependencies (#34262)
+ * Optimize the calling code of queryElems (#34235)
+ * Update protected_branch.tmpl (#34193)
+ * Feat/optimize span svg layout (#34185)
+ * Set MERMAID_MAX_SOURCE_CHARACTERS to 50000 (#34152)
+ * Update JS and PY deps (#34143)
+ * Add Chinese translations for README files (#34132)
+ * Use `overflow-wrap: anywhere` to replace `word-break: break-all` (#34126)
+ * Clarify ownership in password change error messages (#34092)
+ * Add toggleClass function in dom.ts (#34063)
+ * Update to golangci-lint v2 (#34054)
+ * Update Makefile test comments (#34013)
+ * Update go mod dependencies (#33988)
+ * Use filepath.Join instead of path.Join for file system file operations (#33978)
+ * Prepare common tmpl functions in a middleware (#33957)
+ * Remove unused or abused styles (#33918)
+ * Update JS and PY deps, misc tweaks (#33903)
+ * Try to figure out attribute checker problem (#33901)
+ * Add lock for a repository pull mirror (#33876)
+ * Fine tune push mirror UI (#33866)
+ * Improve issue & code search (#33860)
+ * Use pullrequestlist instead of []*pullrequest (#33765)
+ * Upgrade act to 0.261.4 and actions-proto-go to v0.4.1 (#33760)
+ * Align sidebar gears to the right (#33721)
+ * Update Go dependencies (skip blevesearch, meilisearch) (#33655)
+ * Add migrations and doctor fixes (#33556)
+ * Remove "class-name" from svg icon (#33540)
+ * Update MAINTAINERS (#33529)
+ * Add "No data available" display when list is empty (#33517)
+ * Use `git diff-tree` for `DiffFileTree` on diff pages (#33514)
+ * Give organisation members access to organisation feeds (#33508)
+ * Update feishu icon (#33470)
+ * Hide/disable unusable UI elements when a repository is archived (#33459)
+ * Update `@github/text-expander-element` to 2.9.0 (#33435)
+ * Do not access GitRepo when a repo is being created (#33380)
+ * Fix incorrect ref usages (#33301)
+ * Prepare for support performance trace (#33286)
+ * Enable Typescript `noImplicitThis` (#33250)
+ * Remove unused CSS styles and move some styles to proper files (#33217)
+ * Add .run to gitignore (#33175)
+ * Fix typo in gitea downloader test and add missing codebase in `ToGitServiceType` (#33146)
+ * Remove extended glob pattern from branch protection UI (#33125)
+ * Clean up legacy form CSS styles (#33081)
+ * Unset XDG_HOME_CONFIG as gitea manages configuration locations (#33067)
+ * Add IntelliJ Gateway's .uuid to gitignore (#33052)
+ * User facing messages for AGit errors (#33012)
+ * Always show assignees on right (#33006)
+ * Fix eslint (#33002)
+ * Update JS dependencies (#32914)
+ * Bump x/net (#32896) (#32900)
+ * Only activity tab needs heatmap data loading (#34652)
+
+## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
+
+* SECURITY
+ * Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
+ * Update net package (#34228) (#34232)
+* BUGFIXES
+ * Fix releases sidebar navigation link (#34436) #34439
+ * Fix bug webhook milestone is not right. (#34419) #34429
+ * Fix two missed null value checks on the wiki page. (#34205) (#34215)
+ * Swift files can be passed either as file or as form value (#34068) (#34236)
+ * Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
+ * Upgrade github v61 -> v71 to fix migrating bug (#34389)
+ * Fix bug when visiting comparation page (#34334) (#34364)
+ * Fix wrong review requests when updating the pull request (#34286) (#34304)
+ * Fix github migration error when using multiple tokens (#34144) (#34302)
+ * Explicitly not update indexes when sync database schemas (#34281) (#34295)
+ * Fix panic when comment is nil (#34257) (#34277)
+ * Fix project board links to related Pull Requests (#34213) (#34222)
+ * Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
+* DOCUMENTATION
+ * Update token creation API swagger documentation (#34288) (#34296)
+* MISC
+ * Fix CI Build (#34315)
+ * Add riscv64 support (#34199) (#34204)
+ * Bump go version in go.mod (#34160)
+ * remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
+
+## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
+
+* Enhancements
+ * Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
+ * Also check default ssh-cert location for host (#34099) (#34100) (#34116)
+* BUGFIXES
+ * Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
+ * Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120)
+ * Fix invalid version in RPM package path (#34112) (#34115)
+ * Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
+ * Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
+ * Try to fix check-attr bug (#34029) (#34033)
+ * Git client will follow 301 but 307 (#34005) (#34010)
+ * Fix block expensive for 1.23 (#34127)
+ * Fix markdown frontmatter rendering (#34102) (#34107)
+ * Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
+ * Do not show 500 error when default branch doesn't exist (#34096) (#34097)
+ * Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
+ * Simplify emoji rendering (#34048) (#34049)
+ * Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
+ * Pull request updates will also trigger code owners review requests (#33744) (#34045)
+ * Fix org repo creation being limited by user limits (#34030) (#34044)
+ * Fix git client accessing renamed repo (#34034) (#34043)
+ * Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036)
+ * Polyfill WeakRef (#34025) (#34028)
+
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
* SECURITY
diff --git a/Dockerfile b/Dockerfile
index fa2ae9913c..c9e6a2d3db 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Build stage
-FROM docker.io/library/golang:1.24-alpine3.21 AS build-env
+FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct}
@@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
-FROM docker.io/library/alpine:3.21
+FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000
diff --git a/Dockerfile.rootless b/Dockerfile.rootless
index b74dfa58e0..558e6cf73b 100644
--- a/Dockerfile.rootless
+++ b/Dockerfile.rootless
@@ -1,5 +1,5 @@
# Build stage
-FROM docker.io/library/golang:1.24-alpine3.21 AS build-env
+FROM docker.io/library/golang:1.24-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct}
@@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
-FROM docker.io/library/alpine:3.21
+FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000
@@ -52,6 +52,7 @@ RUN apk --no-cache add \
git \
curl \
gnupg \
+ openssh-keygen \
&& rm -rf /var/cache/apk/*
RUN addgroup \
diff --git a/Makefile b/Makefile
index d10250bbc7..6a3fa60e49 100644
--- a/Makefile
+++ b/Makefile
@@ -26,17 +26,18 @@ COMMA := ,
XGO_VERSION := go-1.24.x
AIR_PACKAGE ?= github.com/air-verse/air@v1
-EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1
-GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
-GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2
+EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
+GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0
+GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
-MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
-SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
+MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
+SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.32.3
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
-GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.1
+GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.19.1
+GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.19.1
DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest
@@ -80,7 +81,6 @@ ifeq ($(RACE_ENABLED),true)
endif
STORED_VERSION_FILE := VERSION
-HUGO_VERSION ?= 0.111.3
GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
@@ -120,8 +120,7 @@ WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
-BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
-BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
+BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.*
GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go
@@ -149,14 +148,8 @@ SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
GO_SOURCES := $(wildcard *.go)
-GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
+GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go")
GO_SOURCES += $(GENERATED_GO_DEST)
-GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
-
-ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
- GO_SOURCES += $(BINDATA_DEST)
- GENERATED_GO_DEST += $(BINDATA_DEST)
-endif
# Force installation of playwright dependencies by setting this flag
ifdef DEPS_PLAYWRIGHT
@@ -226,7 +219,7 @@ clean-all: clean ## delete backend, frontend and integration files
.PHONY: clean
clean: ## delete backend and integration files
- rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
+ rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \
integrations*.test \
e2e*.test \
tests/integration/gitea-integration-* \
@@ -237,7 +230,7 @@ clean: ## delete backend and integration files
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
.PHONY: fmt
-fmt: ## format the Go code
+fmt: ## format the Go and template code
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
@@ -256,6 +249,19 @@ fmt-check: fmt
exit 1; \
fi
+.PHONY: fix
+fix: ## apply automated fixes to Go code
+ $(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./...
+
+.PHONY: fix-check
+fix-check: fix
+ @diff=$$(git diff --color=always $(GO_SOURCES)); \
+ if [ -n "$$diff" ]; then \
+ echo "Please run 'make fix' and commit the result:"; \
+ printf "%s" "$${diff}"; \
+ exit 1; \
+ fi
+
.PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE):
@mkdir -p $(MAKE_EVIDENCE_DIR)
@@ -268,7 +274,7 @@ endif
.PHONY: generate-swagger
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
-$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) $(SWAGGER_SPEC_INPUT)
+$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
.PHONY: swagger-check
@@ -295,7 +301,7 @@ checks: checks-frontend checks-backend ## run various consistency checks
checks-frontend: lockfile-check svg-check ## check frontend files
.PHONY: checks-backend
-checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
+checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files
.PHONY: lint
lint: lint-frontend lint-backend lint-spell ## lint everything
@@ -373,7 +379,7 @@ lint-go-gitea-vet: ## lint go files with gitea-vet
.PHONY: lint-go-gopls
lint-go-gopls: ## lint go files with gopls
@echo "Running gopls check..."
- @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA)
+ @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES)
.PHONY: lint-editorconfig
lint-editorconfig:
@@ -816,6 +822,7 @@ deps-tools: ## install tool dependencies
$(GO) install $(GOVULNCHECK_PACKAGE) & \
$(GO) install $(ACTIONLINT_PACKAGE) & \
$(GO) install $(GOPLS_PACKAGE) & \
+ $(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
wait
node_modules: package-lock.json
diff --git a/SECURITY.md b/SECURITY.md
index c9dbf859f5..d7c27ea613 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -14,12 +14,12 @@ Please **DO NOT** file a public issue, instead send your report privately to `se
Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body.
-The PGP key is valid until July 9, 2025.
+The PGP key is valid until July 4, 2026.
```
Key ID: 6FCD2D5B
Key Type: RSA
-Expires: 7/9/2025
+Expires: 7/4/2026
Key Size: 4096/4096
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
```
@@ -42,18 +42,18 @@ lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBAhsD
BQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAFiEEPeA9HhRKfwaTWZncqv0jgW/N
-LVsFAmaMse0FCQW4fW8ACgkQqv0jgW/NLVtXLg/+PF4G9Jhlui15BTNlEBJAV2P/
-1QlAV2krk0fP7tykn0FR9RfGIfVV/kwC1f+ouosYPQDDevl9LWdUIM+g94DtNo2o
-7ACpcL3morvt5lVGpIZHL8TbX0qmFRXL/pB/cB+K6IwYvh2mrbp2zH+r4SCRyFYq
-BjgXYFTI1MylJ1ShAjU6Z+m3oJ+2xs5LzHS0X6zkTjzA2Zl4zQzciQ9T+wJcE7Zi
-HXdM1+YMF8KGNP8J9Rpug5oNDJ98lgZirRY7c3A/1xmYBiPnULwuuymdqEZO7l70
-SeAlE1RWYX8kbOBnBb/KY4XwE3Vic1oEzc9DiPWVH1ElX86WNNsFzuyULiwoBoWg
-pqZGhL9x1p5+46RGQSDczsHM7YGVtfYOiDo2PAVrmwsT0BnXnK8Oe3YIkvmUPEJu
-OkLt0Z6A5n8pz8zhQzuApwBsK4ncJ8zTCpvz/pfKKqZC/Vnoh3gKGhDGvOZ+b5IJ
-0kUTe2JsbnwFixDUMDtacQ1op8XOyLoLVmgqLn0+Pws4XPBlMof2bioFir3yHKnP
-gNchsF1agrlSIo5GA8u4ga+IlCSfvFIKrl7+cxacKcJYt/vbOU5KcvVJI5EtHKCG
-xfHjHY2ah1Qww7SxW6IXiRZZzPpsL2mBM2CD7N3qh9bV2s27wxYCdUodsIZbiyHe
-oWPzfBnkmiAN8KlZxHm5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
+LVsFAmhoHmkFCQeT6esACgkQqv0jgW/NLVuFLRAAmjBQSKRAgs2bFIEj7HLAbDp4
+f+XkdH+GsT3jRPOZ9QZgmtM+TfoE4yNgIVfOl+s4RdjM/W4QzqZuPQ55hbEHd056
+cJmm7B+6GsHFcdrPmh65sOCEIyh4+t45dUfeWpFsDPqm9j1UHXAJQIpB8vDEVAPH
+t+3wLCk8GMPJs1o5tIyMmaO23ngvkwn8eG7KgY+rp2PzObrb5g7ppci0ILzILkrp
+HVjZsEfUWRgSVF7LuU5ppqDKrlcqwUpQq6n3kGMZcLrCp6ACKP04TBmTfUxNwdL7
+I0N7apI2Pbct9T1Gv/lYAUFWyU2c3gh/EBLbO6BukaLOFRQHrtNfdJV/YnMPlcXr
+LUJjK9K4eAH9DsrZqrisz/LthsC2BaNIN3KRMTk5YTYgmIh8GXzSgihORmtDFELC
+RroID3pTuS0zjXh+wpY9GuPTh7UW23p42Daxca4fAT4k5EclvDRUrL21xMopPMiL
+HuNdELz4FVchRTy05PjzKVyjVInDNojE2KUxnjxZDzYJ6aT/g+coD5yfntYm8BEj
++ZzL0ndZES54hzKLpv7zwBQwFzam68clZYmDPILOPTflQDfpGEWmJK4undFU5obz
+ZsQRz0R3ulspChATbZxO0d5LX2obLpKO9X3b5VoO1KF+R8Vjw1Y0KxrNZ6rIcfqH
+Z50QVQKSe9dm08K0ON+5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
@@ -65,19 +65,19 @@ s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYCGwwWIQQ94D0eFEp/BpNZmdyq/SOBb80t
-WwUCZoyyjQUJBbh+DwAKCRCq/SOBb80tW18XD/9MXztmf01MT+1kZdBouZ/7Rp/7
-9kuqo//B1G+RXau4oFtPqb67kNe2WaIc3u5B73PUHsMf3i6z4ib2KbMhZZerLn0O
-dRglcuPeNWmsASY3dH/XVG0cT0zvvWegagd12TJEl3Vs+7XNrOw4cwDj9L1+GH9m
-kSt4uaANWn/6a3RvMRhiVEYuNwhAzcKaactPmYqrLJgoVLbRSDkgyHaMQ2jKgLxk
-ifS/fvluGV0ub2Po6DJiqfRpd1tDvPhe9y1+r1WFDZsOcvTcZUfSt/7dXMGfqGu0
-2daVFlfeSXSALrDE5uc0UxodHCpP3sqRYDZevGLBRaaTkIjYXG/+N898+7K5WJF4
-xXOLWxM2cwGkG7eC9pugcDnBp9XlF7O+GBiZ05JUe5flXDQFZ+h3exjopu6KHF1B
-RnzNy8LC0UKb+AuvRIOLV92a9Q9wGWU/jaVDu6nZ0umAeuSzxiHoDsonm0Fl9QAz
-2/xCokebuoeLrEK7R2af3X86mqq3sVO4ax+HPYChzOaVQBiHUW/TAldWcldYYphR
-/e2WsbmQfvCRtz/bZfo+aUVnrHNjzVMtF2SszdVmA/04Y8pS28MqtuRqhm5DPOOd
-g1YeUywK5jRZ1twyo1kzJEFPLaoeaXaycsR1PMVBW0Urik5mrR/pOWq7PPoZoKb2
-lXYLE8bwkuQTmsyL1g==
-=9i7d
+WwUCaGgeJAUJB5PppgAKCRCq/SOBb80tW/NWEACB6Jrf0gWlk7e+hNCdnbM0ZVWU
+f2sHNFfXxxsdhpcDgKbNHtkZb8nZgv8AX+5fTtUwMVa3vKcdw30xFiIM5N7cCIPV
+vg/5z5BtfEaitnabEUG2iiVDIy8IHXIcK10rX+7BosA3QDl2PsiBHwyi5G13lRk8
+zGTSNDuOalug33h5/lr2dPigamkq74Aoy29q8Rjad6GfWHipL2bFimgtY+Zdi0BH
+NLk4EJXxj1SgVx5dtkQzWJReBA5M+FQ4QYQZBO+f4TDoOLmjui152uhkoLBQbGAa
+WWJFTVxm0bG5MXloEL3gA8DfU7XDwuW/sHJC5pBko8RpQViooOhckMepZV3Y83DK
+bwLYa3JmPgj2rEv4993dvrJbQhpGd082HOxOsllCs8pgNq1SnXpWYfcGTgGKC3ts
+U8YZUUJUQ7mi2L8Tv3ix20c9EiGmA30JAmA8eZTC3cWup91ZkkVBFRml2czTXajd
+RWZ6GbHV5503ueDQcB8yBVgF3CSixs67+dGSbD3p86OqGrjAcJzM5TFbNKcnGLdE
+kGbZpNwAISy750lXzXKmyrh5RTCeTOQerbwCMBvHZO+HAevA/LXDTw2OAiSIQlP5
+sYA4sFYLQ30OAkgJcmdp/pSgVj/erNtSN07ClrOpDb/uFpQymO6K2h0Pst3feNVK
+9M2VbqL9C51z/wyHLg==
+=SfZA
-----END PGP PUBLIC KEY BLOCK-----
```
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index 3827a092f1..d961444239 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -1080,9 +1080,14 @@
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License."
},
{
- "name": "github.com/urfave/cli/v2",
- "path": "github.com/urfave/cli/v2/LICENSE",
- "licenseText": "MIT License\n\nCopyright (c) 2022 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
+ "name": "github.com/urfave/cli-docs/v3",
+ "path": "github.com/urfave/cli-docs/v3/LICENSE",
+ "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
+ },
+ {
+ "name": "github.com/urfave/cli/v3",
+ "path": "github.com/urfave/cli/v3/LICENSE",
+ "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"name": "github.com/valyala/fastjson",
@@ -1110,11 +1115,6 @@
"licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n"
},
{
- "name": "github.com/xrash/smetrics",
- "path": "github.com/xrash/smetrics/LICENSE",
- "licenseText": "Copyright (C) 2016 Felipe da Cunha Gonçalves\nAll Rights Reserved.\n\nMIT LICENSE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
- },
- {
"name": "github.com/yohcop/openid-go",
"path": "github.com/yohcop/openid-go/LICENSE",
"licenseText": "Copyright 2015 Yohann Coppel\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
diff --git a/build.go b/build.go
index 234579b514..e81ba54690 100644
--- a/build.go
+++ b/build.go
@@ -5,19 +5,10 @@
package main
-// Libraries that are included to vendor utilities used during build.
+// Libraries that are included to vendor utilities used during Makefile build.
// These libraries will not be included in a normal compilation.
import (
- // for embed
- _ "github.com/shurcooL/vfsgen"
-
- // for cover merge
- _ "golang.org/x/tools/cover"
-
// for vet
_ "code.gitea.io/gitea-vet"
-
- // for swagger
- _ "github.com/go-swagger/go-swagger/cmd/swagger"
)
diff --git a/build/generate-bindata.go b/build/generate-bindata.go
index 2fcb7c2f2a..2553770762 100644
--- a/build/generate-bindata.go
+++ b/build/generate-bindata.go
@@ -6,87 +6,22 @@
package main
import (
- "bytes"
- "crypto/sha1"
"fmt"
- "log"
- "net/http"
"os"
- "path/filepath"
- "strconv"
- "github.com/shurcooL/vfsgen"
+ "code.gitea.io/gitea/modules/assetfs"
)
-func needsUpdate(dir, filename string) (bool, []byte) {
- needRegen := false
- _, err := os.Stat(filename)
- if err != nil {
- needRegen = true
- }
-
- oldHash, err := os.ReadFile(filename + ".hash")
- if err != nil {
- oldHash = []byte{}
- }
-
- hasher := sha1.New()
-
- err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
- if err != nil {
- return err
- }
- info, err := d.Info()
- if err != nil {
- return err
- }
- _, _ = hasher.Write([]byte(d.Name()))
- _, _ = hasher.Write([]byte(info.ModTime().String()))
- _, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
- return nil
- })
- if err != nil {
- return true, oldHash
- }
-
- newHash := hasher.Sum([]byte{})
-
- if bytes.Compare(oldHash, newHash) != 0 {
- return true, newHash
- }
-
- return needRegen, newHash
-}
-
func main() {
- if len(os.Args) < 4 {
- log.Fatal("Insufficient number of arguments. Need: directory packageName filename")
- }
-
- dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3]
- var useGlobalModTime bool
- if len(os.Args) == 5 {
- useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
- }
-
- update, newHash := needsUpdate(dir, filename)
-
- if !update {
- fmt.Printf("bindata for %s already up-to-date\n", packageName)
- return
+ if len(os.Args) != 3 {
+ fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}")
+ os.Exit(1)
}
- fmt.Printf("generating bindata for %s\n", packageName)
- var fsTemplates http.FileSystem = http.Dir(dir)
- err := vfsgen.Generate(fsTemplates, vfsgen.Options{
- PackageName: packageName,
- BuildTags: "bindata",
- VariableName: "Assets",
- Filename: filename,
- UseGlobalModTime: useGlobalModTime,
- })
- if err != nil {
- log.Fatalf("%v\n", err)
+ dir, filename := os.Args[1], os.Args[2]
+ fmt.Printf("generating bindata for %s to %s\n", dir, filename)
+ if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil {
+ fmt.Printf("failed: %s\n", err.Error())
+ os.Exit(1)
}
- _ = os.WriteFile(filename+".hash", newHash, 0o666)
}
diff --git a/cmd/actions.go b/cmd/actions.go
index f582c16c81..2c51c6a1bc 100644
--- a/cmd/actions.go
+++ b/cmd/actions.go
@@ -4,12 +4,13 @@
package cmd
import (
+ "context"
"fmt"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var (
@@ -17,7 +18,7 @@ var (
CmdActions = &cli.Command{
Name: "actions",
Usage: "Manage Gitea Actions",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
subcmdActionsGenRunnerToken,
},
}
@@ -38,10 +39,7 @@ var (
}
)
-func runGenerateActionsRunnerToken(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
setting.MustInstalled()
scope := c.String("scope")
diff --git a/cmd/admin.go b/cmd/admin.go
index 6c9480e76e..559544edd3 100644
--- a/cmd/admin.go
+++ b/cmd/admin.go
@@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var (
@@ -23,7 +23,7 @@ var (
CmdAdmin = &cli.Command{
Name: "admin",
Usage: "Perform common administrative operations",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
subcmdUser,
subcmdRepoSyncReleases,
subcmdRegenerate,
@@ -41,7 +41,7 @@ var (
subcmdRegenerate = &cli.Command{
Name: "regenerate",
Usage: "Regenerate specific files",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
microcmdRegenHooks,
microcmdRegenKeys,
},
@@ -50,15 +50,15 @@ var (
subcmdAuth = &cli.Command{
Name: "auth",
Usage: "Modify external auth providers",
- Subcommands: []*cli.Command{
- microcmdAuthAddOauth,
- microcmdAuthUpdateOauth,
- microcmdAuthAddLdapBindDn,
- microcmdAuthUpdateLdapBindDn,
- microcmdAuthAddLdapSimpleAuth,
- microcmdAuthUpdateLdapSimpleAuth,
- microcmdAuthAddSMTP,
- microcmdAuthUpdateSMTP,
+ Commands: []*cli.Command{
+ microcmdAuthAddOauth(),
+ microcmdAuthUpdateOauth(),
+ microcmdAuthAddLdapBindDn(),
+ microcmdAuthUpdateLdapBindDn(),
+ microcmdAuthAddLdapSimpleAuth(),
+ microcmdAuthUpdateLdapSimpleAuth(),
+ microcmdAuthAddSMTP(),
+ microcmdAuthUpdateSMTP(),
microcmdAuthList,
microcmdAuthDelete,
},
@@ -70,9 +70,9 @@ var (
Action: runSendMail,
Flags: []cli.Flag{
&cli.StringFlag{
- Name: "title",
- Usage: `a title of a message`,
- Value: "",
+ Name: "title",
+ Usage: "a title of a message",
+ Required: true,
},
&cli.StringFlag{
Name: "content",
@@ -86,17 +86,16 @@ var (
},
},
}
+)
- idFlag = &cli.Int64Flag{
+func idFlag() *cli.Int64Flag {
+ return &cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}
-)
-
-func runRepoSyncReleases(_ *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
+}
+func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
@@ -107,7 +106,7 @@ func runRepoSyncReleases(_ *cli.Context) error {
log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ {
- repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err := repo_model.SearchRepositoryByName(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: repo_model.RepositoryListDefaultPageSize,
Page: page,
diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go
index 4777a92908..1a09366722 100644
--- a/cmd/admin_auth.go
+++ b/cmd/admin_auth.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"fmt"
"os"
@@ -13,14 +14,14 @@ import (
"code.gitea.io/gitea/models/db"
auth_service "code.gitea.io/gitea/services/auth"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var (
microcmdAuthDelete = &cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
- Flags: []cli.Flag{idFlag},
+ Flags: []cli.Flag{idFlag()},
Action: runDeleteAuth,
}
microcmdAuthList = &cli.Command{
@@ -56,10 +57,7 @@ var (
}
)
-func runListAuth(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runListAuth(ctx context.Context, c *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
@@ -90,14 +88,11 @@ func runListAuth(c *cli.Context) error {
return nil
}
-func runDeleteAuth(c *cli.Context) error {
+func runDeleteAuth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
- ctx, cancel := installSignals()
- defer cancel()
-
if err := initDB(ctx); err != nil {
return err
}
diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go
index d2eeb7c0d6..069ad6600c 100644
--- a/cmd/admin_auth_ldap.go
+++ b/cmd/admin_auth_ldap.go
@@ -12,7 +12,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/ldap"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
type (
@@ -24,8 +24,8 @@ type (
}
)
-var (
- commonLdapCLIFlags = []cli.Flag{
+func commonLdapCLIFlags() []cli.Flag {
+ return []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "Authentication name.",
@@ -103,8 +103,10 @@ var (
Usage: "The attribute of the user’s LDAP record containing the user’s avatar.",
},
}
+}
- ldapBindDnCLIFlags = append(commonLdapCLIFlags,
+func ldapBindDnCLIFlags() []cli.Flag {
+ return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.",
@@ -157,49 +159,59 @@ var (
Name: "group-team-map-removal",
Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
})
+}
- ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
+func ldapSimpleAuthCLIFlags() []cli.Flag {
+ return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "user-dn",
Usage: "The user's DN.",
})
+}
- microcmdAuthAddLdapBindDn = &cli.Command{
+func microcmdAuthAddLdapBindDn() *cli.Command {
+ return &cli.Command{
Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source",
- Action: func(c *cli.Context) error {
- return newAuthService().addLdapBindDn(c)
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().addLdapBindDn(ctx, cmd)
},
- Flags: ldapBindDnCLIFlags,
+ Flags: ldapBindDnCLIFlags(),
}
+}
- microcmdAuthUpdateLdapBindDn = &cli.Command{
+func microcmdAuthUpdateLdapBindDn() *cli.Command {
+ return &cli.Command{
Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source",
- Action: func(c *cli.Context) error {
- return newAuthService().updateLdapBindDn(c)
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().updateLdapBindDn(ctx, cmd)
},
- Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
+ Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),
}
+}
- microcmdAuthAddLdapSimpleAuth = &cli.Command{
+func microcmdAuthAddLdapSimpleAuth() *cli.Command {
+ return &cli.Command{
Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source",
- Action: func(c *cli.Context) error {
- return newAuthService().addLdapSimpleAuth(c)
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().addLdapSimpleAuth(ctx, cmd)
},
- Flags: ldapSimpleAuthCLIFlags,
+ Flags: ldapSimpleAuthCLIFlags(),
}
+}
- microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
+func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
+ return &cli.Command{
Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source",
- Action: func(c *cli.Context) error {
- return newAuthService().updateLdapSimpleAuth(c)
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().updateLdapSimpleAuth(ctx, cmd)
},
- Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
+ Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
}
-)
+}
// newAuthService creates a service with default functions.
func newAuthService() *authService {
@@ -212,7 +224,7 @@ func newAuthService() *authService {
}
// parseAuthSourceLdap assigns values on authSource according to command line flags.
-func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) {
+func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) {
if c.IsSet("name") {
authSource.Name = c.String("name")
}
@@ -232,7 +244,7 @@ func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) {
}
// parseLdapConfig assigns values on config according to command line flags.
-func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
+func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("name") {
config.Name = c.String("name")
}
@@ -245,7 +257,7 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
if c.IsSet("security-protocol") {
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
if !ok {
- return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
+ return fmt.Errorf("unknown security protocol name: %s", c.String("security-protocol"))
}
config.SecurityProtocol = p
}
@@ -337,32 +349,27 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
// getAuthSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type.
-func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) {
+func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) {
if err := argsSet(c, "id"); err != nil {
return nil, err
}
-
authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return nil, err
}
if authSource.Type != authType {
- return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
+ return nil, fmt.Errorf("invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
}
return authSource, nil
}
// addLdapBindDn adds a new LDAP via Bind DN authentication source.
-func (a *authService) addLdapBindDn(c *cli.Context) error {
+func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err
}
-
- ctx, cancel := installSignals()
- defer cancel()
-
if err := a.initDB(ctx); err != nil {
return err
}
@@ -384,10 +391,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
}
// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
-func (a *authService) updateLdapBindDn(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
@@ -406,14 +410,11 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
}
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
-func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
+func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err
}
- ctx, cancel := installSignals()
- defer cancel()
-
if err := a.initDB(ctx); err != nil {
return err
}
@@ -435,10 +436,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
}
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
-func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go
index ea9a83ef76..2da7ebc573 100644
--- a/cmd/admin_auth_ldap_test.go
+++ b/cmd/admin_auth_ldap_test.go
@@ -8,17 +8,16 @@ import (
"testing"
"code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/services/auth/source/ldap"
"github.com/stretchr/testify/assert"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
func TestAddLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error
- osExiter := cli.OsExiter
- defer func() { cli.OsExiter = osExiter }()
- cli.OsExiter = func(code int) {}
+ defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
cases := []struct {
@@ -135,7 +134,7 @@ func TestAddLdapBindDn(t *testing.T) {
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
"--email-attribute", "mail",
},
- errMsg: "Unknown security protocol name: zzzzz",
+ errMsg: "unknown security protocol name: zzzzz",
},
// case 3
{
@@ -239,12 +238,13 @@ func TestAddLdapBindDn(t *testing.T) {
}
// Create a copy of command to test
- app := cli.NewApp()
- app.Flags = microcmdAuthAddLdapBindDn.Flags
- app.Action = service.addLdapBindDn
+ app := cli.Command{
+ Flags: microcmdAuthAddLdapBindDn().Flags,
+ Action: service.addLdapBindDn,
+ }
// Run it
- err := app.Run(c.args)
+ err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@@ -256,9 +256,7 @@ func TestAddLdapBindDn(t *testing.T) {
func TestAddLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error
- osExiter := cli.OsExiter
- defer func() { cli.OsExiter = osExiter }()
- cli.OsExiter = func(code int) {}
+ defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
cases := []struct {
@@ -348,12 +346,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
"--name", "ldap (simple auth) source",
"--security-protocol", "zzzzz",
"--host", "ldap-server",
- "--port", "123",
+ "--port", "1234",
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
"--email-attribute", "mail",
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
},
- errMsg: "Unknown security protocol name: zzzzz",
+ errMsg: "unknown security protocol name: zzzzz",
},
// case 3
{
@@ -470,12 +468,13 @@ func TestAddLdapSimpleAuth(t *testing.T) {
}
// Create a copy of command to test
- app := cli.NewApp()
- app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
- app.Action = service.addLdapSimpleAuth
+ app := &cli.Command{
+ Flags: microcmdAuthAddLdapSimpleAuth().Flags,
+ Action: service.addLdapSimpleAuth,
+ }
// Run it
- err := app.Run(c.args)
+ err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@@ -487,9 +486,7 @@ func TestAddLdapSimpleAuth(t *testing.T) {
func TestUpdateLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error
- osExiter := cli.OsExiter
- defer func() { cli.OsExiter = osExiter }()
- cli.OsExiter = func(code int) {}
+ defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
cases := []struct {
@@ -864,7 +861,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
"--id", "1",
"--security-protocol", "xxxxx",
},
- errMsg: "Unknown security protocol name: xxxxx",
+ errMsg: "unknown security protocol name: xxxxx",
},
// case 22
{
@@ -883,7 +880,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
Type: auth.OAuth2,
Cfg: &ldap.Source{},
},
- errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
+ errMsg: "invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
},
// case 24
{
@@ -947,12 +944,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
}
// Create a copy of command to test
- app := cli.NewApp()
- app.Flags = microcmdAuthUpdateLdapBindDn.Flags
- app.Action = service.updateLdapBindDn
-
+ app := cli.Command{
+ Flags: microcmdAuthUpdateLdapBindDn().Flags,
+ Action: service.updateLdapBindDn,
+ }
// Run it
- err := app.Run(c.args)
+ err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@@ -964,9 +961,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
func TestUpdateLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error
- osExiter := cli.OsExiter
- defer func() { cli.OsExiter = osExiter }()
- cli.OsExiter = func(code int) {}
+ defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
cases := []struct {
@@ -1257,7 +1252,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
"--id", "1",
"--security-protocol", "xxxxx",
},
- errMsg: "Unknown security protocol name: xxxxx",
+ errMsg: "unknown security protocol name: xxxxx",
},
// case 18
{
@@ -1276,7 +1271,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
Type: auth.PAM,
Cfg: &ldap.Source{},
},
- errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM",
+ errMsg: "invalid authentication type. expected: LDAP (simple auth), actual: PAM",
},
// case 20
{
@@ -1337,12 +1332,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
}
// Create a copy of command to test
- app := cli.NewApp()
- app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
- app.Action = service.updateLdapSimpleAuth
-
+ app := cli.Command{
+ Flags: microcmdAuthUpdateLdapSimpleAuth().Flags,
+ Action: service.updateLdapSimpleAuth,
+ }
// Run it
- err := app.Run(c.args)
+ err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go
index be5345d992..d1aa753500 100644
--- a/cmd/admin_auth_oauth.go
+++ b/cmd/admin_auth_oauth.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"fmt"
"net/url"
@@ -12,11 +13,11 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-var (
- oauthCLIFlags = []cli.Flag{
+func oauthCLIFlags() []cli.Flag {
+ return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
@@ -121,23 +122,34 @@ var (
Usage: "Activate automatic team membership removal depending on groups",
},
}
+}
- microcmdAuthAddOauth = &cli.Command{
- Name: "add-oauth",
- Usage: "Add new Oauth authentication source",
- Action: runAddOauth,
- Flags: oauthCLIFlags,
+func microcmdAuthAddOauth() *cli.Command {
+ return &cli.Command{
+ Name: "add-oauth",
+ Usage: "Add new Oauth authentication source",
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().runAddOauth(ctx, cmd)
+ },
+ Flags: oauthCLIFlags(),
}
+}
- microcmdAuthUpdateOauth = &cli.Command{
- Name: "update-oauth",
- Usage: "Update existing Oauth authentication source",
- Action: runUpdateOauth,
- Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
+func microcmdAuthUpdateOauth() *cli.Command {
+ return &cli.Command{
+ Name: "update-oauth",
+ Usage: "Update existing Oauth authentication source",
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().runUpdateOauth(ctx, cmd)
+ },
+ Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
+ Name: "id",
+ Usage: "ID of authentication source",
+ }}, oauthCLIFlags()[1:]...)...),
}
-)
+}
-func parseOAuth2Config(c *cli.Context) *oauth2.Source {
+func parseOAuth2Config(c *cli.Command) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
@@ -168,11 +180,8 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
}
}
-func runAddOauth(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(ctx); err != nil {
+func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error {
+ if err := a.initDB(ctx); err != nil {
return err
}
@@ -184,7 +193,7 @@ func runAddOauth(c *cli.Context) error {
}
}
- return auth_model.CreateSource(ctx, &auth_model.Source{
+ return a.createAuthSource(ctx, &auth_model.Source{
Type: auth_model.OAuth2,
Name: c.String("name"),
IsActive: true,
@@ -193,19 +202,16 @@ func runAddOauth(c *cli.Context) error {
})
}
-func runUpdateOauth(c *cli.Context) error {
+func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
- ctx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(ctx); err != nil {
+ if err := a.initDB(ctx); err != nil {
return err
}
- source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
+ source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
@@ -296,5 +302,5 @@ func runUpdateOauth(c *cli.Context) error {
oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
- return auth_model.UpdateSource(ctx, source)
+ return a.updateAuthSource(ctx, source)
}
diff --git a/cmd/admin_auth_oauth_test.go b/cmd/admin_auth_oauth_test.go
new file mode 100644
index 0000000000..df1bd9c1a6
--- /dev/null
+++ b/cmd/admin_auth_oauth_test.go
@@ -0,0 +1,333 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "context"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/services/auth/source/oauth2"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/urfave/cli/v3"
+)
+
+func TestAddOauth(t *testing.T) {
+ testCases := []struct {
+ name string
+ args []string
+ source *auth_model.Source
+ errMsg string
+ }{
+ {
+ name: "valid config",
+ args: []string{
+ "--name", "test",
+ "--provider", "github",
+ "--key", "some_key",
+ "--secret", "some_secret",
+ },
+ source: &auth_model.Source{
+ Type: auth_model.OAuth2,
+ Name: "test",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ Scopes: []string{},
+ Provider: "github",
+ ClientID: "some_key",
+ ClientSecret: "some_secret",
+ },
+ TwoFactorPolicy: "",
+ },
+ },
+ {
+ name: "valid config with openid connect",
+ args: []string{
+ "--name", "test",
+ "--provider", "openidConnect",
+ "--key", "some_key",
+ "--secret", "some_secret",
+ "--auto-discover-url", "https://example.com",
+ },
+ source: &auth_model.Source{
+ Type: auth_model.OAuth2,
+ Name: "test",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ Scopes: []string{},
+ Provider: "openidConnect",
+ ClientID: "some_key",
+ ClientSecret: "some_secret",
+ OpenIDConnectAutoDiscoveryURL: "https://example.com",
+ },
+ TwoFactorPolicy: "",
+ },
+ },
+ {
+ name: "valid config with options",
+ args: []string{
+ "--name", "test",
+ "--provider", "gitlab",
+ "--key", "some_key",
+ "--secret", "some_secret",
+ "--use-custom-urls", "true",
+ "--custom-token-url", "https://example.com/token",
+ "--custom-auth-url", "https://example.com/auth",
+ "--custom-profile-url", "https://example.com/profile",
+ "--custom-email-url", "https://example.com/email",
+ "--custom-tenant-id", "some_tenant",
+ "--icon-url", "https://example.com/icon",
+ "--scopes", "scope1,scope2",
+ "--skip-local-2fa", "true",
+ "--required-claim-name", "claim_name",
+ "--required-claim-value", "claim_value",
+ "--group-claim-name", "group_name",
+ "--admin-group", "admin",
+ "--restricted-group", "restricted",
+ "--group-team-map", `{"group1": [1,2]}`,
+ "--group-team-map-removal=true",
+ },
+ source: &auth_model.Source{
+ Type: auth_model.OAuth2,
+ Name: "test",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ Provider: "gitlab",
+ ClientID: "some_key",
+ ClientSecret: "some_secret",
+ CustomURLMapping: &oauth2.CustomURLMapping{
+ TokenURL: "https://example.com/token",
+ AuthURL: "https://example.com/auth",
+ ProfileURL: "https://example.com/profile",
+ EmailURL: "https://example.com/email",
+ Tenant: "some_tenant",
+ },
+ IconURL: "https://example.com/icon",
+ Scopes: []string{"scope1", "scope2"},
+ RequiredClaimName: "claim_name",
+ RequiredClaimValue: "claim_value",
+ GroupClaimName: "group_name",
+ AdminGroup: "admin",
+ RestrictedGroup: "restricted",
+ GroupTeamMap: `{"group1": [1,2]}`,
+ GroupTeamMapRemoval: true,
+ },
+ TwoFactorPolicy: "skip",
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ var createdSource *auth_model.Source
+ a := &authService{
+ initDB: func(ctx context.Context) error {
+ return nil
+ },
+ createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
+ createdSource = source
+ return nil
+ },
+ }
+
+ app := &cli.Command{
+ Flags: microcmdAuthAddOauth().Flags,
+ Action: a.runAddOauth,
+ }
+
+ args := []string{"oauth-test"}
+ args = append(args, tc.args...)
+
+ err := app.Run(t.Context(), args)
+
+ if tc.errMsg != "" {
+ assert.EqualError(t, err, tc.errMsg)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tc.source, createdSource)
+ }
+ })
+ }
+}
+
+func TestUpdateOauth(t *testing.T) {
+ testCases := []struct {
+ name string
+ args []string
+ id int64
+ existingAuthSource *auth_model.Source
+ authSource *auth_model.Source
+ errMsg string
+ }{
+ {
+ name: "missing id",
+ args: []string{
+ "--name", "test",
+ },
+ errMsg: "--id flag is missing",
+ },
+ {
+ name: "valid config",
+ id: 1,
+ existingAuthSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.OAuth2,
+ Name: "old name",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ Provider: "github",
+ ClientID: "old_key",
+ ClientSecret: "old_secret",
+ },
+ TwoFactorPolicy: "",
+ },
+ args: []string{
+ "--id", "1",
+ "--name", "test",
+ "--provider", "gitlab",
+ "--key", "new_key",
+ "--secret", "new_secret",
+ },
+ authSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.OAuth2,
+ Name: "test",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ Provider: "gitlab",
+ ClientID: "new_key",
+ ClientSecret: "new_secret",
+ CustomURLMapping: &oauth2.CustomURLMapping{},
+ },
+ TwoFactorPolicy: "",
+ },
+ },
+ {
+ name: "valid config with options",
+ id: 1,
+ existingAuthSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.OAuth2,
+ Name: "old name",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ Provider: "gitlab",
+ ClientID: "old_key",
+ ClientSecret: "old_secret",
+ CustomURLMapping: &oauth2.CustomURLMapping{
+ TokenURL: "https://old.example.com/token",
+ AuthURL: "https://old.example.com/auth",
+ ProfileURL: "https://old.example.com/profile",
+ EmailURL: "https://old.example.com/email",
+ Tenant: "old_tenant",
+ },
+ IconURL: "https://old.example.com/icon",
+ Scopes: []string{"old_scope1", "old_scope2"},
+ RequiredClaimName: "old_claim_name",
+ RequiredClaimValue: "old_claim_value",
+ GroupClaimName: "old_group_name",
+ AdminGroup: "old_admin",
+ RestrictedGroup: "old_restricted",
+ GroupTeamMap: `{"old_group1": [1,2]}`,
+ GroupTeamMapRemoval: true,
+ },
+ TwoFactorPolicy: "",
+ },
+ args: []string{
+ "--id", "1",
+ "--name", "test",
+ "--provider", "github",
+ "--key", "new_key",
+ "--secret", "new_secret",
+ "--use-custom-urls", "true",
+ "--custom-token-url", "https://example.com/token",
+ "--custom-auth-url", "https://example.com/auth",
+ "--custom-profile-url", "https://example.com/profile",
+ "--custom-email-url", "https://example.com/email",
+ "--custom-tenant-id", "new_tenant",
+ "--icon-url", "https://example.com/icon",
+ "--scopes", "scope1,scope2",
+ "--skip-local-2fa=true",
+ "--required-claim-name", "claim_name",
+ "--required-claim-value", "claim_value",
+ "--group-claim-name", "group_name",
+ "--admin-group", "admin",
+ "--restricted-group", "restricted",
+ "--group-team-map", `{"group1": [1,2]}`,
+ "--group-team-map-removal=false",
+ },
+ authSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.OAuth2,
+ Name: "test",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ Provider: "github",
+ ClientID: "new_key",
+ ClientSecret: "new_secret",
+ CustomURLMapping: &oauth2.CustomURLMapping{
+ TokenURL: "https://example.com/token",
+ AuthURL: "https://example.com/auth",
+ ProfileURL: "https://example.com/profile",
+ EmailURL: "https://example.com/email",
+ Tenant: "new_tenant",
+ },
+ IconURL: "https://example.com/icon",
+ Scopes: []string{"scope1", "scope2"},
+ RequiredClaimName: "claim_name",
+ RequiredClaimValue: "claim_value",
+ GroupClaimName: "group_name",
+ AdminGroup: "admin",
+ RestrictedGroup: "restricted",
+ GroupTeamMap: `{"group1": [1,2]}`,
+ GroupTeamMapRemoval: false,
+ },
+ TwoFactorPolicy: "skip",
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ a := &authService{
+ initDB: func(ctx context.Context) error {
+ return nil
+ },
+ getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
+ return &auth_model.Source{
+ ID: 1,
+ Type: auth_model.OAuth2,
+ Name: "test",
+ IsActive: true,
+ Cfg: &oauth2.Source{
+ CustomURLMapping: &oauth2.CustomURLMapping{},
+ },
+ TwoFactorPolicy: "skip",
+ }, nil
+ },
+ updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
+ assert.Equal(t, tc.authSource, source)
+ return nil
+ },
+ }
+
+ app := &cli.Command{
+ Flags: microcmdAuthUpdateOauth().Flags,
+ Action: a.runUpdateOauth,
+ }
+
+ args := []string{"oauth-test"}
+ args = append(args, tc.args...)
+
+ err := app.Run(t.Context(), args)
+
+ if tc.errMsg != "" {
+ assert.EqualError(t, err, tc.errMsg)
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_smtp.go
index babcf78cea..93e0587fc3 100644
--- a/cmd/admin_auth_stmp.go
+++ b/cmd/admin_auth_smtp.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"strings"
@@ -11,11 +12,11 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/smtp"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-var (
- smtpCLIFlags = []cli.Flag{
+func smtpCLIFlags() []cli.Flag {
+ return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
@@ -38,12 +39,10 @@ var (
&cli.BoolFlag{
Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
- Value: true,
},
&cli.BoolFlag{
Name: "skip-verify",
Usage: "Skip TLS verify.",
- Value: true,
},
&cli.StringFlag{
Name: "helo-hostname",
@@ -53,7 +52,6 @@ var (
&cli.BoolFlag{
Name: "disable-helo",
Usage: "Disable SMTP helo.",
- Value: true,
},
&cli.StringFlag{
Name: "allowed-domains",
@@ -63,7 +61,6 @@ var (
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.",
- Value: true,
},
&cli.BoolFlag{
Name: "active",
@@ -71,23 +68,34 @@ var (
Value: true,
},
}
+}
- microcmdAuthAddSMTP = &cli.Command{
- Name: "add-smtp",
- Usage: "Add new SMTP authentication source",
- Action: runAddSMTP,
- Flags: smtpCLIFlags,
+func microcmdAuthUpdateSMTP() *cli.Command {
+ return &cli.Command{
+ Name: "update-smtp",
+ Usage: "Update existing SMTP authentication source",
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().runUpdateSMTP(ctx, cmd)
+ },
+ Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
+ Name: "id",
+ Usage: "ID of authentication source",
+ }}, smtpCLIFlags()[1:]...)...),
}
+}
- microcmdAuthUpdateSMTP = &cli.Command{
- Name: "update-smtp",
- Usage: "Update existing SMTP authentication source",
- Action: runUpdateSMTP,
- Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
+func microcmdAuthAddSMTP() *cli.Command {
+ return &cli.Command{
+ Name: "add-smtp",
+ Usage: "Add new SMTP authentication source",
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ return newAuthService().runAddSMTP(ctx, cmd)
+ },
+ Flags: smtpCLIFlags(),
}
-)
+}
-func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
+func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
@@ -120,11 +128,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
return nil
}
-func runAddSMTP(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(ctx); err != nil {
+func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
+ if err := a.initDB(ctx); err != nil {
return err
}
@@ -152,7 +157,7 @@ func runAddSMTP(c *cli.Context) error {
smtpConfig.Auth = "PLAIN"
}
- return auth_model.CreateSource(ctx, &auth_model.Source{
+ return a.createAuthSource(ctx, &auth_model.Source{
Type: auth_model.SMTP,
Name: c.String("name"),
IsActive: active,
@@ -161,19 +166,16 @@ func runAddSMTP(c *cli.Context) error {
})
}
-func runUpdateSMTP(c *cli.Context) error {
+func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
- ctx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(ctx); err != nil {
+ if err := a.initDB(ctx); err != nil {
return err
}
- source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
+ source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
@@ -194,5 +196,5 @@ func runUpdateSMTP(c *cli.Context) error {
source.Cfg = smtpConfig
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
- return auth_model.UpdateSource(ctx, source)
+ return a.updateAuthSource(ctx, source)
}
diff --git a/cmd/admin_auth_smtp_test.go b/cmd/admin_auth_smtp_test.go
new file mode 100644
index 0000000000..e54e01830c
--- /dev/null
+++ b/cmd/admin_auth_smtp_test.go
@@ -0,0 +1,271 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "context"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/services/auth/source/smtp"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/urfave/cli/v3"
+)
+
+func TestAddSMTP(t *testing.T) {
+ testCases := []struct {
+ name string
+ args []string
+ source *auth_model.Source
+ errMsg string
+ }{
+ {
+ name: "missing name",
+ args: []string{
+ "--host", "localhost",
+ "--port", "25",
+ },
+ errMsg: "name must be set",
+ },
+ {
+ name: "missing host",
+ args: []string{
+ "--name", "test",
+ "--port", "25",
+ },
+ errMsg: "host must be set",
+ },
+ {
+ name: "missing port",
+ args: []string{
+ "--name", "test",
+ "--host", "localhost",
+ },
+ errMsg: "port must be set",
+ },
+ {
+ name: "valid config",
+ args: []string{
+ "--name", "test",
+ "--host", "localhost",
+ "--port", "25",
+ },
+ source: &auth_model.Source{
+ Type: auth_model.SMTP,
+ Name: "test",
+ IsActive: true,
+ Cfg: &smtp.Source{
+ Auth: "PLAIN",
+ Host: "localhost",
+ Port: 25,
+ },
+ TwoFactorPolicy: "",
+ },
+ },
+ {
+ name: "valid config with options",
+ args: []string{
+ "--name", "test",
+ "--host", "localhost",
+ "--port", "25",
+ "--auth-type", "LOGIN",
+ "--force-smtps",
+ "--skip-verify",
+ "--helo-hostname", "example.com",
+ "--disable-helo=true",
+ "--allowed-domains", "example.com,example.org",
+ "--skip-local-2fa",
+ "--active=false",
+ },
+ source: &auth_model.Source{
+ Type: auth_model.SMTP,
+ Name: "test",
+ IsActive: false,
+ Cfg: &smtp.Source{
+ Auth: "LOGIN",
+ Host: "localhost",
+ Port: 25,
+ ForceSMTPS: true,
+ SkipVerify: true,
+ HeloHostname: "example.com",
+ DisableHelo: true,
+ AllowedDomains: "example.com,example.org",
+ },
+ TwoFactorPolicy: "skip",
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ a := &authService{
+ initDB: func(ctx context.Context) error {
+ return nil
+ },
+ createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
+ assert.Equal(t, tc.source, source)
+ return nil
+ },
+ }
+
+ cmd := &cli.Command{
+ Flags: microcmdAuthAddSMTP().Flags,
+ Action: a.runAddSMTP,
+ }
+
+ args := []string{"smtp-test"}
+ args = append(args, tc.args...)
+
+ t.Log(args)
+ err := cmd.Run(t.Context(), args)
+
+ if tc.errMsg != "" {
+ assert.EqualError(t, err, tc.errMsg)
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestUpdateSMTP(t *testing.T) {
+ testCases := []struct {
+ name string
+ args []string
+ existingAuthSource *auth_model.Source
+ authSource *auth_model.Source
+ errMsg string
+ }{
+ {
+ name: "missing id",
+ args: []string{
+ "--name", "test",
+ "--host", "localhost",
+ "--port", "25",
+ },
+ errMsg: "--id flag is missing",
+ },
+ {
+ name: "valid config",
+ existingAuthSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.SMTP,
+ Name: "old name",
+ IsActive: true,
+ Cfg: &smtp.Source{
+ Auth: "PLAIN",
+ Host: "old host",
+ Port: 26,
+ },
+ },
+ args: []string{
+ "--id", "1",
+ "--name", "test",
+ "--host", "localhost",
+ "--port", "25",
+ },
+ authSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.SMTP,
+ Name: "test",
+ IsActive: true,
+ Cfg: &smtp.Source{
+ Auth: "PLAIN",
+ Host: "localhost",
+ Port: 25,
+ },
+ },
+ },
+ {
+ name: "valid config with options",
+ existingAuthSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.SMTP,
+ Name: "old name",
+ IsActive: true,
+ Cfg: &smtp.Source{
+ Auth: "PLAIN",
+ Host: "old host",
+ Port: 26,
+ HeloHostname: "old.example.com",
+ AllowedDomains: "old.example.com",
+ },
+ TwoFactorPolicy: "",
+ },
+ args: []string{
+ "--id", "1",
+ "--name", "test",
+ "--host", "localhost",
+ "--port", "25",
+ "--auth-type", "LOGIN",
+ "--force-smtps",
+ "--skip-verify",
+ "--helo-hostname", "example.com",
+ "--disable-helo",
+ "--allowed-domains", "example.com,example.org",
+ "--skip-local-2fa",
+ "--active=false",
+ },
+ authSource: &auth_model.Source{
+ ID: 1,
+ Type: auth_model.SMTP,
+ Name: "test",
+ IsActive: false,
+ Cfg: &smtp.Source{
+ Auth: "LOGIN",
+ Host: "localhost",
+ Port: 25,
+ ForceSMTPS: true,
+ SkipVerify: true,
+ HeloHostname: "example.com",
+ DisableHelo: true,
+ AllowedDomains: "example.com,example.org",
+ },
+ TwoFactorPolicy: "skip",
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ a := &authService{
+ initDB: func(ctx context.Context) error {
+ return nil
+ },
+ getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
+ return &auth_model.Source{
+ ID: 1,
+ Type: auth_model.SMTP,
+ Name: "test",
+ IsActive: true,
+ Cfg: &smtp.Source{
+ Auth: "PLAIN",
+ },
+ }, nil
+ },
+
+ updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
+ assert.Equal(t, tc.authSource, source)
+ return nil
+ },
+ }
+
+ app := &cli.Command{
+ Flags: microcmdAuthUpdateSMTP().Flags,
+ Action: a.runUpdateSMTP,
+ }
+ args := []string{"smtp-tests"}
+ args = append(args, tc.args...)
+
+ err := app.Run(t.Context(), args)
+
+ if tc.errMsg != "" {
+ assert.EqualError(t, err, tc.errMsg)
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go
index ab769f6d0c..a5f1bd5105 100644
--- a/cmd/admin_regenerate.go
+++ b/cmd/admin_regenerate.go
@@ -4,11 +4,13 @@
package cmd
import (
+ "context"
+
"code.gitea.io/gitea/modules/graceful"
asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var (
@@ -25,20 +27,14 @@ var (
}
)
-func runRegenerateHooks(_ *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
}
-func runRegenerateKeys(_ *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
diff --git a/cmd/admin_user.go b/cmd/admin_user.go
index 967a6ed88a..3a24c3e56f 100644
--- a/cmd/admin_user.go
+++ b/cmd/admin_user.go
@@ -4,18 +4,18 @@
package cmd
import (
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var subcmdUser = &cli.Command{
Name: "user",
Usage: "Modify users",
- Subcommands: []*cli.Command{
- microcmdUserCreate,
+ Commands: []*cli.Command{
+ microcmdUserCreate(),
microcmdUserList,
- microcmdUserChangePassword,
- microcmdUserDelete,
+ microcmdUserChangePassword(),
+ microcmdUserDelete(),
microcmdUserGenerateAccessToken,
- microcmdUserMustChangePassword,
+ microcmdUserMustChangePassword(),
},
}
diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go
index f1ed46e70b..c27905b4db 100644
--- a/cmd/admin_user_change_password.go
+++ b/cmd/admin_user_change_password.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"fmt"
@@ -13,44 +14,41 @@ import (
"code.gitea.io/gitea/modules/setting"
user_service "code.gitea.io/gitea/services/user"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-var microcmdUserChangePassword = &cli.Command{
- Name: "change-password",
- Usage: "Change a user's password",
- Action: runChangePassword,
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "username",
- Aliases: []string{"u"},
- Value: "",
- Usage: "The user to change password for",
+func microcmdUserChangePassword() *cli.Command {
+ return &cli.Command{
+ Name: "change-password",
+ Usage: "Change a user's password",
+ Action: runChangePassword,
+ Flags: []cli.Flag{
+ &cli.StringFlag{
+ Name: "username",
+ Aliases: []string{"u"},
+ Usage: "The user to change password for",
+ Required: true,
+ },
+ &cli.StringFlag{
+ Name: "password",
+ Aliases: []string{"p"},
+ Usage: "New password to set for user",
+ Required: true,
+ },
+ &cli.BoolFlag{
+ Name: "must-change-password",
+ Usage: "User must change password (can be disabled by --must-change-password=false)",
+ Value: true,
+ },
},
- &cli.StringFlag{
- Name: "password",
- Aliases: []string{"p"},
- Value: "",
- Usage: "New password to set for user",
- },
- &cli.BoolFlag{
- Name: "must-change-password",
- Usage: "User must change password (can be disabled by --must-change-password=false)",
- Value: true,
- },
- },
-}
-
-func runChangePassword(c *cli.Context) error {
- if err := argsSet(c, "username", "password"); err != nil {
- return err
}
+}
- ctx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(ctx); err != nil {
- return err
+func runChangePassword(ctx context.Context, c *cli.Command) error {
+ if !setting.IsInTesting {
+ if err := initDB(ctx); err != nil {
+ return err
+ }
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
diff --git a/cmd/admin_user_change_password_test.go b/cmd/admin_user_change_password_test.go
new file mode 100644
index 0000000000..17d0382af7
--- /dev/null
+++ b/cmd/admin_user_change_password_test.go
@@ -0,0 +1,91 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestChangePasswordCommand(t *testing.T) {
+ ctx := t.Context()
+
+ defer func() {
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
+ }()
+
+ t.Run("change password successfully", func(t *testing.T) {
+ // defer func() {
+ // require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
+ // }()
+ // Prepare test user
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ err := microcmdUserCreate().Run(ctx, []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
+ require.NoError(t, err)
+
+ // load test user
+ userBase := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+
+ // Change the password
+ err = microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "newpassword"})
+ require.NoError(t, err)
+
+ // Verify the password has been changed
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.NotEqual(t, userBase.Passwd, user.Passwd)
+ assert.NotEqual(t, userBase.Salt, user.Salt)
+
+ // Additional check for must-change-password flag
+ require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "anotherpassword", "--must-change-password=false"}))
+ user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.False(t, user.MustChangePassword)
+
+ require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "yetanotherpassword", "--must-change-password"}))
+ user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.True(t, user.MustChangePassword)
+ })
+
+ t.Run("failure cases", func(t *testing.T) {
+ testCases := []struct {
+ name string
+ args []string
+ expectedErr string
+ }{
+ {
+ name: "user does not exist",
+ args: []string{"change-password", "--username", "nonexistentuser", "--password", "newpassword"},
+ expectedErr: "user does not exist",
+ },
+ {
+ name: "missing username",
+ args: []string{"change-password", "--password", "newpassword"},
+ expectedErr: `"username" not set`,
+ },
+ {
+ name: "missing password",
+ args: []string{"change-password", "--username", "testuser"},
+ expectedErr: `"password" not set`,
+ },
+ {
+ name: "too short password",
+ args: []string{"change-password", "--username", "testuser", "--password", "1"},
+ expectedErr: "password is not long enough",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ err := microcmdUserChangePassword().Run(ctx, tc.args)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tc.expectedErr)
+ })
+ }
+ })
+}
diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go
index 97f9bb7f06..cbdb5f90e2 100644
--- a/cmd/admin_user_create.go
+++ b/cmd/admin_user_create.go
@@ -16,87 +16,95 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-var microcmdUserCreate = &cli.Command{
- Name: "create",
- Usage: "Create a new user in database",
- Action: runCreateUser,
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "name",
- Usage: "Username. DEPRECATED: use username instead",
+func microcmdUserCreate() *cli.Command {
+ return &cli.Command{
+ Name: "create",
+ Usage: "Create a new user in database",
+ Action: runCreateUser,
+ MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{
+ {
+ Flags: [][]cli.Flag{
+ {
+ &cli.StringFlag{
+ Name: "name",
+ Usage: "Username. DEPRECATED: use username instead",
+ },
+ &cli.StringFlag{
+ Name: "username",
+ Usage: "Username",
+ },
+ },
+ },
+ Required: true,
+ },
},
- &cli.StringFlag{
- Name: "username",
- Usage: "Username",
+ Flags: []cli.Flag{
+ &cli.StringFlag{
+ Name: "user-type",
+ Usage: "Set user's type: individual or bot",
+ Value: "individual",
+ },
+ &cli.StringFlag{
+ Name: "password",
+ Usage: "User password",
+ },
+ &cli.StringFlag{
+ Name: "email",
+ Usage: "User email address",
+ Required: true,
+ },
+ &cli.BoolFlag{
+ Name: "admin",
+ Usage: "User is an admin",
+ },
+ &cli.BoolFlag{
+ Name: "random-password",
+ Usage: "Generate a random password for the user",
+ },
+ &cli.BoolFlag{
+ Name: "must-change-password",
+ Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
+ HideDefault: true,
+ },
+ &cli.IntFlag{
+ Name: "random-password-length",
+ Usage: "Length of the random password to be generated",
+ Value: 12,
+ },
+ &cli.BoolFlag{
+ Name: "access-token",
+ Usage: "Generate access token for the user",
+ },
+ &cli.StringFlag{
+ Name: "access-token-name",
+ Usage: `Name of the generated access token`,
+ Value: "gitea-admin",
+ },
+ &cli.StringFlag{
+ Name: "access-token-scopes",
+ Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
+ Value: "all",
+ },
+ &cli.BoolFlag{
+ Name: "restricted",
+ Usage: "Make a restricted user account",
+ },
+ &cli.StringFlag{
+ Name: "fullname",
+ Usage: `The full, human-readable name of the user`,
+ },
},
- &cli.StringFlag{
- Name: "user-type",
- Usage: "Set user's type: individual or bot",
- Value: "individual",
- },
- &cli.StringFlag{
- Name: "password",
- Usage: "User password",
- },
- &cli.StringFlag{
- Name: "email",
- Usage: "User email address",
- },
- &cli.BoolFlag{
- Name: "admin",
- Usage: "User is an admin",
- },
- &cli.BoolFlag{
- Name: "random-password",
- Usage: "Generate a random password for the user",
- },
- &cli.BoolFlag{
- Name: "must-change-password",
- Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
- DisableDefaultText: true,
- },
- &cli.IntFlag{
- Name: "random-password-length",
- Usage: "Length of the random password to be generated",
- Value: 12,
- },
- &cli.BoolFlag{
- Name: "access-token",
- Usage: "Generate access token for the user",
- },
- &cli.StringFlag{
- Name: "access-token-name",
- Usage: `Name of the generated access token`,
- Value: "gitea-admin",
- },
- &cli.StringFlag{
- Name: "access-token-scopes",
- Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
- Value: "all",
- },
- &cli.BoolFlag{
- Name: "restricted",
- Usage: "Make a restricted user account",
- },
- &cli.StringFlag{
- Name: "fullname",
- Usage: `The full, human-readable name of the user`,
- },
- },
+ }
}
-func runCreateUser(c *cli.Context) error {
+func runCreateUser(ctx context.Context, c *cli.Command) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings()
- if err := argsSet(c, "email"); err != nil {
- return err
- }
-
userTypes := map[string]user_model.UserType{
"individual": user_model.UserTypeIndividual,
"bot": user_model.UserTypeBot,
@@ -113,12 +121,6 @@ func runCreateUser(c *cli.Context) error {
return errors.New("password can only be set for individual users")
}
}
- if c.IsSet("name") && c.IsSet("username") {
- return errors.New("cannot set both --name and --username flags")
- }
- if !c.IsSet("name") && !c.IsSet("username") {
- return errors.New("one of --name or --username flags must be set")
- }
if c.IsSet("password") && c.IsSet("random-password") {
return errors.New("cannot set both -random-password and -password flags")
@@ -129,16 +131,12 @@ func runCreateUser(c *cli.Context) error {
username = c.String("username")
} else {
username = c.String("name")
- _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
+ _, _ = fmt.Fprintf(c.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
}
- ctx := c.Context
if !setting.IsInTesting {
- // FIXME: need to refactor the "installSignals/initDB" related code later
+ // FIXME: need to refactor the "initDB" related code later
// it doesn't make sense to call it in (almost) every command action function
- var cancel context.CancelFunc
- ctx, cancel = installSignals()
- defer cancel()
if err := initDB(ctx); err != nil {
return err
}
diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go
index d5952412c3..437e07d9a2 100644
--- a/cmd/admin_user_create_test.go
+++ b/cmd/admin_user_create_test.go
@@ -18,8 +18,6 @@ import (
)
func TestAdminUserCreate(t *testing.T) {
- app := NewMainApp(AppVersion{})
-
reset := func() {
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
@@ -31,8 +29,9 @@ func TestAdminUserCreate(t *testing.T) {
IsAdmin bool
MustChangePassword bool
}
+
createCheck := func(name, args string) check {
- require.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
+ require.NoError(t, microcmdUserCreate().Run(t.Context(), strings.Fields(fmt.Sprintf("create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword}
}
@@ -51,7 +50,7 @@ func TestAdminUserCreate(t *testing.T) {
})
createUser := func(name string, args ...string) error {
- return app.Run(append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...))
+ return microcmdUserCreate().Run(t.Context(), append([]string{"create", "--username", name, "--email", name + "@gitea.local"}, args...))
}
t.Run("UserType", func(t *testing.T) {
diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go
index 520557554a..f91041577c 100644
--- a/cmd/admin_user_delete.go
+++ b/cmd/admin_user_delete.go
@@ -4,53 +4,56 @@
package cmd
import (
+ "context"
"errors"
"fmt"
"strings"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-var microcmdUserDelete = &cli.Command{
- Name: "delete",
- Usage: "Delete specific user by id, name or email",
- Flags: []cli.Flag{
- &cli.Int64Flag{
- Name: "id",
- Usage: "ID of user of the user to delete",
+func microcmdUserDelete() *cli.Command {
+ return &cli.Command{
+ Name: "delete",
+ Usage: "Delete specific user by id, name or email",
+ Flags: []cli.Flag{
+ &cli.Int64Flag{
+ Name: "id",
+ Usage: "ID of user of the user to delete",
+ },
+ &cli.StringFlag{
+ Name: "username",
+ Aliases: []string{"u"},
+ Usage: "Username of the user to delete",
+ },
+ &cli.StringFlag{
+ Name: "email",
+ Aliases: []string{"e"},
+ Usage: "Email of the user to delete",
+ },
+ &cli.BoolFlag{
+ Name: "purge",
+ Usage: "Purge user, all their repositories, organizations and comments",
+ },
},
- &cli.StringFlag{
- Name: "username",
- Aliases: []string{"u"},
- Usage: "Username of the user to delete",
- },
- &cli.StringFlag{
- Name: "email",
- Aliases: []string{"e"},
- Usage: "Email of the user to delete",
- },
- &cli.BoolFlag{
- Name: "purge",
- Usage: "Purge user, all their repositories, organizations and comments",
- },
- },
- Action: runDeleteUser,
+ Action: runDeleteUser,
+ }
}
-func runDeleteUser(c *cli.Context) error {
+func runDeleteUser(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return errors.New("You must provide the id, username or email of a user to delete")
}
- ctx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(ctx); err != nil {
- return err
+ if !setting.IsInTesting {
+ if err := initDB(ctx); err != nil {
+ return err
+ }
}
if err := storage.Init(); err != nil {
@@ -70,11 +73,11 @@ func runDeleteUser(c *cli.Context) error {
return err
}
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
- return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
+ return fmt.Errorf("the user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
}
if c.IsSet("id") && user.ID != c.Int64("id") {
- return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
+ return fmt.Errorf("the user %s does not match the provided id %d", user.Name, c.Int64("id"))
}
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
diff --git a/cmd/admin_user_delete_test.go b/cmd/admin_user_delete_test.go
new file mode 100644
index 0000000000..d0330582d7
--- /dev/null
+++ b/cmd/admin_user_delete_test.go
@@ -0,0 +1,111 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestAdminUserDelete(t *testing.T) {
+ ctx := t.Context()
+ defer func() {
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
+ }()
+
+ setupTestUser := func(t *testing.T) {
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
+ require.NoError(t, err)
+ }
+
+ t.Run("delete user by id", func(t *testing.T) {
+ setupTestUser(t)
+
+ u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)})
+ require.NoError(t, err)
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ })
+ t.Run("delete user by username", func(t *testing.T) {
+ setupTestUser(t)
+
+ err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"})
+ require.NoError(t, err)
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ })
+ t.Run("delete user by email", func(t *testing.T) {
+ setupTestUser(t)
+
+ err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"})
+ require.NoError(t, err)
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ })
+ t.Run("delete user by all 3 attributes", func(t *testing.T) {
+ setupTestUser(t)
+
+ u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"})
+ require.NoError(t, err)
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ })
+}
+
+func TestAdminUserDeleteFailure(t *testing.T) {
+ testCases := []struct {
+ name string
+ args []string
+ expectedErr string
+ }{
+ {
+ name: "no user to delete",
+ args: []string{"delete", "--username", "nonexistentuser"},
+ expectedErr: "user does not exist",
+ },
+ {
+ name: "user exists but provided username does not match",
+ args: []string{"delete", "--email", "testuser@gitea.local", "--username", "wrongusername"},
+ expectedErr: "the user testuser who has email testuser@gitea.local does not match the provided username wrongusername",
+ },
+ {
+ name: "user exists but provided id does not match",
+ args: []string{"delete", "--username", "testuser", "--id", "999"},
+ expectedErr: "the user testuser does not match the provided id 999",
+ },
+ {
+ name: "no required flags are provided",
+ args: []string{"delete"},
+ expectedErr: "You must provide the id, username or email of a user to delete",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx := t.Context()
+ if strings.Contains(tc.name, "user exists") {
+ unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
+ err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
+ require.NoError(t, err)
+ }
+
+ err := microcmdUserDelete().Run(ctx, tc.args)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tc.expectedErr)
+ })
+
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
+ }
+}
diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go
index f6db7a74bd..61064fdef4 100644
--- a/cmd/admin_user_generate_access_token.go
+++ b/cmd/admin_user_generate_access_token.go
@@ -4,13 +4,14 @@
package cmd
import (
+ "context"
"errors"
"fmt"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var microcmdUserGenerateAccessToken = &cli.Command{
@@ -41,14 +42,11 @@ var microcmdUserGenerateAccessToken = &cli.Command{
Action: runGenerateAccessToken,
}
-func runGenerateAccessToken(c *cli.Context) error {
+func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("you must provide a username to generate a token for")
}
- ctx, cancel := installSignals()
- defer cancel()
-
if err := initDB(ctx); err != nil {
return err
}
diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go
index 4c2b26d1df..e3d345e2f2 100644
--- a/cmd/admin_user_list.go
+++ b/cmd/admin_user_list.go
@@ -4,13 +4,14 @@
package cmd
import (
+ "context"
"fmt"
"os"
"text/tabwriter"
user_model "code.gitea.io/gitea/models/user"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var microcmdUserList = &cli.Command{
@@ -25,10 +26,7 @@ var microcmdUserList = &cli.Command{
},
}
-func runListUsers(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runListUsers(ctx context.Context, c *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
diff --git a/cmd/admin_user_must_change_password.go b/cmd/admin_user_must_change_password.go
index 2794414259..8521853dc1 100644
--- a/cmd/admin_user_must_change_password.go
+++ b/cmd/admin_user_must_change_password.go
@@ -4,40 +4,41 @@
package cmd
import (
+ "context"
"errors"
"fmt"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-var microcmdUserMustChangePassword = &cli.Command{
- Name: "must-change-password",
- Usage: "Set the must change password flag for the provided users or all users",
- Action: runMustChangePassword,
- Flags: []cli.Flag{
- &cli.BoolFlag{
- Name: "all",
- Aliases: []string{"A"},
- Usage: "All users must change password, except those explicitly excluded with --exclude",
+func microcmdUserMustChangePassword() *cli.Command {
+ return &cli.Command{
+ Name: "must-change-password",
+ Usage: "Set the must change password flag for the provided users or all users",
+ Action: runMustChangePassword,
+ Flags: []cli.Flag{
+ &cli.BoolFlag{
+ Name: "all",
+ Aliases: []string{"A"},
+ Usage: "All users must change password, except those explicitly excluded with --exclude",
+ },
+ &cli.StringSliceFlag{
+ Name: "exclude",
+ Aliases: []string{"e"},
+ Usage: "Do not change the must-change-password flag for these users",
+ },
+ &cli.BoolFlag{
+ Name: "unset",
+ Usage: "Instead of setting the must-change-password flag, unset it",
+ },
},
- &cli.StringSliceFlag{
- Name: "exclude",
- Aliases: []string{"e"},
- Usage: "Do not change the must-change-password flag for these users",
- },
- &cli.BoolFlag{
- Name: "unset",
- Usage: "Instead of setting the must-change-password flag, unset it",
- },
- },
+ }
}
-func runMustChangePassword(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runMustChangePassword(ctx context.Context, c *cli.Command) error {
if c.NArg() == 0 && !c.IsSet("all") {
return errors.New("either usernames or --all must be provided")
}
@@ -46,8 +47,10 @@ func runMustChangePassword(c *cli.Context) error {
all := c.Bool("all")
exclude := c.StringSlice("exclude")
- if err := initDB(ctx); err != nil {
- return err
+ if !setting.IsInTesting {
+ if err := initDB(ctx); err != nil {
+ return err
+ }
}
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)
diff --git a/cmd/admin_user_must_change_password_test.go b/cmd/admin_user_must_change_password_test.go
new file mode 100644
index 0000000000..a6611fdc04
--- /dev/null
+++ b/cmd/admin_user_must_change_password_test.go
@@ -0,0 +1,78 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestMustChangePassword(t *testing.T) {
+ defer func() {
+ require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
+ }()
+ err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
+ require.NoError(t, err)
+ err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"})
+ require.NoError(t, err)
+ // Reset password change flag
+ err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"})
+ require.NoError(t, err)
+
+ testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.False(t, testUser.MustChangePassword)
+ testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
+ assert.False(t, testUserExclude.MustChangePassword)
+
+ // Make all users change password
+ err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"})
+ require.NoError(t, err)
+
+ testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.True(t, testUser.MustChangePassword)
+ testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
+ assert.True(t, testUserExclude.MustChangePassword)
+
+ // Reset password change flag but exclude all tested users
+ err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"})
+ require.NoError(t, err)
+
+ testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.True(t, testUser.MustChangePassword)
+ testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
+ assert.True(t, testUserExclude.MustChangePassword)
+
+ // Reset password change flag by listing multiple users
+ err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"})
+ require.NoError(t, err)
+
+ testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.False(t, testUser.MustChangePassword)
+ testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
+ assert.False(t, testUserExclude.MustChangePassword)
+
+ // Exclude a user from all user
+ err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"})
+ require.NoError(t, err)
+
+ testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.True(t, testUser.MustChangePassword)
+ testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
+ assert.False(t, testUserExclude.MustChangePassword)
+
+ // Unset a flag for single user
+ err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"})
+ require.NoError(t, err)
+
+ testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
+ assert.False(t, testUser.MustChangePassword)
+ testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
+ assert.False(t, testUserExclude.MustChangePassword)
+}
diff --git a/cmd/cert.go b/cmd/cert.go
index 38241d71a3..53b4f9dcb4 100644
--- a/cmd/cert.go
+++ b/cmd/cert.go
@@ -6,6 +6,7 @@
package cmd
import (
+ "context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@@ -13,6 +14,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
+ "fmt"
"log"
"math/big"
"net"
@@ -20,47 +22,59 @@ import (
"strings"
"time"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-// CmdCert represents the available cert sub-command.
-var CmdCert = &cli.Command{
- Name: "cert",
- Usage: "Generate self-signed certificate",
- Description: `Generate a self-signed X.509 certificate for a TLS server.
+// cmdCert represents the available cert sub-command.
+func cmdCert() *cli.Command {
+ return &cli.Command{
+ Name: "cert",
+ Usage: "Generate self-signed certificate",
+ Description: `Generate a self-signed X.509 certificate for a TLS server.
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
- Action: runCert,
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "host",
- Value: "",
- Usage: "Comma-separated hostnames and IPs to generate a certificate for",
+ Action: runCert,
+ Flags: []cli.Flag{
+ &cli.StringFlag{
+ Name: "host",
+ Usage: "Comma-separated hostnames and IPs to generate a certificate for",
+ Required: true,
+ },
+ &cli.StringFlag{
+ Name: "ecdsa-curve",
+ Value: "",
+ Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
+ },
+ &cli.IntFlag{
+ Name: "rsa-bits",
+ Value: 3072,
+ Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
+ },
+ &cli.StringFlag{
+ Name: "start-date",
+ Value: "",
+ Usage: "Creation date formatted as Jan 1 15:04:05 2011",
+ },
+ &cli.DurationFlag{
+ Name: "duration",
+ Value: 365 * 24 * time.Hour,
+ Usage: "Duration that certificate is valid for",
+ },
+ &cli.BoolFlag{
+ Name: "ca",
+ Usage: "whether this cert should be its own Certificate Authority",
+ },
+ &cli.StringFlag{
+ Name: "out",
+ Value: "cert.pem",
+ Usage: "Path to the file where there certificate will be saved",
+ },
+ &cli.StringFlag{
+ Name: "keyout",
+ Value: "key.pem",
+ Usage: "Path to the file where there certificate key will be saved",
+ },
},
- &cli.StringFlag{
- Name: "ecdsa-curve",
- Value: "",
- Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
- },
- &cli.IntFlag{
- Name: "rsa-bits",
- Value: 3072,
- Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
- },
- &cli.StringFlag{
- Name: "start-date",
- Value: "",
- Usage: "Creation date formatted as Jan 1 15:04:05 2011",
- },
- &cli.DurationFlag{
- Name: "duration",
- Value: 365 * 24 * time.Hour,
- Usage: "Duration that certificate is valid for",
- },
- &cli.BoolFlag{
- Name: "ca",
- Usage: "whether this cert should be its own Certificate Authority",
- },
- },
+ }
}
func publicKey(priv any) any {
@@ -89,11 +103,7 @@ func pemBlockForKey(priv any) *pem.Block {
}
}
-func runCert(c *cli.Context) error {
- if err := argsSet(c, "host"); err != nil {
- return err
- }
-
+func runCert(_ context.Context, c *cli.Command) error {
var priv any
var err error
switch c.String("ecdsa-curve") {
@@ -108,17 +118,17 @@ func runCert(c *cli.Context) error {
case "P521":
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
- log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
+ err = fmt.Errorf("unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
}
if err != nil {
- log.Fatalf("Failed to generate private key: %v", err)
+ return fmt.Errorf("failed to generate private key: %w", err)
}
var notBefore time.Time
if startDate := c.String("start-date"); startDate != "" {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
if err != nil {
- log.Fatalf("Failed to parse creation date: %v", err)
+ return fmt.Errorf("failed to parse creation date %w", err)
}
} else {
notBefore = time.Now()
@@ -129,7 +139,7 @@ func runCert(c *cli.Context) error {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
- log.Fatalf("Failed to generate serial number: %v", err)
+ return fmt.Errorf("failed to generate serial number: %w", err)
}
template := x509.Certificate{
@@ -146,8 +156,8 @@ func runCert(c *cli.Context) error {
BasicConstraintsValid: true,
}
- hosts := strings.Split(c.String("host"), ",")
- for _, h := range hosts {
+ hosts := strings.SplitSeq(c.String("host"), ",")
+ for h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
@@ -162,35 +172,35 @@ func runCert(c *cli.Context) error {
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
- log.Fatalf("Failed to create certificate: %v", err)
+ return fmt.Errorf("failed to create certificate: %w", err)
}
- certOut, err := os.Create("cert.pem")
+ certOut, err := os.Create(c.String("out"))
if err != nil {
- log.Fatalf("Failed to open cert.pem for writing: %v", err)
+ return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err)
}
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
- log.Fatalf("Failed to encode certificate: %v", err)
+ return fmt.Errorf("failed to encode certificate: %w", err)
}
err = certOut.Close()
if err != nil {
- log.Fatalf("Failed to write cert: %v", err)
+ return fmt.Errorf("failed to write cert: %w", err)
}
- log.Println("Written cert.pem")
+ fmt.Fprintf(c.Writer, "Written cert to %s\n", c.String("out"))
- keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
+ keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
- log.Fatalf("Failed to open key.pem for writing: %v", err)
+ return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err)
}
err = pem.Encode(keyOut, pemBlockForKey(priv))
if err != nil {
- log.Fatalf("Failed to encode key: %v", err)
+ return fmt.Errorf("failed to encode key: %w", err)
}
err = keyOut.Close()
if err != nil {
- log.Fatalf("Failed to write key: %v", err)
+ return fmt.Errorf("failed to write key: %w", err)
}
- log.Println("Written key.pem")
+ fmt.Fprintf(c.Writer, "Written key to %s\n", c.String("keyout"))
return nil
}
diff --git a/cmd/cert_test.go b/cmd/cert_test.go
new file mode 100644
index 0000000000..4242d8915b
--- /dev/null
+++ b/cmd/cert_test.go
@@ -0,0 +1,123 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestCertCommand(t *testing.T) {
+ cases := []struct {
+ name string
+ args []string
+ }{
+ {
+ name: "RSA cert generation",
+ args: []string{
+ "cert-test",
+ "--host", "localhost",
+ "--rsa-bits", "2048",
+ "--duration", "1h",
+ "--start-date", "Jan 1 00:00:00 2024",
+ },
+ },
+ {
+ name: "ECDSA cert generation",
+ args: []string{
+ "cert-test",
+ "--host", "localhost",
+ "--ecdsa-curve", "P256",
+ "--duration", "1h",
+ "--start-date", "Jan 1 00:00:00 2024",
+ },
+ },
+ {
+ name: "mixed host, certificate authority",
+ args: []string{
+ "cert-test",
+ "--host", "localhost,127.0.0.1",
+ "--duration", "1h",
+ "--start-date", "Jan 1 00:00:00 2024",
+ },
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ app := cmdCert()
+ tempDir := t.TempDir()
+
+ certFile := filepath.Join(tempDir, "cert.pem")
+ keyFile := filepath.Join(tempDir, "key.pem")
+
+ err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
+ require.NoError(t, err)
+
+ assert.FileExists(t, certFile)
+ assert.FileExists(t, keyFile)
+ })
+ }
+}
+
+func TestCertCommandFailures(t *testing.T) {
+ cases := []struct {
+ name string
+ args []string
+ errMsg string
+ }{
+ {
+ name: "Start Date Parsing failure",
+ args: []string{
+ "cert-test",
+ "--host", "localhost",
+ "--start-date", "invalid-date",
+ },
+ errMsg: "parsing time",
+ },
+ {
+ name: "Unknown curve",
+ args: []string{
+ "cert-test",
+ "--host", "localhost",
+ "--ecdsa-curve", "invalid-curve",
+ },
+ errMsg: "unrecognized elliptic curve",
+ },
+ {
+ name: "Key generation failure",
+ args: []string{
+ "cert-test",
+ "--host", "localhost",
+ "--rsa-bits", "invalid-bits",
+ },
+ },
+ {
+ name: "Missing parameters",
+ args: []string{
+ "cert-test",
+ },
+ errMsg: `"host" not set`,
+ },
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ app := cmdCert()
+ tempDir := t.TempDir()
+
+ certFile := filepath.Join(tempDir, "cert.pem")
+ keyFile := filepath.Join(tempDir, "key.pem")
+ err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
+ require.Error(t, err)
+ if c.errMsg != "" {
+ assert.ErrorContains(t, err, c.errMsg)
+ }
+ assert.NoFileExists(t, certFile)
+ assert.NoFileExists(t, keyFile)
+ })
+ }
+}
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 423dce2674..5b96bcbf9a 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -18,20 +18,19 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context.
-func argsSet(c *cli.Context, args ...string) error {
+func argsSet(c *cli.Command, args ...string) error {
for _, a := range args {
if !c.IsSet(a) {
return errors.New(a + " is not set")
}
- if util.IsEmptyString(c.String(a)) {
+ if c.Value(a) == nil {
return errors.New(a + " is required")
}
}
@@ -109,7 +108,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
-func globalBool(c *cli.Context, name string) bool {
+func globalBool(c *cli.Command, name string) bool {
for _, ctx := range c.Lineage() {
if ctx.Bool(name) {
return true
@@ -120,8 +119,8 @@ func globalBool(c *cli.Context, name string) bool {
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
-func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
- return func(c *cli.Context) error {
+func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) {
+ return func(ctx context.Context, c *cli.Command) (context.Context, error) {
level := defaultLevel
if globalBool(c, "quiet") {
level = log.FATAL
@@ -130,6 +129,16 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error
level = log.TRACE
}
log.SetConsoleLogger(log.DEFAULT, "console-default", level)
- return nil
+ return ctx, nil
}
}
+
+func isValidDefaultSubCommand(cmd *cli.Command) (string, bool) {
+ // Dirty patch for urfave/cli's strange design.
+ // "./gitea bad-cmd" should not start the web server.
+ rootArgs := cmd.Root().Args().Slice()
+ if len(rootArgs) != 0 && rootArgs[0] != cmd.Name {
+ return rootArgs[0], false
+ }
+ return "", true
+}
diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go
new file mode 100644
index 0000000000..a36d05c76e
--- /dev/null
+++ b/cmd/cmd_test.go
@@ -0,0 +1,38 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/urfave/cli/v3"
+)
+
+func TestDefaultCommand(t *testing.T) {
+ test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) {
+ called := false
+ cmd := &cli.Command{
+ DefaultCommand: "test",
+ Commands: []*cli.Command{
+ {
+ Name: "test",
+ Action: func(ctx context.Context, command *cli.Command) error {
+ retName, retValid := isValidDefaultSubCommand(command)
+ assert.Equal(t, expectedRetName, retName)
+ assert.Equal(t, expectedRetValid, retValid)
+ called = true
+ return nil
+ },
+ },
+ },
+ }
+ assert.NoError(t, cmd.Run(t.Context(), args))
+ assert.True(t, called)
+ }
+ test(t, []string{"./gitea"}, "", true)
+ test(t, []string{"./gitea", "test"}, "", true)
+ test(t, []string{"./gitea", "other"}, "other", false)
+}
diff --git a/cmd/docs.go b/cmd/docs.go
index 605d02e3ef..098c0e9a8a 100644
--- a/cmd/docs.go
+++ b/cmd/docs.go
@@ -4,11 +4,13 @@
package cmd
import (
+ "context"
"fmt"
"os"
"strings"
- "github.com/urfave/cli/v2"
+ cli_docs "github.com/urfave/cli-docs/v3"
+ "github.com/urfave/cli/v3"
)
// CmdDocs represents the available docs sub-command.
@@ -30,16 +32,16 @@ var CmdDocs = &cli.Command{
},
}
-func runDocs(ctx *cli.Context) error {
- docs, err := ctx.App.ToMarkdown()
- if ctx.Bool("man") {
- docs, err = ctx.App.ToMan()
+func runDocs(_ context.Context, cmd *cli.Command) error {
+ docs, err := cli_docs.ToMarkdown(cmd.Root())
+ if cmd.Bool("man") {
+ docs, err = cli_docs.ToMan(cmd.Root())
}
if err != nil {
return err
}
- if !ctx.Bool("man") {
+ if !cmd.Bool("man") {
// Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040
@@ -51,8 +53,8 @@ func runDocs(ctx *cli.Context) error {
}
out := os.Stdout
- if ctx.String("output") != "" {
- fi, err := os.Create(ctx.String("output"))
+ if cmd.String("output") != "" {
+ fi, err := os.Create(cmd.String("output"))
if err != nil {
return err
}
diff --git a/cmd/doctor.go b/cmd/doctor.go
index 4a12b957f5..9e0fcbf877 100644
--- a/cmd/doctor.go
+++ b/cmd/doctor.go
@@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/doctor"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
"xorm.io/xorm"
)
@@ -30,7 +30,7 @@ var CmdDoctor = &cli.Command{
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
cmdDoctorCheck,
cmdRecreateTable,
cmdDoctorConvert,
@@ -93,16 +93,13 @@ You should back-up your database before doing this and ensure that your database
Action: runRecreateTable,
}
-func runRecreateTable(ctx *cli.Context) error {
- stdCtx, cancel := installSignals()
- defer cancel()
-
+func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
// Redirect the default golog to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
- debug := ctx.Bool("debug")
+ debug := cmd.Bool("debug")
setting.MustInstalled()
setting.LoadDBSetting()
@@ -113,15 +110,15 @@ func runRecreateTable(ctx *cli.Context) error {
}
setting.Database.LogSQL = debug
- if err := db.InitEngine(stdCtx); err != nil {
+ if err := db.InitEngine(ctx); err != nil {
fmt.Println(err)
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil
}
- args := ctx.Args()
- names := make([]string, 0, ctx.NArg())
- for i := 0; i < ctx.NArg(); i++ {
+ args := cmd.Args()
+ names := make([]string, 0, cmd.NArg())
+ for i := 0; i < cmd.NArg(); i++ {
names = append(names, args.Get(i))
}
@@ -131,7 +128,7 @@ func runRecreateTable(ctx *cli.Context) error {
}
recreateTables := migrate_base.RecreateTables(beans...)
- return db.InitEngineWithMigration(stdCtx, func(ctx context.Context, x *xorm.Engine) error {
+ return db.InitEngineWithMigration(ctx, func(ctx context.Context, x *xorm.Engine) error {
if err := migrations.EnsureUpToDate(ctx, x); err != nil {
return err
}
@@ -139,11 +136,11 @@ func runRecreateTable(ctx *cli.Context) error {
})
}
-func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
+func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) {
// Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
- logFile := ctx.String("log-file")
+ logFile := cmd.String("log-file")
switch logFile {
case "":
return // if no doctor log-file is set, do not show any log from default logger
@@ -161,23 +158,20 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
}
}
-func runDoctorCheck(ctx *cli.Context) error {
- stdCtx, cancel := installSignals()
- defer cancel()
-
+func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
colorize := log.CanColorStdout
- if ctx.IsSet("color") {
- colorize = ctx.Bool("color")
+ if cmd.IsSet("color") {
+ colorize = cmd.Bool("color")
}
- setupDoctorDefaultLogger(ctx, colorize)
+ setupDoctorDefaultLogger(cmd, colorize)
// Finally redirect the default golang's log to here
golog.SetFlags(0)
golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
- if ctx.IsSet("list") {
+ if cmd.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n"))
doctor.SortChecks(doctor.Checks)
@@ -195,12 +189,12 @@ func runDoctorCheck(ctx *cli.Context) error {
}
var checks []*doctor.Check
- if ctx.Bool("all") {
+ if cmd.Bool("all") {
checks = make([]*doctor.Check, len(doctor.Checks))
copy(checks, doctor.Checks)
- } else if ctx.IsSet("run") {
- addDefault := ctx.Bool("default")
- runNamesSet := container.SetOf(ctx.StringSlice("run")...)
+ } else if cmd.IsSet("run") {
+ addDefault := cmd.Bool("default")
+ runNamesSet := container.SetOf(cmd.StringSlice("run")...)
for _, check := range doctor.Checks {
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check)
@@ -217,5 +211,5 @@ func runDoctorCheck(ctx *cli.Context) error {
}
}
}
- return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
+ return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks)
}
diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go
index 48c835ad0e..8cb718d383 100644
--- a/cmd/doctor_convert.go
+++ b/cmd/doctor_convert.go
@@ -4,13 +4,14 @@
package cmd
import (
+ "context"
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// cmdDoctorConvert represents the available convert sub-command.
@@ -21,11 +22,8 @@ var cmdDoctorConvert = &cli.Command{
Action: runDoctorConvert,
}
-func runDoctorConvert(ctx *cli.Context) error {
- stdCtx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(stdCtx); err != nil {
+func runDoctorConvert(ctx context.Context, cmd *cli.Command) error {
+ if err := initDB(ctx); err != nil {
return err
}
diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go
index 3e1ff299c5..da942b38b6 100644
--- a/cmd/doctor_test.go
+++ b/cmd/doctor_test.go
@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/services/doctor"
"github.com/stretchr/testify/assert"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
func TestDoctorRun(t *testing.T) {
@@ -22,12 +22,13 @@ func TestDoctorRun(t *testing.T) {
SkipDatabaseInitialization: true,
})
- app := cli.NewApp()
- app.Commands = []*cli.Command{cmdDoctorCheck}
- err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
+ app := &cli.Command{
+ Commands: []*cli.Command{cmdDoctorCheck},
+ }
+ err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
assert.NoError(t, err)
- err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
+ err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
- err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
+ err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
}
diff --git a/cmd/dump.go b/cmd/dump.go
index 7d640b78fd..ed19e3d4bf 100644
--- a/cmd/dump.go
+++ b/cmd/dump.go
@@ -5,6 +5,7 @@
package cmd
import (
+ "context"
"os"
"path"
"path/filepath"
@@ -20,7 +21,7 @@ import (
"gitea.com/go-chi/session"
"github.com/mholt/archiver/v3"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// CmdDump represents the available dump sub-command.
@@ -101,17 +102,17 @@ func fatal(format string, args ...any) {
log.Fatal(format, args...)
}
-func runDump(ctx *cli.Context) error {
+func runDump(ctx context.Context, cmd *cli.Command) error {
setting.MustInstalled()
- quite := ctx.Bool("quiet")
- verbose := ctx.Bool("verbose")
+ quite := cmd.Bool("quiet")
+ verbose := cmd.Bool("verbose")
if verbose && quite {
fatal("Option --quiet and --verbose cannot both be set")
}
// outFileName is either "-" or a file name (will be made absolute)
- outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type"))
+ outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type"))
if outType == "" {
fatal("Invalid output type")
}
@@ -136,10 +137,7 @@ func runDump(ctx *cli.Context) error {
setting.DisableLoggerInit()
setting.LoadSettings() // cannot access session settings otherwise
- stdCtx, cancel := installSignals()
- defer cancel()
-
- err := db.InitEngine(stdCtx)
+ err := db.InitEngine(ctx)
if err != nil {
return err
}
@@ -165,7 +163,7 @@ func runDump(ctx *cli.Context) error {
}
dumper.GlobalExcludeAbsPath(outFileName)
- if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
+ if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") {
log.Info("Skip dumping local repositories")
} else {
log.Info("Dumping local repositories... %s", setting.RepoRootPath)
@@ -173,7 +171,7 @@ func runDump(ctx *cli.Context) error {
fatal("Failed to include repositories: %v", err)
}
- if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
+ if cmd.IsSet("skip-lfs-data") && cmd.Bool("skip-lfs-data") {
log.Info("Skip dumping LFS data")
} else if !setting.LFS.StartServer {
log.Info("LFS isn't enabled. Skip dumping LFS data")
@@ -188,12 +186,12 @@ func runDump(ctx *cli.Context) error {
}
}
- if ctx.Bool("skip-db") {
+ if cmd.Bool("skip-db") {
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
dumper.GlobalExcludeAbsPath(setting.Database.Path)
log.Info("Skipping database")
} else {
- tmpDir := ctx.String("tempdir")
+ tmpDir := cmd.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
fatal("Path does not exist: %s", tmpDir)
}
@@ -209,7 +207,7 @@ func runDump(ctx *cli.Context) error {
}
}()
- targetDBType := ctx.String("database")
+ targetDBType := cmd.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else {
@@ -230,7 +228,7 @@ func runDump(ctx *cli.Context) error {
fatal("Failed to include specified app.ini: %v", err)
}
- if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
+ if cmd.IsSet("skip-custom-dir") && cmd.Bool("skip-custom-dir") {
log.Info("Skipping custom directory")
} else {
customDir, err := os.Stat(setting.CustomPath)
@@ -263,7 +261,7 @@ func runDump(ctx *cli.Context) error {
excludes = append(excludes, opts.ProviderConfig)
}
- if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
+ if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
excludes = append(excludes, setting.Indexer.RepoPath)
excludes = append(excludes, setting.Indexer.IssuePath)
}
@@ -278,7 +276,7 @@ func runDump(ctx *cli.Context) error {
}
}
- if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
+ if cmd.IsSet("skip-attachment-data") && cmd.Bool("skip-attachment-data") {
log.Info("Skip dumping attachment data")
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat()
@@ -290,7 +288,7 @@ func runDump(ctx *cli.Context) error {
fatal("Failed to dump attachments: %v", err)
}
- if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
+ if cmd.IsSet("skip-package-data") && cmd.Bool("skip-package-data") {
log.Info("Skip dumping package data")
} else if !setting.Packages.Enabled {
log.Info("Packages isn't enabled. Skip dumping package data")
@@ -307,7 +305,7 @@ func runDump(ctx *cli.Context) error {
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
// ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not.
- if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
+ if cmd.IsSet("skip-log") && cmd.Bool("skip-log") {
log.Info("Skip dumping log files")
} else {
isExist, err := util.IsExist(setting.Log.RootPath)
diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go
index 3a24cf6c5f..a75b2d1b94 100644
--- a/cmd/dump_repo.go
+++ b/cmd/dump_repo.go
@@ -19,7 +19,7 @@ import (
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/migrations"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// CmdDumpRepository represents the available dump repository sub-command.
@@ -79,11 +79,13 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
},
}
-func runDumpRepository(ctx *cli.Context) error {
- stdCtx, cancel := installSignals()
- defer cancel()
+func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
+ setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
- if err := initDB(stdCtx); err != nil {
+ setting.DisableLoggerInit()
+ setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
+
+ if err := initDB(ctx); err != nil {
return err
}
@@ -100,8 +102,8 @@ func runDumpRepository(ctx *cli.Context) error {
var (
serviceType structs.GitServiceType
- cloneAddr = ctx.String("clone_addr")
- serviceStr = ctx.String("git_service")
+ cloneAddr = cmd.String("clone_addr")
+ serviceStr = cmd.String("git_service")
)
if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
@@ -119,13 +121,13 @@ func runDumpRepository(ctx *cli.Context) error {
opts := base.MigrateOptions{
GitServiceType: serviceType,
CloneAddr: cloneAddr,
- AuthUsername: ctx.String("auth_username"),
- AuthPassword: ctx.String("auth_password"),
- AuthToken: ctx.String("auth_token"),
- RepoName: ctx.String("repo_name"),
+ AuthUsername: cmd.String("auth_username"),
+ AuthPassword: cmd.String("auth_password"),
+ AuthToken: cmd.String("auth_token"),
+ RepoName: cmd.String("repo_name"),
}
- if len(ctx.String("units")) == 0 {
+ if len(cmd.String("units")) == 0 {
opts.Wiki = true
opts.Issues = true
opts.Milestones = true
@@ -135,8 +137,8 @@ func runDumpRepository(ctx *cli.Context) error {
opts.PullRequests = true
opts.ReleaseAssets = true
} else {
- units := strings.Split(ctx.String("units"), ",")
- for _, unit := range units {
+ units := strings.SplitSeq(cmd.String("units"), ",")
+ for unit := range units {
switch strings.ToLower(strings.TrimSpace(unit)) {
case "":
continue
@@ -164,7 +166,7 @@ func runDumpRepository(ctx *cli.Context) error {
// the repo_dir will be removed if error occurs in DumpRepository
// make sure the directory doesn't exist or is empty, prevent from deleting user files
- repoDir := ctx.String("repo_dir")
+ repoDir := cmd.String("repo_dir")
if exists, err := util.IsExist(repoDir); err != nil {
return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
} else if exists {
@@ -179,7 +181,7 @@ func runDumpRepository(ctx *cli.Context) error {
if err := migrations.DumpRepository(
context.Background(),
repoDir,
- ctx.String("owner_name"),
+ cmd.String("owner_name"),
opts,
); err != nil {
log.Fatal("Failed to dump repository: %v", err)
diff --git a/cmd/embedded.go b/cmd/embedded.go
index 9f03f7be7c..1908352453 100644
--- a/cmd/embedded.go
+++ b/cmd/embedded.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"fmt"
"os"
@@ -19,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// CmdEmbedded represents the available extract sub-command.
@@ -28,7 +29,7 @@ var (
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
subcmdList,
subcmdView,
subcmdExtract,
@@ -100,7 +101,7 @@ type assetFile struct {
path string
}
-func initEmbeddedExtractor(c *cli.Context) error {
+func initEmbeddedExtractor(c *cli.Command) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args().Slice())
@@ -115,31 +116,31 @@ func initEmbeddedExtractor(c *cli.Context) error {
return nil
}
-func runList(c *cli.Context) error {
+func runList(_ context.Context, c *cli.Command) error {
if err := runListDo(c); err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
-func runView(c *cli.Context) error {
+func runView(_ context.Context, c *cli.Command) error {
if err := runViewDo(c); err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
-func runExtract(c *cli.Context) error {
+func runExtract(_ context.Context, c *cli.Command) error {
if err := runExtractDo(c); err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
-func runListDo(c *cli.Context) error {
+func runListDo(c *cli.Command) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
@@ -151,7 +152,7 @@ func runListDo(c *cli.Context) error {
return nil
}
-func runViewDo(c *cli.Context) error {
+func runViewDo(c *cli.Command) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
@@ -174,7 +175,7 @@ func runViewDo(c *cli.Context) error {
return nil
}
-func runExtractDo(c *cli.Context) error {
+func runExtractDo(c *cli.Command) error {
if err := initEmbeddedExtractor(c); err != nil {
return err
}
@@ -216,7 +217,7 @@ func runExtractDo(c *cli.Context) error {
for _, a := range matchedAssetFiles {
if err := extractAsset(destdir, a, overwrite, rename); err != nil {
// Non-fatal error
- fmt.Fprintf(os.Stderr, "%s: %v", a.path, err)
+ _, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", a.path, err)
}
}
@@ -271,7 +272,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
return nil
}
-func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
+func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) {
fs := assetfs.Layered(layer)
files, err := fs.ListAllFiles(".", true)
if err != nil {
@@ -294,16 +295,14 @@ func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string,
}
}
-func compileCollectPatterns(args []string) ([]glob.Glob, error) {
+func compileCollectPatterns(args []string) (_ []glob.Glob, err error) {
if len(args) == 0 {
args = []string{"**"}
}
pat := make([]glob.Glob, len(args))
for i := range args {
- if g, err := glob.Compile(args[i], '/'); err != nil {
- return nil, fmt.Errorf("'%s': Invalid glob pattern: %w", args[i], err)
- } else { //nolint:revive
- pat[i] = g
+ if pat[i], err = glob.Compile(args[i], '/'); err != nil {
+ return nil, fmt.Errorf("invalid glob patterh %q: %w", args[i], err)
}
}
return pat, nil
diff --git a/cmd/generate.go b/cmd/generate.go
index 90b32ecaf0..cf491604ef 100644
--- a/cmd/generate.go
+++ b/cmd/generate.go
@@ -5,13 +5,14 @@
package cmd
import (
+ "context"
"fmt"
"os"
"code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var (
@@ -19,7 +20,7 @@ var (
CmdGenerate = &cli.Command{
Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
subcmdSecret,
},
}
@@ -27,7 +28,7 @@ var (
subcmdSecret = &cli.Command{
Name: "secret",
Usage: "Generate a secret token",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey,
@@ -54,7 +55,7 @@ var (
}
)
-func runGenerateInternalToken(c *cli.Context) error {
+func runGenerateInternalToken(_ context.Context, c *cli.Command) error {
internalToken, err := generate.NewInternalToken()
if err != nil {
return err
@@ -69,7 +70,7 @@ func runGenerateInternalToken(c *cli.Context) error {
return nil
}
-func runGenerateLfsJwtSecret(c *cli.Context) error {
+func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error {
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
if err != nil {
return err
@@ -84,7 +85,7 @@ func runGenerateLfsJwtSecret(c *cli.Context) error {
return nil
}
-func runGenerateSecretKey(c *cli.Context) error {
+func runGenerateSecretKey(_ context.Context, c *cli.Command) error {
secretKey, err := generate.NewSecretKey()
if err != nil {
return err
diff --git a/cmd/hook.go b/cmd/hook.go
index 41e3c3ce34..2ce272b411 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -20,11 +20,11 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
const (
- hookBatchSize = 30
+ hookBatchSize = 500
)
var (
@@ -34,7 +34,7 @@ var (
Usage: "(internal) Should only be called by Git",
Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL),
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
subcmdHookPreReceive,
subcmdHookUpdate,
subcmdHookPostReceive,
@@ -161,12 +161,10 @@ func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil
}
-func runHookPreReceive(c *cli.Context) error {
+func runHookPreReceive(ctx context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
- ctx, cancel := installSignals()
- defer cancel()
setup(ctx, c.Bool("debug"))
@@ -292,7 +290,7 @@ Gitea or set your environment appropriately.`, "")
// runHookUpdate avoid to do heavy operations on update hook because it will be
// invoked for every ref update which does not like pre-receive and post-receive
-func runHookUpdate(c *cli.Context) error {
+func runHookUpdate(_ context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
@@ -309,15 +307,12 @@ func runHookUpdate(c *cli.Context) error {
return nil
}
-func runHookPostReceive(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runHookPostReceive(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what
if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil {
- return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
+ return fmt.Errorf("failed to call 'git update-server-info': %w", err)
}
// Now if we're an internal don't do anything else
@@ -485,7 +480,7 @@ func hookPrintResult(output, isCreate bool, branch, url string) {
func pushOptions() map[string]string {
opts := make(map[string]string)
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
- for idx := 0; idx < pushCount; idx++ {
+ for idx := range pushCount {
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
kv := strings.SplitN(opt, "=", 2)
if len(kv) == 2 {
@@ -496,10 +491,7 @@ func pushOptions() map[string]string {
return opts
}
-func runHookProcReceive(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runHookProcReceive(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
@@ -740,7 +732,7 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType)
// read prefix
lengthBytes := make([]byte, 4)
- for i := 0; i < 4; i++ {
+ for i := range 4 {
lengthBytes[i], err = in.ReadByte()
if err != nil {
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)
diff --git a/cmd/keys.go b/cmd/keys.go
index 7fdbe16119..8710756a81 100644
--- a/cmd/keys.go
+++ b/cmd/keys.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"fmt"
"strings"
@@ -11,7 +12,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// CmdKeys represents the available keys sub-command
@@ -49,7 +50,7 @@ var CmdKeys = &cli.Command{
},
}
-func runKeys(c *cli.Context) error {
+func runKeys(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("No username provided")
}
@@ -68,9 +69,6 @@ func runKeys(c *cli.Context) error {
return errors.New("No key type and content provided")
}
- ctx, cancel := installSignals()
- defer cancel()
-
setup(ctx, c.Bool("debug"))
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
@@ -78,6 +76,6 @@ func runKeys(c *cli.Context) error {
if extra.Error != nil {
return extra.Error
}
- _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
+ _, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text))
return nil
}
diff --git a/cmd/mailer.go b/cmd/mailer.go
index 0c5f2c8c8d..72bd8e5601 100644
--- a/cmd/mailer.go
+++ b/cmd/mailer.go
@@ -4,24 +4,18 @@
package cmd
import (
+ "context"
"fmt"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
-func runSendMail(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runSendMail(ctx context.Context, c *cli.Command) error {
setting.MustInstalled()
- if err := argsSet(c, "title"); err != nil {
- return err
- }
-
subject := c.String("title")
confirmSkiped := c.Bool("force")
body := c.String("content")
diff --git a/cmd/main.go b/cmd/main.go
index 7251bd09a3..3b8a8a9311 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"fmt"
"os"
"strings"
@@ -11,7 +12,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// cmdHelp is our own help subcommand with more information
@@ -22,18 +23,18 @@ func cmdHelp() *cli.Command {
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
- Action: func(c *cli.Context) (err error) {
- lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
+ Action: func(ctx context.Context, c *cli.Command) (err error) {
+ lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea
targetCmdIdx := 0
- if c.Command.Name == "help" {
+ if c.Name == "help" {
targetCmdIdx = 1
}
- if lineage[targetCmdIdx+1].Command != nil {
- err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
+ if lineage[targetCmdIdx] != lineage[targetCmdIdx].Root() {
+ err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1] /* parent cmd */, lineage[targetCmdIdx].Name /* sub cmd */)
} else {
err = cli.ShowAppHelp(c)
}
- _, _ = fmt.Fprintf(c.App.Writer, `
+ _, _ = fmt.Fprintf(c.Root().Writer, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
@@ -74,25 +75,25 @@ func appGlobalFlags() []cli.Flag {
}
}
-func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
- command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
+func prepareSubcommandWithGlobalFlags(command *cli.Command) {
+ command.Flags = append(append([]cli.Flag{}, appGlobalFlags()...), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
- command.Subcommands = append(command.Subcommands, cmdHelp())
+ command.Commands = append(command.Commands, cmdHelp())
}
- for i := range command.Subcommands {
- prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
+ for i := range command.Commands {
+ prepareSubcommandWithGlobalFlags(command.Commands[i])
}
}
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
-func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
- return func(ctx *cli.Context) error {
+func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(context.Context, *cli.Command) error {
+ return func(ctx context.Context, cmd *cli.Command) error {
var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags
- for _, curCtx := range ctx.Lineage() {
+ for _, curCtx := range cmd.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
@@ -104,11 +105,11 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
}
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
- if ctx.Bool("help") || action == nil {
+ if cmd.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
- return cmdHelp().Action(ctx)
+ return cmdHelp().Action(ctx, cmd)
}
- return action(ctx)
+ return action(ctx, cmd)
}
}
@@ -117,14 +118,13 @@ type AppVersion struct {
Extra string
}
-func NewMainApp(appVer AppVersion) *cli.App {
- app := cli.NewApp()
- app.Name = "Gitea"
- app.HelpName = "gitea"
+func NewMainApp(appVer AppVersion) *cli.Command {
+ app := &cli.Command{}
+ app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]"
app.Usage = "A painless self-hosted Git service"
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
app.Version = appVer.Version + appVer.Extra
- app.EnableBashCompletion = true
+ app.EnableShellCompletion = true
// these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{
@@ -147,20 +147,21 @@ func NewMainApp(appVer AppVersion) *cli.App {
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
- CmdCert,
+ cmdCert(),
CmdGenerate,
CmdDocs,
}
+ // TODO: we should eventually drop the default command,
+ // but not sure whether it would break Windows users who used to double-click the EXE to run.
app.DefaultCommand = CmdWeb.Name
- globalFlags := appGlobalFlags()
app.Flags = append(app.Flags, cli.VersionFlag)
- app.Flags = append(app.Flags, globalFlags...)
+ app.Flags = append(app.Flags, appGlobalFlags()...)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig {
- prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
+ prepareSubcommandWithGlobalFlags(subCmdWithConfig[i])
}
app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...)
@@ -169,8 +170,10 @@ func NewMainApp(appVer AppVersion) *cli.App {
return app
}
-func RunMainApp(app *cli.App, args ...string) error {
- err := app.Run(args)
+func RunMainApp(app *cli.Command, args ...string) error {
+ ctx, cancel := installSignals()
+ defer cancel()
+ err := app.Run(ctx, args)
if err == nil {
return nil
}
diff --git a/cmd/main_test.go b/cmd/main_test.go
index 9573cacbd4..7dfa87a0ef 100644
--- a/cmd/main_test.go
+++ b/cmd/main_test.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"fmt"
"io"
@@ -16,7 +17,7 @@ import (
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
func TestMain(m *testing.M) {
@@ -27,10 +28,10 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
}
-func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
+func newTestApp(testCmdAction cli.ActionFunc) *cli.Command {
app := NewMainApp(AppVersion{})
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
- prepareSubcommandWithConfig(testCmd, appGlobalFlags())
+ prepareSubcommandWithGlobalFlags(testCmd)
app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name
return app
@@ -42,7 +43,7 @@ type runResult struct {
ExitCode int
}
-func runTestApp(app *cli.App, args ...string) (runResult, error) {
+func runTestApp(app *cli.Command, args ...string) (runResult, error) {
outBuf := new(strings.Builder)
errBuf := new(strings.Builder)
app.Writer = outBuf
@@ -65,7 +66,7 @@ func TestCliCmd(t *testing.T) {
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)"
- cli.AppHelpTemplate = "(app help template)"
+ cli.RootCommandHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct {
@@ -109,12 +110,12 @@ func TestCliCmd(t *testing.T) {
},
}
- app := newTestApp(func(ctx *cli.Context) error {
- _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
- return nil
- })
for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) {
+ app := newTestApp(func(ctx context.Context, cmd *cli.Command) error {
+ _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
+ return nil
+ })
for k, v := range c.env {
t.Setenv(k, v)
}
@@ -128,28 +129,28 @@ func TestCliCmd(t *testing.T) {
}
func TestCliCmdError(t *testing.T) {
- app := newTestApp(func(ctx *cli.Context) error { return errors.New("normal error") })
+ app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") })
r, err := runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr)
- app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) })
+ app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) })
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr)
- app = newTestApp(func(ctx *cli.Context) error { return nil })
+ app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
- assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout)
- assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
+ assert.Empty(t, r.Stdout)
+ assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
- app = newTestApp(func(ctx *cli.Context) error { return nil })
+ app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
diff --git a/cmd/manager.go b/cmd/manager.go
index bd2da8edc7..f0935ea065 100644
--- a/cmd/manager.go
+++ b/cmd/manager.go
@@ -4,12 +4,13 @@
package cmd
import (
+ "context"
"os"
"time"
"code.gitea.io/gitea/modules/private"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var (
@@ -18,7 +19,7 @@ var (
Name: "manager",
Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
subcmdShutdown,
subcmdRestart,
subcmdReloadTemplates,
@@ -108,46 +109,31 @@ var (
}
)
-func runShutdown(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runShutdown(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.Shutdown(ctx)
return handleCliResponseExtra(extra)
}
-func runRestart(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runRestart(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.Restart(ctx)
return handleCliResponseExtra(extra)
}
-func runReloadTemplates(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runReloadTemplates(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.ReloadTemplates(ctx)
return handleCliResponseExtra(extra)
}
-func runFlushQueues(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runFlushQueues(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
return handleCliResponseExtra(extra)
}
-func runProcesses(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runProcesses(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
return handleCliResponseExtra(extra)
diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go
index c2ae25ec57..ac29e7d3e5 100644
--- a/cmd/manager_logging.go
+++ b/cmd/manager_logging.go
@@ -4,6 +4,7 @@
package cmd
import (
+ "context"
"errors"
"fmt"
"os"
@@ -11,7 +12,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
var (
@@ -60,7 +61,7 @@ var (
subcmdLogging = &cli.Command{
Name: "logging",
Usage: "Adjust logging commands",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
{
Name: "pause",
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
@@ -104,7 +105,7 @@ var (
}, {
Name: "add",
Usage: "Add a logger",
- Subcommands: []*cli.Command{
+ Commands: []*cli.Command{
{
Name: "file",
Usage: "Add a file logger",
@@ -118,7 +119,6 @@ var (
Name: "rotate",
Aliases: []string{"r"},
Usage: "Rotate logs",
- Value: true,
},
&cli.Int64Flag{
Name: "max-size",
@@ -129,7 +129,6 @@ var (
Name: "daily",
Aliases: []string{"d"},
Usage: "Rotate logs daily",
- Value: true,
},
&cli.IntFlag{
Name: "max-days",
@@ -140,7 +139,6 @@ var (
Name: "compress",
Aliases: []string{"z"},
Usage: "Compress rotated logs",
- Value: true,
},
&cli.IntFlag{
Name: "compression-level",
@@ -195,10 +193,7 @@ var (
}
)
-func runRemoveLogger(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runRemoveLogger(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
logger := c.String("logger")
if len(logger) == 0 {
@@ -210,10 +205,7 @@ func runRemoveLogger(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
-func runAddConnLogger(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runAddConnLogger(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
vals := map[string]any{}
mode := "conn"
@@ -237,13 +229,10 @@ func runAddConnLogger(c *cli.Context) error {
if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
}
- return commonAddLogger(c, mode, vals)
+ return commonAddLogger(ctx, c, mode, vals)
}
-func runAddFileLogger(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runAddFileLogger(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
vals := map[string]any{}
mode := "file"
@@ -270,10 +259,10 @@ func runAddFileLogger(c *cli.Context) error {
if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level")
}
- return commonAddLogger(c, mode, vals)
+ return commonAddLogger(ctx, c, mode, vals)
}
-func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
+func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error {
if len(c.String("level")) > 0 {
vals["level"] = log.LevelFromString(c.String("level")).String()
}
@@ -300,46 +289,33 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
if c.IsSet("writer") {
writer = c.String("writer")
}
- ctx, cancel := installSignals()
- defer cancel()
extra := private.AddLogger(ctx, logger, writer, mode, vals)
return handleCliResponseExtra(extra)
}
-func runPauseLogging(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runPauseLogging(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
userMsg := private.PauseLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil
}
-func runResumeLogging(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runResumeLogging(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
userMsg := private.ResumeLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil
}
-func runReleaseReopenLogging(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
userMsg := private.ReleaseReopenLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil
}
-func runSetLogSQL(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
+func runSetLogSQL(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
extra := private.SetLogSQL(ctx, !c.Bool("off"))
diff --git a/cmd/migrate.go b/cmd/migrate.go
index 25d8b50c45..e24dc9e572 100644
--- a/cmd/migrate.go
+++ b/cmd/migrate.go
@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/versioned_migration"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// CmdMigrate represents the available migrate sub-command.
@@ -22,11 +22,8 @@ var CmdMigrate = &cli.Command{
Action: runMigrate,
}
-func runMigrate(ctx *cli.Context) error {
- stdCtx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(stdCtx); err != nil {
+func runMigrate(ctx context.Context, c *cli.Command) error {
+ if err := initDB(ctx); err != nil {
return err
}
diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go
index f9ed140395..2c63e15f50 100644
--- a/cmd/migrate_storage.go
+++ b/cmd/migrate_storage.go
@@ -22,7 +22,7 @@ import (
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/versioned_migration"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// CmdMigrateStorage represents the available migrate storage sub-command.
@@ -213,11 +213,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
})
}
-func runMigrateStorage(ctx *cli.Context) error {
- stdCtx, cancel := installSignals()
- defer cancel()
-
- if err := initDB(stdCtx); err != nil {
+func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
+ if err := initDB(ctx); err != nil {
return err
}
@@ -238,51 +235,51 @@ func runMigrateStorage(ctx *cli.Context) error {
var dstStorage storage.ObjectStorage
var err error
- switch strings.ToLower(ctx.String("storage")) {
+ switch strings.ToLower(cmd.String("storage")) {
case "":
fallthrough
case string(setting.LocalStorageType):
- p := ctx.String("path")
+ p := cmd.String("path")
if p == "" {
log.Fatal("Path must be given when storage is local")
return nil
}
dstStorage, err = storage.NewLocalStorage(
- stdCtx,
+ ctx,
&setting.Storage{
Path: p,
})
case string(setting.MinioStorageType):
dstStorage, err = storage.NewMinioStorage(
- stdCtx,
+ ctx,
&setting.Storage{
MinioConfig: setting.MinioStorageConfig{
- Endpoint: ctx.String("minio-endpoint"),
- AccessKeyID: ctx.String("minio-access-key-id"),
- SecretAccessKey: ctx.String("minio-secret-access-key"),
- Bucket: ctx.String("minio-bucket"),
- Location: ctx.String("minio-location"),
- BasePath: ctx.String("minio-base-path"),
- UseSSL: ctx.Bool("minio-use-ssl"),
- InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
- ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
- BucketLookUpType: ctx.String("minio-bucket-lookup-type"),
+ Endpoint: cmd.String("minio-endpoint"),
+ AccessKeyID: cmd.String("minio-access-key-id"),
+ SecretAccessKey: cmd.String("minio-secret-access-key"),
+ Bucket: cmd.String("minio-bucket"),
+ Location: cmd.String("minio-location"),
+ BasePath: cmd.String("minio-base-path"),
+ UseSSL: cmd.Bool("minio-use-ssl"),
+ InsecureSkipVerify: cmd.Bool("minio-insecure-skip-verify"),
+ ChecksumAlgorithm: cmd.String("minio-checksum-algorithm"),
+ BucketLookUpType: cmd.String("minio-bucket-lookup-type"),
},
})
case string(setting.AzureBlobStorageType):
dstStorage, err = storage.NewAzureBlobStorage(
- stdCtx,
+ ctx,
&setting.Storage{
AzureBlobConfig: setting.AzureBlobStorageConfig{
- Endpoint: ctx.String("azureblob-endpoint"),
- AccountName: ctx.String("azureblob-account-name"),
- AccountKey: ctx.String("azureblob-account-key"),
- Container: ctx.String("azureblob-container"),
- BasePath: ctx.String("azureblob-base-path"),
+ Endpoint: cmd.String("azureblob-endpoint"),
+ AccountName: cmd.String("azureblob-account-name"),
+ AccountKey: cmd.String("azureblob-account-key"),
+ Container: cmd.String("azureblob-container"),
+ BasePath: cmd.String("azureblob-base-path"),
},
})
default:
- return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
+ return fmt.Errorf("unsupported storage type: %s", cmd.String("storage"))
}
if err != nil {
return err
@@ -299,14 +296,14 @@ func runMigrateStorage(ctx *cli.Context) error {
"actions-artifacts": migrateActionsArtifacts,
}
- tp := strings.ToLower(ctx.String("type"))
+ tp := strings.ToLower(cmd.String("type"))
if m, ok := migratedMethods[tp]; ok {
- if err := m(stdCtx, dstStorage); err != nil {
+ if err := m(ctx, dstStorage); err != nil {
return err
}
log.Info("%s files have successfully been copied to the new storage.", tp)
return nil
}
- return fmt.Errorf("unsupported storage: %s", ctx.String("type"))
+ return fmt.Errorf("unsupported storage: %s", cmd.String("type"))
}
diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go
index 37b32aa304..c61f5a582e 100644
--- a/cmd/restore_repo.go
+++ b/cmd/restore_repo.go
@@ -4,12 +4,13 @@
package cmd
import (
+ "context"
"strings"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// CmdRestoreRepository represents the available restore a repository sub-command.
@@ -48,10 +49,7 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
},
}
-func runRestoreRepository(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runRestoreRepository(ctx context.Context, c *cli.Command) error {
setting.MustInstalled()
var units []string
if s := c.String("units"); s != "" {
diff --git a/cmd/serv.go b/cmd/serv.go
index b18508459f..8c6001e727 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -11,7 +11,6 @@ import (
"os"
"os/exec"
"path/filepath"
- "regexp"
"strconv"
"strings"
"time"
@@ -20,7 +19,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
- "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer"
@@ -34,15 +33,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote"
- "github.com/urfave/cli/v2"
-)
-
-const (
- verbUploadPack = "git-upload-pack"
- verbUploadArchive = "git-upload-archive"
- verbReceivePack = "git-receive-pack"
- verbLfsAuthenticate = "git-lfs-authenticate"
- verbLfsTransfer = "git-lfs-transfer"
+ "github.com/urfave/cli/v3"
)
// CmdServ represents the available serv sub-command.
@@ -78,22 +69,6 @@ func setup(ctx context.Context, debug bool) {
}
}
-var (
- // keep getAccessMode() in sync
- allowedCommands = container.SetOf(
- verbUploadPack,
- verbUploadArchive,
- verbReceivePack,
- verbLfsAuthenticate,
- verbLfsTransfer,
- )
- allowedCommandsLfs = container.SetOf(
- verbLfsAuthenticate,
- verbLfsTransfer,
- )
- alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
-)
-
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
// The output will be passed to git client and shown to user.
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
@@ -139,19 +114,20 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
switch verb {
- case verbUploadPack, verbUploadArchive:
+ case git.CmdVerbUploadPack, git.CmdVerbUploadArchive:
return perm.AccessModeRead
- case verbReceivePack:
+ case git.CmdVerbReceivePack:
return perm.AccessModeWrite
- case verbLfsAuthenticate, verbLfsTransfer:
+ case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer:
switch lfsVerb {
- case "upload":
+ case git.CmdSubVerbLfsUpload:
return perm.AccessModeWrite
- case "download":
+ case git.CmdSubVerbLfsDownload:
return perm.AccessModeRead
}
}
// should be unreachable
+ setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
return perm.AccessModeNone
}
@@ -176,10 +152,7 @@ func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServC
return "Bearer " + tokenString, nil
}
-func runServ(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runServ(ctx context.Context, c *cli.Command) error {
// FIXME: This needs to internationalised
setup(ctx, c.Bool("debug"))
@@ -230,41 +203,37 @@ func runServ(c *cli.Context) error {
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
}
- words, err := shellquote.Split(cmd)
+ sshCmdArgs, err := shellquote.Split(cmd)
if err != nil {
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
}
- if len(words) < 2 {
+ if len(sshCmdArgs) < 2 {
if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow
if cmd == "ssh_info" {
- fmt.Print(`{"type":"gitea","version":1}`)
+ fmt.Print(`{"type":"agit","version":1}`)
return nil
}
}
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
}
- verb := words[0]
- repoPath := strings.TrimPrefix(words[1], "/")
-
- var lfsVerb string
-
- rr := strings.SplitN(repoPath, "/", 2)
- if len(rr) != 2 {
+ repoPath := strings.TrimPrefix(sshCmdArgs[1], "/")
+ repoPathFields := strings.SplitN(repoPath, "/", 2)
+ if len(repoPathFields) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
}
- username := rr[0]
- reponame := strings.TrimSuffix(rr[1], ".git")
+ username := repoPathFields[0]
+ reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
- if alphaDashDotPattern.MatchString(reponame) {
+ if !repo.IsValidSSHAccessRepoName(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
}
@@ -286,22 +255,23 @@ func runServ(c *cli.Context) error {
}()
}
- if allowedCommands.Contains(verb) {
- if allowedCommandsLfs.Contains(verb) {
- if !setting.LFS.StartServer {
- return fail(ctx, "LFS Server is not enabled", "")
- }
- if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
- return fail(ctx, "LFS SSH transfer is not enabled", "")
- }
- if len(words) > 2 {
- lfsVerb = words[2]
- }
- }
- } else {
+ verb, lfsVerb := sshCmdArgs[0], ""
+ if !git.IsAllowedVerbForServe(verb) {
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
}
+ if git.IsAllowedVerbForServeLfs(verb) {
+ if !setting.LFS.StartServer {
+ return fail(ctx, "LFS Server is not enabled", "")
+ }
+ if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
+ return fail(ctx, "LFS SSH transfer is not enabled", "")
+ }
+ if len(sshCmdArgs) > 2 {
+ lfsVerb = sshCmdArgs[2]
+ }
+ }
+
requestedMode := getAccessMode(verb, lfsVerb)
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
@@ -310,7 +280,7 @@ func runServ(c *cli.Context) error {
}
// LFS SSH protocol
- if verb == verbLfsTransfer {
+ if verb == git.CmdVerbLfsTransfer {
token, err := getLFSAuthToken(ctx, lfsVerb, results)
if err != nil {
return err
@@ -319,7 +289,7 @@ func runServ(c *cli.Context) error {
}
// LFS token authentication
- if verb == verbLfsAuthenticate {
+ if verb == git.CmdVerbLfsAuthenticate {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
token, err := getLFSAuthToken(ctx, lfsVerb, results)
diff --git a/cmd/web.go b/cmd/web.go
index e47b171455..61ee3cbc20 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -28,7 +28,7 @@ import (
"code.gitea.io/gitea/routers/install"
"github.com/felixge/fgprof"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// PIDFile could be set from build tag
@@ -130,19 +130,19 @@ func showWebStartupMessage(msg string) {
}
}
-func serveInstall(ctx *cli.Context) error {
+func serveInstall(cmd *cli.Command) error {
showWebStartupMessage("Prepare to run install page")
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
// Flag for port number in case first time run conflict
- if ctx.IsSet("port") {
- if err := setPort(ctx.String("port")); err != nil {
+ if cmd.IsSet("port") {
+ if err := setPort(cmd.String("port")); err != nil {
return err
}
}
- if ctx.IsSet("install-port") {
- if err := setPort(ctx.String("install-port")); err != nil {
+ if cmd.IsSet("install-port") {
+ if err := setPort(cmd.String("install-port")); err != nil {
return err
}
}
@@ -163,7 +163,7 @@ func serveInstall(ctx *cli.Context) error {
return nil
}
-func serveInstalled(ctx *cli.Context) error {
+func serveInstalled(c *cli.Command) error {
setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings()
setting.MustInstalled()
@@ -218,8 +218,8 @@ func serveInstalled(ctx *cli.Context) error {
setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour)
// Override the provided port number within the configuration
- if ctx.IsSet("port") {
- if err := setPort(ctx.String("port")); err != nil {
+ if c.IsSet("port") {
+ if err := setPort(c.String("port")); err != nil {
return err
}
}
@@ -244,13 +244,17 @@ func servePprof() {
finished()
}
-func runWeb(ctx *cli.Context) error {
+func runWeb(_ context.Context, cmd *cli.Command) error {
defer func() {
if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
}
}()
+ if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid {
+ return fmt.Errorf("unknown command: %s", subCmdName)
+ }
+
managerCtx, cancel := context.WithCancel(context.Background())
graceful.InitManager(managerCtx)
defer cancel()
@@ -262,12 +266,12 @@ func runWeb(ctx *cli.Context) error {
}
// Set pid file setting
- if ctx.IsSet("pid") {
- createPIDFile(ctx.String("pid"))
+ if cmd.IsSet("pid") {
+ createPIDFile(cmd.String("pid"))
}
if !setting.InstallLock {
- if err := serveInstall(ctx); err != nil {
+ if err := serveInstall(cmd); err != nil {
return err
}
} else {
@@ -278,7 +282,7 @@ func runWeb(ctx *cli.Context) error {
go servePprof()
}
- return serveInstalled(ctx)
+ return serveInstalled(cmd)
}
func setPort(port string) error {
diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go
index 996537be3b..5e06d2c216 100644
--- a/cmd/web_graceful.go
+++ b/cmd/web_graceful.go
@@ -23,12 +23,6 @@ func NoHTTPRedirector() {
graceful.GetManager().InformCleanup()
}
-// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener
-// for our main HTTP/HTTPS service
-func NoMainListener() {
- graceful.GetManager().InformCleanup()
-}
-
// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener
// for our install HTTP/HTTPS service
func NoInstallListener() {
diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go
index 44e4eacf90..2052295fb1 100644
--- a/contrib/backport/backport.go
+++ b/contrib/backport/backport.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-//nolint:forbidigo
+//nolint:forbidigo // use of print functions is allowed in cli
package main
import (
@@ -12,21 +12,19 @@ import (
"net/http"
"os"
"os/exec"
- "os/signal"
"path"
"strconv"
"strings"
- "syscall"
"github.com/google/go-github/v71/github"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
)
const defaultVersion = "v1.18" // to backport to
func main() {
- app := cli.NewApp()
+ app := &cli.Command{}
app.Name = "backport"
app.Usage = "Backport provided PR-number on to the current or previous released version"
app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version`
@@ -91,7 +89,7 @@ func main() {
Usage: "Set this flag to continue from a git cherry-pick that has broken",
},
}
- cli.AppHelpTemplate = `NAME:
+ cli.RootCommandHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
@@ -105,16 +103,12 @@ OPTIONS:
`
app.Action = runBackport
-
- if err := app.Run(os.Args); err != nil {
+ if err := app.Run(context.Background(), os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
}
}
-func runBackport(c *cli.Context) error {
- ctx, cancel := installSignals()
- defer cancel()
-
+func runBackport(ctx context.Context, c *cli.Command) error {
continuing := c.Bool("continue")
var pr string
@@ -343,8 +337,8 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
}
- lines := strings.Split(string(out), "\n")
- for _, line := range lines {
+ lines := strings.SplitSeq(string(out), "\n")
+ for line := range lines {
fields := strings.Split(line, "\t")
name, remote := fields[0], fields[1]
// only look at pushers
@@ -362,12 +356,12 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
if !strings.Contains(remote, forkUser) {
continue
}
- if strings.HasPrefix(remote, "git@github.com:") {
- forkUser = strings.TrimPrefix(remote, "git@github.com:")
- } else if strings.HasPrefix(remote, "https://github.com/") {
- forkUser = strings.TrimPrefix(remote, "https://github.com/")
- } else if strings.HasPrefix(remote, "https://www.github.com/") {
- forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
+ if after, ok := strings.CutPrefix(remote, "git@github.com:"); ok {
+ forkUser = after
+ } else if after, ok := strings.CutPrefix(remote, "https://github.com/"); ok {
+ forkUser = after
+ } else if after, ok := strings.CutPrefix(remote, "https://www.github.com/"); ok {
+ forkUser = after
} else if forkUser == "" {
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
}
@@ -460,25 +454,3 @@ func determineSHAforPR(ctx context.Context, prStr, accessToken string) (string,
return "", nil
}
-
-func installSignals() (context.Context, context.CancelFunc) {
- ctx, cancel := context.WithCancel(context.Background())
- go func() {
- // install notify
- signalChannel := make(chan os.Signal, 1)
-
- signal.Notify(
- signalChannel,
- syscall.SIGINT,
- syscall.SIGTERM,
- )
- select {
- case <-signalChannel:
- case <-ctx.Done():
- }
- cancel()
- signal.Reset()
- }()
-
- return ctx, cancel
-}
diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go
index a7d7a6d293..5eb576c6fe 100644
--- a/contrib/environment-to-ini/environment-to-ini.go
+++ b/contrib/environment-to-ini/environment-to-ini.go
@@ -4,16 +4,17 @@
package main
import (
+ "context"
"os"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
func main() {
- app := cli.NewApp()
+ app := cli.Command{}
app.Name = "environment-to-ini"
app.Usage = "Use provided environment to update configuration ini"
app.Description = `As a helper to allow docker users to update the gitea configuration
@@ -72,13 +73,13 @@ func main() {
},
}
app.Action = runEnvironmentToIni
- err := app.Run(os.Args)
+ err := app.Run(context.Background(), os.Args)
if err != nil {
log.Fatal("Failed to run app with %s: %v", os.Args, err)
}
}
-func runEnvironmentToIni(c *cli.Context) error {
+func runEnvironmentToIni(_ context.Context, c *cli.Command) error {
// the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...)
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index a7476ad1be..aa2fcee765 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -186,17 +186,13 @@ RUN_USER = ; git
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
;;
-;; For the built-in SSH server, choose the ciphers to support for SSH connections,
-;; for system SSH this setting has no effect
-;SSH_SERVER_CIPHERS = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
-;;
-;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
-;; for system SSH this setting has no effect
-;SSH_SERVER_KEY_EXCHANGES = curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
-;;
-;; For the built-in SSH server, choose the MACs to support for SSH connections,
-;; for system SSH this setting has no effect
-;SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1
+;; For the builtin SSH server, choose the supported ciphers/key-exchange-algorithms/MACs for SSH connections.
+;; The supported names are listed in https://github.com/golang/crypto/blob/master/ssh/common.go.
+;; Leave them empty to use the Golang crypto's recommended default values.
+;; For system SSH (non-builtin SSH server), this setting has no effect.
+;SSH_SERVER_CIPHERS =
+;SSH_SERVER_KEY_EXCHANGES =
+;SSH_SERVER_MACS =
;;
;; For the built-in SSH server, choose the keypair to offer as the host key
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
@@ -1190,17 +1186,24 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
-;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
+;; GPG or SSH key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
+;; Depending on the value of SIGNING_FORMAT this is either:
+;; - openpgp: the GPG key ID
+;; - ssh: the path to the ssh public key "/path/to/key.pub": where "/path/to/key" is the private key, use ssh-keygen -t ed25519 to generate a new key pair without password
;; run in the context of the RUN_USER
;; Switch to none to stop signing completely
;SIGNING_KEY = default
;;
-;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
+;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer and the signing format.
;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to
-;; the results of git config --get user.name and git config --get user.email respectively and can only be overridden
+;; the results of git config --get user.name, git config --get user.email and git config --default openpgp --get gpg.format respectively and can only be overridden
;; by setting the SIGNING_KEY ID to the correct ID.)
;SIGNING_NAME =
;SIGNING_EMAIL =
+;; SIGNING_FORMAT can be one of:
+;; - openpgp (default): use GPG to sign commits
+;; - ssh: use SSH to sign commits
+;SIGNING_FORMAT = openpgp
;;
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
;DEFAULT_TRUST_MODEL = collaborator
@@ -1227,6 +1230,13 @@ LEVEL = Info
;; - commitssigned: require that all the commits in the head branch are signed.
;; - approved: only sign when merging an approved pr to a protected branch
;MERGES = pubkey, twofa, basesigned, commitssigned
+;;
+;; Determines which additional ssh keys are trusted for all signed commits regardless of the user
+;; This is useful for ssh signing key rotation.
+;; Exposes the provided SIGNING_NAME and SIGNING_EMAIL as the signer, regardless of the SIGNING_FORMAT value.
+;; Multiple keys should be comma separated.
+;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
+;TRUSTED_SSH_KEYS =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/flake.lock b/flake.lock
index 2f7b86359b..da3f19bbd2 100644
--- a/flake.lock
+++ b/flake.lock
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1739214665,
- "narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=",
+ "lastModified": 1747179050,
+ "narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a",
+ "rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
"type": "github"
},
"original": {
diff --git a/go.mod b/go.mod
index a43a0a3111..afe7c990e4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module code.gitea.io/gitea
-go 1.24.2
+go 1.24.4
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related:
@@ -51,7 +51,7 @@ require (
github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
- github.com/go-chi/chi/v5 v5.2.1
+ github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2
@@ -60,7 +60,6 @@ require (
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.9.2
- github.com/go-swagger/go-swagger v0.31.0
github.com/go-webauthn/webauthn v0.12.3
github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
@@ -92,7 +91,7 @@ require (
github.com/minio/minio-go/v7 v7.0.91
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.63
- github.com/niklasfasching/go-org v1.7.0
+ github.com/niklasfasching/go-org v1.8.0
github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
@@ -105,12 +104,12 @@ require (
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
- github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.12
- github.com/urfave/cli/v2 v2.27.6
+ github.com/urfave/cli-docs/v3 v3.0.0-alpha6
+ github.com/urfave/cli/v3 v3.3.3
github.com/wneessen/go-mail v0.6.2
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
@@ -118,14 +117,13 @@ require (
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.127.0
- golang.org/x/crypto v0.37.0
+ golang.org/x/crypto v0.39.0
golang.org/x/image v0.26.0
- golang.org/x/net v0.39.0
+ golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.29.0
- golang.org/x/sync v0.13.0
- golang.org/x/sys v0.32.0
- golang.org/x/text v0.24.0
- golang.org/x/tools v0.32.0
+ golang.org/x/sync v0.15.0
+ golang.org/x/sys v0.33.0
+ golang.org/x/text v0.26.0
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
gopkg.in/ini.v1 v1.67.0
@@ -143,15 +141,11 @@ require (
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
- github.com/Masterminds/goutils v1.1.1 // indirect
- github.com/Masterminds/semver/v3 v3.3.1 // indirect
- github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
- github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
@@ -186,7 +180,7 @@ require (
github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect
github.com/couchbase/goutils v0.1.2 // indirect
- github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
@@ -194,7 +188,6 @@ require (
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // indirect
- github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650 // indirect
@@ -203,18 +196,6 @@ require (
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect
- github.com/go-openapi/analysis v0.23.0 // indirect
- github.com/go-openapi/errors v0.22.1 // indirect
- github.com/go-openapi/inflect v0.21.2 // indirect
- github.com/go-openapi/jsonpointer v0.21.1 // indirect
- github.com/go-openapi/jsonreference v0.21.0 // indirect
- github.com/go-openapi/loads v0.22.0 // indirect
- github.com/go-openapi/runtime v0.28.0 // indirect
- github.com/go-openapi/spec v0.21.0 // indirect
- github.com/go-openapi/strfmt v0.23.0 // indirect
- github.com/go-openapi/swag v0.23.1 // indirect
- github.com/go-openapi/validate v0.24.0 // indirect
- github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-webauthn/x v0.1.20 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
@@ -228,7 +209,6 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.3 // indirect
github.com/gorilla/css v1.0.1 // indirect
- github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -236,12 +216,9 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
- github.com/jessevdk/go-flags v1.6.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
- github.com/kr/pretty v0.3.1 // indirect
- github.com/kr/text v0.2.0 // indirect
github.com/libdns/libdns v1.0.0-beta.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/markbates/going v1.0.3 // indirect
@@ -252,19 +229,15 @@ require (
github.com/miekg/dns v1.1.65 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
- github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
- github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
- github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
- github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -273,22 +246,11 @@ require (
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
- github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
- github.com/sagikazarmark/locafero v0.9.0 // indirect
- github.com/shopspring/decimal v1.4.0 // indirect
- github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
- github.com/sourcegraph/conc v0.3.0 // indirect
- github.com/spf13/afero v1.14.0 // indirect
- github.com/spf13/cast v1.7.1 // indirect
- github.com/spf13/pflag v1.0.6 // indirect
- github.com/spf13/viper v1.20.1 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
- github.com/subosito/gotenv v1.6.0 // indirect
- github.com/toqueteos/webbrowser v1.2.0 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
@@ -296,18 +258,17 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
- github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/assert v1.3.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
- go.mongodb.org/mongo-driver v1.17.3 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
- golang.org/x/mod v0.24.0 // indirect
+ golang.org/x/mod v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
+ golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@@ -315,9 +276,7 @@ require (
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
-replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
-
-replace github.com/nektos/act => gitea.com/gitea/act v0.261.4
+replace github.com/nektos/act => gitea.com/gitea/act v0.261.6
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
diff --git a/go.sum b/go.sum
index 9b200cc2d9..2e7c51f747 100644
--- a/go.sum
+++ b/go.sum
@@ -14,8 +14,8 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
-gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
-gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
+gitea.com/gitea/act v0.261.6 h1:CjZwKOyejonNFDmsXOw3wGm5Vet573hHM6VMLsxtvPY=
+gitea.com/gitea/act v0.261.6/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
@@ -62,12 +62,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74=
-github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
-github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
-github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
-github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
-github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
-github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@@ -103,8 +97,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
-github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=
@@ -223,8 +215,8 @@ github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9B
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
-github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
+github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
@@ -274,12 +266,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
-github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
-github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
-github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
@@ -301,8 +289,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
-github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
-github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
+github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
+github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
@@ -325,28 +313,6 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
-github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
-github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
-github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
-github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
-github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
-github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
-github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
-github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
-github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
-github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
-github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
-github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
-github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
-github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
-github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
-github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
-github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
-github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
-github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
-github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
-github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
-github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
@@ -357,13 +323,9 @@ github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkv
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
-github.com/go-swagger/go-swagger v0.31.0 h1:H8eOYQnY2u7vNKWDNykv2xJP3pBhRG/R+SOCAmKrLlc=
-github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
-github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
-github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-webauthn/webauthn v0.12.3 h1:hHQl1xkUuabUU9uS+ISNCMLs9z50p9mDUZI/FmkayNE=
github.com/go-webauthn/webauthn v0.12.3/go.mod h1:4JRe8Z3W7HIw8NGEWn2fnUwecoDzkkeach/NnvhkqGY=
github.com/go-webauthn/x v0.1.20 h1:brEBDqfiPtNNCdS/peu8gARtq8fIPsHz0VzpPjGvgiw=
@@ -446,8 +408,6 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
-github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
-github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
@@ -497,8 +457,6 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
-github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
-github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -540,8 +498,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
-github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI=
-github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
@@ -577,14 +533,10 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5JQc=
github.com/minio/minio-go/v7 v7.0.91/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
-github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
-github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
-github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -599,16 +551,14 @@ github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
-github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
+github.com/niklasfasching/go-org v1.8.0 h1:WyGLaajLLp8JbQzkmapZ1y0MOzKuKV47HkZRloi+HGY=
+github.com/niklasfasching/go-org v1.8.0/go.mod h1:e2A9zJs7cdONrEGs3gvxCcaAEpwwPNPG7csDpXckMNg=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
@@ -629,8 +579,6 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
-github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
@@ -674,7 +622,6 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -682,8 +629,6 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
-github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
@@ -692,10 +637,6 @@ github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLS
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
-github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
-github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
-github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
-github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -707,22 +648,12 @@ github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
-github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
-github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
-github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
-github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
-github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc=
@@ -745,13 +676,9 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
-github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
-github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
-github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
-github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ=
github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@@ -761,8 +688,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
-github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
+github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1qGvIytP3BI=
+github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU=
+github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
+github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
@@ -782,8 +711,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
-github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
@@ -809,8 +736,6 @@ gitlab.com/gitlab-org/api/client-go v0.127.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAj
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
-go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
-go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
@@ -835,8 +760,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
+golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
@@ -850,8 +775,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
-golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
+golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
+golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -869,8 +794,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
-golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
+golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -886,8 +811,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -920,8 +845,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -933,8 +858,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
-golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
-golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
+golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
+golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -946,8 +871,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
-golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
-golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
+golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -960,8 +885,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
-golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
-golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
+golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
+golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/main.go b/main.go
index 756c3e0f9b..2c25bac4e3 100644
--- a/main.go
+++ b/main.go
@@ -21,7 +21,7 @@ import (
_ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
// these flags will be set by the build flags
diff --git a/models/actions/run.go b/models/actions/run.go
index 5f077940c5..f0ab61b200 100644
--- a/models/actions/run.go
+++ b/models/actions/run.go
@@ -16,6 +16,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -165,12 +166,24 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
}
+func (run *ActionRun) GetWorkflowRunEventPayload() (*api.WorkflowRunPayload, error) {
+ if run.Event == webhook_module.HookEventWorkflowRun {
+ var payload api.WorkflowRunPayload
+ if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
+ return nil, err
+ }
+ return &payload, nil
+ }
+ return nil, fmt.Errorf("event %s is not a workflow run event", run.Event)
+}
+
func (run *ActionRun) IsSchedule() bool {
return run.ScheduleID > 0
}
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).
+ NoAutoTime().
SetExpr("num_action_runs",
builder.Select("count(*)").From("action_run").
Where(builder.Eq{"repo_id": repo.ID}),
@@ -342,13 +355,13 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return committer.Commit()
}
-func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
+func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) {
var run ActionRun
- has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
+ has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", runID, repoID).Get(&run)
if err != nil {
return nil, err
} else if !has {
- return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
+ return nil, fmt.Errorf("run with id %d: %w", runID, util.ErrNotExist)
}
return &run, nil
@@ -419,17 +432,10 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
if run.Status != 0 || slices.Contains(cols, "status") {
if run.RepoID == 0 {
- run, err = GetRunByID(ctx, run.ID)
- if err != nil {
- return err
- }
+ setting.PanicInDevOrTesting("RepoID should not be 0")
}
- if run.Repo == nil {
- repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
- if err != nil {
- return err
- }
- run.Repo = repo
+ if err = run.LoadRepo(ctx); err != nil {
+ return err
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err
diff --git a/models/actions/run_job.go b/models/actions/run_job.go
index d0dfd10db6..bad895036d 100644
--- a/models/actions/run_job.go
+++ b/models/actions/run_job.go
@@ -51,7 +51,7 @@ func (job *ActionRunJob) Duration() time.Duration {
func (job *ActionRunJob) LoadRun(ctx context.Context) error {
if job.Run == nil {
- run, err := GetRunByID(ctx, job.RunID)
+ run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID)
if err != nil {
return err
}
@@ -142,7 +142,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
{
// Other goroutines may aggregate the status of the run and update it too.
// So we need load the run and its jobs before updating the run.
- run, err := GetRunByID(ctx, job.RunID)
+ run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID)
if err != nil {
return 0, err
}
@@ -185,10 +185,10 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
return StatusSuccess
case hasCancelled:
return StatusCancelled
- case hasFailure:
- return StatusFailure
case hasRunning:
return StatusRunning
+ case hasFailure:
+ return StatusFailure
case hasWaiting:
return StatusWaiting
case hasBlocked:
diff --git a/models/actions/run_job_list.go b/models/actions/run_job_list.go
index 1d50c9c8dd..5f7bb62878 100644
--- a/models/actions/run_job_list.go
+++ b/models/actions/run_job_list.go
@@ -80,22 +80,31 @@ type FindRunJobOptions struct {
func (opts FindRunJobOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.RunID > 0 {
- cond = cond.And(builder.Eq{"run_id": opts.RunID})
+ cond = cond.And(builder.Eq{"`action_run_job`.run_id": opts.RunID})
}
if opts.RepoID > 0 {
- cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
- }
- if opts.OwnerID > 0 {
- cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
+ cond = cond.And(builder.Eq{"`action_run_job`.repo_id": opts.RepoID})
}
if opts.CommitSHA != "" {
- cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
+ cond = cond.And(builder.Eq{"`action_run_job`.commit_sha": opts.CommitSHA})
}
if len(opts.Statuses) > 0 {
- cond = cond.And(builder.In("status", opts.Statuses))
+ cond = cond.And(builder.In("`action_run_job`.status", opts.Statuses))
}
if opts.UpdatedBefore > 0 {
- cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
+ cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore})
}
return cond
}
+
+func (opts FindRunJobOptions) ToJoins() []db.JoinFunc {
+ if opts.OwnerID > 0 {
+ return []db.JoinFunc{
+ func(sess db.Engine) error {
+ sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
+ return nil
+ },
+ }
+ }
+ return nil
+}
diff --git a/models/actions/run_job_status_test.go b/models/actions/run_job_status_test.go
index 523d38327e..2a5eb00a6f 100644
--- a/models/actions/run_job_status_test.go
+++ b/models/actions/run_job_status_test.go
@@ -58,14 +58,14 @@ func TestAggregateJobStatus(t *testing.T) {
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
- // failure with other status, fail fast
- // Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
+ // failure with other status, usually fail fast, but "running" wins to match GitHub's behavior
+ // another reason that we can't make "failure" wins over "running": it would cause a weird behavior that user cannot cancel a workflow or get current running workflows correctly by filter after a job fail.
{[]Status{StatusFailure}, StatusFailure},
{[]Status{StatusFailure, StatusSuccess}, StatusFailure},
{[]Status{StatusFailure, StatusSkipped}, StatusFailure},
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
{[]Status{StatusFailure, StatusWaiting}, StatusFailure},
- {[]Status{StatusFailure, StatusRunning}, StatusFailure},
+ {[]Status{StatusFailure, StatusRunning}, StatusRunning},
{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
// skipped with other status
diff --git a/models/actions/run_list.go b/models/actions/run_list.go
index b9b9324e07..12c55e538e 100644
--- a/models/actions/run_list.go
+++ b/models/actions/run_list.go
@@ -72,39 +72,50 @@ type FindRunOptions struct {
TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true
Status []Status
+ CommitSHA string
}
func (opts FindRunOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.RepoID > 0 {
- cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
- }
- if opts.OwnerID > 0 {
- cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
+ cond = cond.And(builder.Eq{"`action_run`.repo_id": opts.RepoID})
}
if opts.WorkflowID != "" {
- cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowID})
+ cond = cond.And(builder.Eq{"`action_run`.workflow_id": opts.WorkflowID})
}
if opts.TriggerUserID > 0 {
- cond = cond.And(builder.Eq{"trigger_user_id": opts.TriggerUserID})
+ cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID})
}
if opts.Approved {
- cond = cond.And(builder.Gt{"approved_by": 0})
+ cond = cond.And(builder.Gt{"`action_run`.approved_by": 0})
}
if len(opts.Status) > 0 {
- cond = cond.And(builder.In("status", opts.Status))
+ cond = cond.And(builder.In("`action_run`.status", opts.Status))
}
if opts.Ref != "" {
- cond = cond.And(builder.Eq{"ref": opts.Ref})
+ cond = cond.And(builder.Eq{"`action_run`.ref": opts.Ref})
}
if opts.TriggerEvent != "" {
- cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
+ cond = cond.And(builder.Eq{"`action_run`.trigger_event": opts.TriggerEvent})
+ }
+ if opts.CommitSHA != "" {
+ cond = cond.And(builder.Eq{"`action_run`.commit_sha": opts.CommitSHA})
}
return cond
}
+func (opts FindRunOptions) ToJoins() []db.JoinFunc {
+ if opts.OwnerID > 0 {
+ return []db.JoinFunc{func(sess db.Engine) error {
+ sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
+ return nil
+ }}
+ }
+ return nil
+}
+
func (opts FindRunOptions) ToOrders() string {
- return "`id` DESC"
+ return "`action_run`.`id` DESC"
}
type StatusInfo struct {
diff --git a/models/actions/runner.go b/models/actions/runner.go
index b55723efa0..81d4249ae0 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -5,6 +5,7 @@ package actions
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -298,6 +299,23 @@ func DeleteRunner(ctx context.Context, id int64) error {
return err
}
+// DeleteEphemeralRunner deletes a ephemeral runner by given ID.
+func DeleteEphemeralRunner(ctx context.Context, id int64) error {
+ runner, err := GetRunnerByID(ctx, id)
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ return nil
+ }
+ return err
+ }
+ if !runner.Ephemeral {
+ return nil
+ }
+
+ _, err = db.DeleteByID[ActionRunner](ctx, id)
+ return err
+}
+
// CreateRunner creates new runner.
func CreateRunner(ctx context.Context, t *ActionRunner) error {
if t.OwnerID != 0 && t.RepoID != 0 {
diff --git a/models/actions/status.go b/models/actions/status.go
index eda2234137..2b1d70613c 100644
--- a/models/actions/status.go
+++ b/models/actions/status.go
@@ -4,6 +4,8 @@
package actions
import (
+ "slices"
+
"code.gitea.io/gitea/modules/translation"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
@@ -88,12 +90,7 @@ func (s Status) IsBlocked() bool {
// In returns whether s is one of the given statuses
func (s Status) In(statuses ...Status) bool {
- for _, v := range statuses {
- if s == v {
- return true
- }
- }
- return false
+ return slices.Contains(statuses, s)
}
func (s Status) AsResult() runnerv1.Result {
diff --git a/models/actions/task.go b/models/actions/task.go
index 43f11b2730..e0756b10c2 100644
--- a/models/actions/task.go
+++ b/models/actions/task.go
@@ -278,14 +278,13 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
return nil, false, err
}
- var workflowJob *jobparser.Job
- if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil {
+ parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload)
+ if err != nil {
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
- } else if len(gots) != 1 {
+ } else if len(parsedWorkflows) != 1 {
return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID)
- } else { //nolint:revive
- _, workflowJob = gots[0].Job()
}
+ _, workflowJob := parsedWorkflows[0].Job()
if _, err := e.Insert(task); err != nil {
return nil, false, err
@@ -336,6 +335,11 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
sess.Cols(cols...)
}
_, err := sess.Update(task)
+
+ // Automatically delete the ephemeral runner if the task is done
+ if err == nil && task.Status.IsDone() && util.SliceContainsString(cols, "status") {
+ return DeleteEphemeralRunner(ctx, task.RunnerID)
+ }
return err
}
diff --git a/models/actions/task_list.go b/models/actions/task_list.go
index df4b43c5ef..0c80397899 100644
--- a/models/actions/task_list.go
+++ b/models/actions/task_list.go
@@ -48,6 +48,7 @@ func (tasks TaskList) LoadAttributes(ctx context.Context) error {
type FindTaskOptions struct {
db.ListOptions
RepoID int64
+ JobID int64
OwnerID int64
CommitSHA string
Status Status
@@ -61,6 +62,9 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
+ if opts.JobID > 0 {
+ cond = cond.And(builder.Eq{"job_id": opts.JobID})
+ }
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
diff --git a/models/actions/utils.go b/models/actions/utils.go
index 12657942fc..f6ba661ae3 100644
--- a/models/actions/utils.go
+++ b/models/actions/utils.go
@@ -82,3 +82,22 @@ func calculateDuration(started, stopped timeutil.TimeStamp, status Status) time.
}
return timeSince(s).Truncate(time.Second)
}
+
+// best effort function to convert an action schedule to action run, to be used in GenerateGiteaContext
+func (s *ActionSchedule) ToActionRun() *ActionRun {
+ return &ActionRun{
+ Title: s.Title,
+ RepoID: s.RepoID,
+ Repo: s.Repo,
+ OwnerID: s.OwnerID,
+ WorkflowID: s.WorkflowID,
+ TriggerUserID: s.TriggerUserID,
+ TriggerUser: s.TriggerUser,
+ Ref: s.Ref,
+ CommitSHA: s.CommitSHA,
+ Event: s.Event,
+ EventPayload: s.EventPayload,
+ Created: s.Created,
+ Updated: s.Updated,
+ }
+}
diff --git a/models/activities/action.go b/models/activities/action.go
index c89ba3e14e..1a0dfe6412 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -9,6 +9,7 @@ import (
"fmt"
"net/url"
"path"
+ "slices"
"strconv"
"strings"
"time"
@@ -125,12 +126,7 @@ func (at ActionType) String() string {
}
func (at ActionType) InActions(actions ...string) bool {
- for _, action := range actions {
- if action == at.String() {
- return true
- }
- }
- return false
+ return slices.Contains(actions, at.String())
}
// Action represents user operation type and other information to
@@ -191,7 +187,7 @@ func (a *Action) LoadActUser(ctx context.Context) {
return
}
var err error
- a.ActUser, err = user_model.GetUserByID(ctx, a.ActUserID)
+ a.ActUser, err = user_model.GetPossibleUserByID(ctx, a.ActUserID)
if err == nil {
return
} else if user_model.IsErrUserNotExist(err) {
@@ -530,7 +526,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
if opts.RequestedTeam != nil {
env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
- teamRepoIDs, err := env.RepoIDs(ctx, 1, opts.RequestedUser.NumRepos)
+ teamRepoIDs, err := env.RepoIDs(ctx)
if err != nil {
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
}
diff --git a/models/activities/notification_list.go b/models/activities/notification_list.go
index 0cbb91df3c..b47f5dc404 100644
--- a/models/activities/notification_list.go
+++ b/models/activities/notification_list.go
@@ -208,10 +208,7 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
left := len(repoIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", repoIDs[:limit]).
Rows(new(repo_model.Repository))
@@ -282,10 +279,7 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", issueIDs[:limit]).
Rows(new(issues_model.Issue))
@@ -377,10 +371,7 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
users := make(map[int64]*user_model.User, len(userIDs))
left := len(userIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", userIDs[:limit]).
Rows(new(user_model.User))
@@ -428,10 +419,7 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
left := len(commentIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", commentIDs[:limit]).
Rows(new(issues_model.Comment))
diff --git a/models/activities/repo_activity.go b/models/activities/repo_activity.go
index 3ccdbd47d3..aeaa452c9e 100644
--- a/models/activities/repo_activity.go
+++ b/models/activities/repo_activity.go
@@ -139,10 +139,7 @@ func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository
return v[i].Commits > v[j].Commits
})
- cnt := count
- if cnt > len(v) {
- cnt = len(v)
- }
+ cnt := min(count, len(v))
return v[:cnt], nil
}
diff --git a/models/activities/statistic.go b/models/activities/statistic.go
index 983a124550..940651d359 100644
--- a/models/activities/statistic.go
+++ b/models/activities/statistic.go
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
)
// Statistic contains the database statistics
@@ -68,7 +69,7 @@ func GetStatistic(ctx context.Context) (stats Statistic) {
}
stats.Counter.UsersNotActive = user_model.CountUsers(ctx, &usersNotActiveOpts)
- stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludePrivate: true})
+ stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludeVisibility: structs.VisibleTypePrivate})
stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
stats.Counter.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{})
stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))
diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go
index 1f8f0f590e..ef67838be7 100644
--- a/models/activities/user_heatmap.go
+++ b/models/activities/user_heatmap.go
@@ -66,7 +66,7 @@ func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organi
Select(groupBy+" AS timestamp, count(user_id) as contributions").
Table("action").
Where(cond).
- And("created_unix > ?", timeutil.TimeStampNow()-31536000).
+ And("created_unix > ?", timeutil.TimeStampNow()-(366+7)*86400). // (366+7) days to include the first week for the heatmap
GroupBy(groupByName).
OrderBy("timestamp").
Find(&hdata)
diff --git a/models/asymkey/gpg_key_add.go b/models/asymkey/gpg_key_add.go
index ec2031088a..1c7d2c1da2 100644
--- a/models/asymkey/gpg_key_add.go
+++ b/models/asymkey/gpg_key_add.go
@@ -91,7 +91,7 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
}
if err != nil {
- log.Error("Unable to validate token signature. Error: %v", err)
+ log.Debug("AddGPGKey CheckArmoredDetachedSignature failed: %v", err)
return nil, ErrGPGInvalidTokenSignature{
ID: ekeys[0].PrimaryKey.KeyIdString(),
Wrapped: err,
diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go
index 6eedb5b7ba..5ab2fd8081 100644
--- a/models/asymkey/gpg_key_verify.go
+++ b/models/asymkey/gpg_key_verify.go
@@ -85,7 +85,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
}
if signer == nil {
- log.Error("Unable to validate token signature. Error: %v", err)
+ log.Debug("VerifyGPGKey failed: no signer")
return "", ErrGPGInvalidTokenSignature{
ID: key.KeyID,
}
diff --git a/models/asymkey/ssh_key_parse.go b/models/asymkey/ssh_key_parse.go
index 46dcf4d894..fc39f28624 100644
--- a/models/asymkey/ssh_key_parse.go
+++ b/models/asymkey/ssh_key_parse.go
@@ -208,7 +208,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
// The ssh library can parse the key, so next we find out what key exactly we have.
switch pkey.Type() {
- case ssh.KeyAlgoDSA:
+ case ssh.KeyAlgoDSA: //nolint:staticcheck // it's deprecated
rawPub := struct {
Name string
P, Q, G, Y *big.Int
diff --git a/models/asymkey/ssh_key_verify.go b/models/asymkey/ssh_key_verify.go
index 605ffe9096..0cf29ca9f1 100644
--- a/models/asymkey/ssh_key_verify.go
+++ b/models/asymkey/ssh_key_verify.go
@@ -35,7 +35,7 @@ func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signat
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
// see https://github.com/PowerShell/PowerShell/issues/5974
if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil {
- log.Error("Unable to validate token signature. Error: %v", err)
+ log.Debug("VerifySSHKey sshsig.Verify failed: %v", err)
return "", ErrSSHInvalidTokenSignature{
Fingerprint: key.Fingerprint,
}
diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go
index 2293fd89a0..3eae19b2a5 100644
--- a/models/auth/access_token_scope.go
+++ b/models/auth/access_token_scope.go
@@ -213,12 +213,7 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok
// ContainsCategory checks if a list of categories contains a specific category
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
- for _, c := range categories {
- if c == category {
- return true
- }
- }
- return false
+ return slices.Contains(categories, category)
}
// GetScopeLevelFromAccessMode converts permission access mode to scope level
diff --git a/models/auth/auth_token.go b/models/auth/auth_token.go
index 81f07d1a83..54ff5a0d75 100644
--- a/models/auth/auth_token.go
+++ b/models/auth/auth_token.go
@@ -15,7 +15,7 @@ import (
var ErrAuthTokenNotExist = util.NewNotExistErrorf("auth token does not exist")
-type AuthToken struct { //nolint:revive
+type AuthToken struct { //nolint:revive // export stutter
ID string `xorm:"pk"`
TokenHash string
UserID int64 `xorm:"INDEX"`
diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index c270e4856e..c2b6690116 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -12,6 +12,7 @@ import (
"fmt"
"net"
"net/url"
+ "slices"
"strings"
"code.gitea.io/gitea/models/db"
@@ -511,12 +512,7 @@ func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error {
// ScopeContains returns true if the grant scope contains the specified scope
func (grant *OAuth2Grant) ScopeContains(scope string) bool {
- for _, currentScope := range strings.Split(grant.Scope, " ") {
- if scope == currentScope {
- return true
- }
- }
- return false
+ return slices.Contains(strings.Split(grant.Scope, " "), scope)
}
// SetNonce updates the current nonce value of a grant
diff --git a/models/db/context.go b/models/db/context.go
index 4b98796ef0..ad99ada8c8 100644
--- a/models/db/context.go
+++ b/models/db/context.go
@@ -67,7 +67,7 @@ func contextSafetyCheck(e Engine) {
_ = e.SQL("SELECT 1").Iterate(&m{}, func(int, any) error {
callers := make([]uintptr, 32)
callerNum := runtime.Callers(1, callers)
- for i := 0; i < callerNum; i++ {
+ for i := range callerNum {
if funcName := runtime.FuncForPC(callers[i]).Name(); funcName == "xorm.io/xorm.(*Session).Iterate" {
contextSafetyDeniedFuncPCs = append(contextSafetyDeniedFuncPCs, callers[i])
}
@@ -82,7 +82,7 @@ func contextSafetyCheck(e Engine) {
// it should be very fast: xxxx ns/op
callers := make([]uintptr, 32)
callerNum := runtime.Callers(3, callers) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine
- for i := 0; i < callerNum; i++ {
+ for i := range callerNum {
if slices.Contains(contextSafetyDeniedFuncPCs, callers[i]) {
panic(errors.New("using database context in an iterator would cause corrupted results"))
}
@@ -178,6 +178,15 @@ func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error
return txWithNoCheck(parentCtx, f)
}
+// WithTx2 is similar to WithTx, but it has two return values: result and error.
+func WithTx2[T any](parentCtx context.Context, f func(ctx context.Context) (T, error)) (ret T, errRet error) {
+ errRet = WithTx(parentCtx, func(ctx context.Context) (errInner error) {
+ ret, errInner = f(ctx)
+ return errInner
+ })
+ return ret, errRet
+}
+
func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error {
sess := xormEngine.NewSession()
defer sess.Close()
diff --git a/models/db/name.go b/models/db/name.go
index 0e11c78372..48c7fdbce5 100644
--- a/models/db/name.go
+++ b/models/db/name.go
@@ -5,6 +5,7 @@ package db
import (
"fmt"
+ "slices"
"strings"
"unicode/utf8"
@@ -80,10 +81,8 @@ func IsUsableName(reservedNames, reservedPatterns []string, name string) error {
return util.NewInvalidArgumentErrorf("name is empty")
}
- for i := range reservedNames {
- if name == reservedNames[i] {
- return ErrNameReserved{name}
- }
+ if slices.Contains(reservedNames, name) {
+ return ErrNameReserved{name}
}
for _, pat := range reservedPatterns {
diff --git a/models/db/sql_postgres_with_schema.go b/models/db/sql_postgres_with_schema.go
index 64b61b2ef3..812fe4a6a6 100644
--- a/models/db/sql_postgres_with_schema.go
+++ b/models/db/sql_postgres_with_schema.go
@@ -39,7 +39,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
// golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here
// and in any case pq does not implement it
- if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck
+ if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck // see above
_, err := execer.Exec(`SELECT set_config(
'search_path',
$1 || ',' || current_setting('search_path'),
@@ -64,7 +64,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
// driver.String.ConvertValue will never return err for string
// golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here
- _, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck
+ _, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck // see above
if err != nil {
_ = conn.Close()
return nil, err
diff --git a/models/dbfs/dbfile.go b/models/dbfs/dbfile.go
index dd27b5c36b..eaf506fbe6 100644
--- a/models/dbfs/dbfile.go
+++ b/models/dbfs/dbfile.go
@@ -46,10 +46,7 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er
blobPos := int(offset % f.blockSize)
blobOffset := offset - int64(blobPos)
blobRemaining := int(f.blockSize) - blobPos
- needRead := len(p)
- if needRead > blobRemaining {
- needRead = blobRemaining
- }
+ needRead := min(len(p), blobRemaining)
if blobOffset+int64(blobPos)+int64(needRead) > fileMeta.FileSize {
needRead = int(fileMeta.FileSize - blobOffset - int64(blobPos))
}
@@ -66,14 +63,8 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er
blobData = nil
}
- canCopy := len(blobData) - blobPos
- if canCopy <= 0 {
- canCopy = 0
- }
- realRead := needRead
- if realRead > canCopy {
- realRead = canCopy
- }
+ canCopy := max(len(blobData)-blobPos, 0)
+ realRead := min(needRead, canCopy)
if realRead > 0 {
copy(p[:realRead], fileData.BlobData[blobPos:blobPos+realRead])
}
@@ -113,10 +104,7 @@ func (f *file) Write(p []byte) (n int, err error) {
blobPos := int(f.offset % f.blockSize)
blobOffset := f.offset - int64(blobPos)
blobRemaining := int(f.blockSize) - blobPos
- needWrite := len(p)
- if needWrite > blobRemaining {
- needWrite = blobRemaining
- }
+ needWrite := min(len(p), blobRemaining)
buf := make([]byte, f.blockSize)
readBytes, err := f.readAt(fileMeta, blobOffset, buf)
if err != nil && !errors.Is(err, io.EOF) {
diff --git a/models/fixtures/action_artifact.yml b/models/fixtures/action_artifact.yml
index 1b00daf198..ee8ef0d5ce 100644
--- a/models/fixtures/action_artifact.yml
+++ b/models/fixtures/action_artifact.yml
@@ -105,3 +105,39 @@
created_unix: 1730330775
updated_unix: 1730330775
expired_unix: 1738106775
+
+-
+ id: 24
+ run_id: 795
+ runner_id: 1
+ repo_id: 2
+ owner_id: 2
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ storage_path: "27/5/1730330775594233150.chunk"
+ file_size: 1024
+ file_compressed_size: 1024
+ content_encoding: "application/zip"
+ artifact_path: "artifact-795-1.zip"
+ artifact_name: "artifact-795-1"
+ status: 2
+ created_unix: 1730330775
+ updated_unix: 1730330775
+ expired_unix: 1738106775
+
+-
+ id: 25
+ run_id: 795
+ runner_id: 1
+ repo_id: 2
+ owner_id: 2
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ storage_path: "27/5/1730330775594233150.chunk"
+ file_size: 1024
+ file_compressed_size: 1024
+ content_encoding: "application/zip"
+ artifact_path: "artifact-795-2.zip"
+ artifact_name: "artifact-795-2"
+ status: 2
+ created_unix: 1730330775
+ updated_unix: 1730330775
+ expired_unix: 1738106775
diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml
index 1db849352f..09dfa6cccb 100644
--- a/models/fixtures/action_run.yml
+++ b/models/fixtures/action_run.yml
@@ -9,6 +9,7 @@
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
+ trigger_event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
@@ -28,6 +29,7 @@
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
+ trigger_event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
@@ -47,8 +49,9 @@
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
+ trigger_event: "push"
is_fork_pull_request: 0
- status: 1
+ status: 6 # running
started: 1683636528
stopped: 1683636626
created: 1683636108
@@ -66,6 +69,47 @@
ref: "refs/heads/test"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
+ trigger_event: "push"
+ is_fork_pull_request: 0
+ status: 1
+ started: 1683636528
+ stopped: 1683636626
+ created: 1683636108
+ updated: 1683636626
+ need_approval: 0
+ approved_by: 0
+-
+ id: 802
+ title: "workflow run list"
+ repo_id: 5
+ owner_id: 3
+ workflow_id: "test.yaml"
+ index: 191
+ trigger_user_id: 1
+ ref: "refs/heads/test"
+ commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
+ event: "push"
+ trigger_event: "push"
+ is_fork_pull_request: 0
+ status: 1
+ started: 1683636528
+ stopped: 1683636626
+ created: 1683636108
+ updated: 1683636626
+ need_approval: 0
+ approved_by: 0
+-
+ id: 803
+ title: "workflow run list for user"
+ repo_id: 2
+ owner_id: 0
+ workflow_id: "test.yaml"
+ index: 192
+ trigger_user_id: 1
+ ref: "refs/heads/test"
+ commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
+ event: "push"
+ trigger_event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
@@ -74,3 +118,24 @@
updated: 1683636626
need_approval: 0
approved_by: 0
+
+-
+ id: 795
+ title: "to be deleted (test)"
+ repo_id: 2
+ owner_id: 2
+ workflow_id: "test.yaml"
+ index: 191
+ trigger_user_id: 1
+ ref: "refs/heads/test"
+ commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
+ event: "push"
+ trigger_event: "push"
+ is_fork_pull_request: 0
+ status: 2
+ started: 1683636528
+ stopped: 1683636626
+ created: 1683636108
+ updated: 1683636626
+ need_approval: 0
+ approved_by: 0
diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml
index 8837e6ec2d..6c06d94aa4 100644
--- a/models/fixtures/action_run_job.yml
+++ b/models/fixtures/action_run_job.yml
@@ -69,3 +69,63 @@
status: 5
started: 1683636528
stopped: 1683636626
+
+-
+ id: 198
+ run_id: 795
+ repo_id: 2
+ owner_id: 2
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ name: job_1
+ attempt: 1
+ job_id: job_1
+ task_id: 53
+ status: 1
+ started: 1683636528
+ stopped: 1683636626
+
+-
+ id: 199
+ run_id: 795
+ repo_id: 2
+ owner_id: 2
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ name: job_2
+ attempt: 1
+ job_id: job_2
+ task_id: 54
+ status: 2
+ started: 1683636528
+ stopped: 1683636626
+-
+ id: 203
+ run_id: 802
+ repo_id: 5
+ owner_id: 0
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ name: job2
+ attempt: 1
+ job_id: job2
+ needs: '["job1"]'
+ task_id: 51
+ status: 5
+ started: 1683636528
+ stopped: 1683636626
+-
+ id: 204
+ run_id: 803
+ repo_id: 2
+ owner_id: 0
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ name: job2
+ attempt: 1
+ job_id: job2
+ needs: '["job1"]'
+ task_id: 51
+ status: 5
+ started: 1683636528
+ stopped: 1683636626
diff --git a/models/fixtures/action_runner.yml b/models/fixtures/action_runner.yml
index dce2d41cfb..ecb7214006 100644
--- a/models/fixtures/action_runner.yml
+++ b/models/fixtures/action_runner.yml
@@ -38,3 +38,14 @@
repo_id: 0
description: "This runner is going to be deleted"
agent_labels: '["runner_to_be_deleted","linux"]'
+-
+ id: 34350
+ name: runner_to_be_deleted-org-ephemeral
+ uuid: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
+ token_hash: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
+ ephemeral: true
+ version: "1.0.0"
+ owner_id: 3
+ repo_id: 0
+ description: "This runner is going to be deleted"
+ agent_labels: '["runner_to_be_deleted","linux"]'
diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml
index 506a47d8a0..c79fb07050 100644
--- a/models/fixtures/action_task.yml
+++ b/models/fixtures/action_task.yml
@@ -117,3 +117,63 @@
log_length: 707
log_size: 90179
log_expired: 0
+-
+ id: 52
+ job_id: 196
+ attempt: 1
+ runner_id: 34350
+ status: 6 # running
+ started: 1683636528
+ stopped: 1683636626
+ repo_id: 4
+ owner_id: 1
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ token_hash: f8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222
+ token_salt: ffffffffff
+ token_last_eight: ffffffff
+ log_filename: artifact-test2/2f/47.log
+ log_in_storage: 1
+ log_length: 707
+ log_size: 90179
+ log_expired: 0
+-
+ id: 53
+ job_id: 198
+ attempt: 1
+ runner_id: 1
+ status: 1
+ started: 1683636528
+ stopped: 1683636626
+ repo_id: 2
+ owner_id: 2
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784223
+ token_salt: ffffffffff
+ token_last_eight: ffffffff
+ log_filename: artifact-test2/2f/47.log
+ log_in_storage: 1
+ log_length: 0
+ log_size: 0
+ log_expired: 0
+-
+ id: 54
+ job_id: 199
+ attempt: 1
+ runner_id: 1
+ status: 2
+ started: 1683636528
+ stopped: 1683636626
+ repo_id: 2
+ owner_id: 2
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ is_fork_pull_request: 0
+ token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784224
+ token_salt: ffffffffff
+ token_last_eight: ffffffff
+ log_filename: artifact-test2/2f/47.log
+ log_in_storage: 1
+ log_length: 0
+ log_size: 0
+ log_expired: 0
diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml
index 6536e1dda7..03e21d04b4 100644
--- a/models/fixtures/branch.yml
+++ b/models/fixtures/branch.yml
@@ -201,3 +201,15 @@
is_deleted: false
deleted_by_id: 0
deleted_unix: 0
+
+-
+ id: 25
+ repo_id: 54
+ name: 'master'
+ commit_id: '73cf03db6ece34e12bf91e8853dc58f678f2f82d'
+ commit_message: 'Initial commit'
+ commit_time: 1671663402
+ pusher_id: 2
+ is_deleted: false
+ deleted_by_id: 0
+ deleted_unix: 0
diff --git a/models/fixtures/commit_status.yml b/models/fixtures/commit_status.yml
index 20d57975ef..87c652e53a 100644
--- a/models/fixtures/commit_status.yml
+++ b/models/fixtures/commit_status.yml
@@ -7,6 +7,7 @@
target_url: https://example.com/builds/
description: My awesome CI-service
context: ci/awesomeness
+ context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
creator_id: 2
-
@@ -18,6 +19,7 @@
target_url: https://example.com/converage/
description: My awesome Coverage service
context: cov/awesomeness
+ context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
creator_id: 2
-
@@ -29,6 +31,7 @@
target_url: https://example.com/converage/
description: My awesome Coverage service
context: cov/awesomeness
+ context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
creator_id: 2
-
@@ -40,6 +43,7 @@
target_url: https://example.com/builds/
description: My awesome CI-service
context: ci/awesomeness
+ context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
creator_id: 2
-
@@ -51,4 +55,5 @@
target_url: https://example.com/builds/
description: My awesome deploy service
context: deploy/awesomeness
+ context_hash: ae9547713a6665fc4261d0756904932085a41cf2
creator_id: 2
diff --git a/models/fixtures/email_address.yml b/models/fixtures/email_address.yml
index b2a0432635..0f6bd9ee6d 100644
--- a/models/fixtures/email_address.yml
+++ b/models/fixtures/email_address.yml
@@ -81,7 +81,7 @@
-
id: 11
uid: 4
- email: user4@example.com
+ email: User4@Example.Com
lower_email: user4@example.com
is_activated: true
is_primary: true
diff --git a/models/fixtures/hook_task.yml b/models/fixtures/hook_task.yml
index d573406b36..6023719b1e 100644
--- a/models/fixtures/hook_task.yml
+++ b/models/fixtures/hook_task.yml
@@ -18,7 +18,7 @@
id: 2
hook_id: 1
uuid: uuid2
- is_delivered: false
+ is_delivered: true
-
id: 3
diff --git a/models/git/branch.go b/models/git/branch.go
index beeb7c0689..07c94a8ba5 100644
--- a/models/git/branch.go
+++ b/models/git/branch.go
@@ -487,7 +487,7 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
ForkFrom: opts.BaseRepo.ID,
Archived: optional.Some(false),
}
- repoCond := repo_model.SearchRepositoryCondition(&repoOpts).And(repo_model.AccessibleRepositoryCondition(doer, unit.TypeCode))
+ repoCond := repo_model.SearchRepositoryCondition(repoOpts).And(repo_model.AccessibleRepositoryCondition(doer, unit.TypeCode))
if opts.Repo.ID == opts.BaseRepo.ID {
// should also include the base repo's branches
repoCond = repoCond.Or(builder.Eq{"id": opts.BaseRepo.ID})
diff --git a/models/git/commit_status.go b/models/git/commit_status.go
index b978476c4b..f85e1b15e5 100644
--- a/models/git/commit_status.go
+++ b/models/git/commit_status.go
@@ -17,10 +17,10 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
@@ -30,17 +30,17 @@ import (
// CommitStatus holds a single Status of a single Commit
type CommitStatus struct {
- ID int64 `xorm:"pk autoincr"`
- Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
- RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
- Repo *repo_model.Repository `xorm:"-"`
- State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
- SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
- TargetURL string `xorm:"TEXT"`
- Description string `xorm:"TEXT"`
- ContextHash string `xorm:"VARCHAR(64) index"`
- Context string `xorm:"TEXT"`
- Creator *user_model.User `xorm:"-"`
+ ID int64 `xorm:"pk autoincr"`
+ Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
+ RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
+ Repo *repo_model.Repository `xorm:"-"`
+ State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
+ SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
+ TargetURL string `xorm:"TEXT"`
+ Description string `xorm:"TEXT"`
+ ContextHash string `xorm:"VARCHAR(64) index"`
+ Context string `xorm:"TEXT"`
+ Creator *user_model.User `xorm:"-"`
CreatorID int64
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
@@ -230,22 +230,25 @@ func (status *CommitStatus) HideActionsURL(ctx context.Context) {
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus {
- var lastStatus *CommitStatus
- state := api.CommitStatusSuccess
+ if len(statuses) == 0 {
+ return nil
+ }
+
+ states := make(commitstatus.CommitStatusStates, 0, len(statuses))
+ targetURL := ""
for _, status := range statuses {
- if status.State.NoBetterThan(state) {
- state = status.State
- lastStatus = status
+ states = append(states, status.State)
+ if status.TargetURL != "" {
+ targetURL = status.TargetURL
}
}
- if lastStatus == nil {
- if len(statuses) > 0 {
- lastStatus = statuses[0]
- } else {
- lastStatus = &CommitStatus{}
- }
+
+ return &CommitStatus{
+ RepoID: statuses[0].RepoID,
+ SHA: statuses[0].SHA,
+ State: states.Combine(),
+ TargetURL: targetURL,
}
- return lastStatus
}
// CommitStatusOptions holds the options for query commit statuses
@@ -298,27 +301,37 @@ type CommitStatusIndex struct {
MaxIndex int64 `xorm:"index"`
}
+func makeRepoCommitQuery(ctx context.Context, repoID int64, sha string) *xorm.Session {
+ return db.GetEngine(ctx).Table(&CommitStatus{}).
+ Where("repo_id = ?", repoID).And("sha = ?", sha)
+}
+
// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
-func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) {
- getBase := func() *xorm.Session {
- return db.GetEngine(ctx).Table(&CommitStatus{}).
- Where("repo_id = ?", repoID).And("sha = ?", sha)
- }
+func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, error) {
indices := make([]int64, 0, 10)
- sess := getBase().Select("max( `index` ) as `index`").
- GroupBy("context_hash").OrderBy("max( `index` ) desc")
+ sess := makeRepoCommitQuery(ctx, repoID, sha).
+ Select("max( `index` ) as `index`").
+ GroupBy("context_hash").
+ OrderBy("max( `index` ) desc")
if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions)
}
- count, err := sess.FindAndCount(&indices)
- if err != nil {
- return nil, count, err
+ if err := sess.Find(&indices); err != nil {
+ return nil, err
}
statuses := make([]*CommitStatus, 0, len(indices))
if len(indices) == 0 {
- return statuses, count, nil
+ return statuses, nil
}
- return statuses, count, getBase().And(builder.In("`index`", indices)).Find(&statuses)
+ err := makeRepoCommitQuery(ctx, repoID, sha).And(builder.In("`index`", indices)).Find(&statuses)
+ return statuses, err
+}
+
+func CountLatestCommitStatus(ctx context.Context, repoID int64, sha string) (int64, error) {
+ return makeRepoCommitQuery(ctx, repoID, sha).
+ Select("count(context_hash)").
+ GroupBy("context_hash").
+ Count()
}
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
diff --git a/models/git/commit_status_summary.go b/models/git/commit_status_summary.go
index 7603e7aa65..dd416fa015 100644
--- a/models/git/commit_status_summary.go
+++ b/models/git/commit_status_summary.go
@@ -7,19 +7,19 @@ import (
"context"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
"xorm.io/builder"
)
// CommitStatusSummary holds the latest commit Status of a single Commit
type CommitStatusSummary struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
- SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
- State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
- TargetURL string `xorm:"TEXT"`
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
+ SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
+ State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
+ TargetURL string `xorm:"TEXT"`
}
func init() {
@@ -55,11 +55,15 @@ func GetLatestCommitStatusForRepoAndSHAs(ctx context.Context, repoSHAs []RepoSHA
}
func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) error {
- commitStatuses, _, err := GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll)
+ commitStatuses, err := GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll)
if err != nil {
return err
}
- state := CalcCommitStatus(commitStatuses)
+ // it guarantees that commitStatuses is not empty because this function is always called after a commit status is created
+ if len(commitStatuses) == 0 {
+ setting.PanicInDevOrTesting("no commit statuses found for repo %d and sha %s", repoID, sha)
+ }
+ state := CalcCommitStatus(commitStatuses) // non-empty commitStatuses is guaranteed
// mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database,
// so we need to use insert in on duplicate
if setting.Database.Type.IsMySQL() {
diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go
index 37d785e938..4c0f5e891b 100644
--- a/models/git/commit_status_test.go
+++ b/models/git/commit_status_test.go
@@ -14,9 +14,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
- "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
@@ -26,7 +26,7 @@ func TestGetCommitStatuses(t *testing.T) {
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- sha1 := "1234123412341234123412341234123412341234"
+ sha1 := "1234123412341234123412341234123412341234" // the mocked commit ID in test fixtures
statuses, maxResults, err := db.FindAndCount[git_model.CommitStatus](db.DefaultContext, &git_model.CommitStatusOptions{
ListOptions: db.ListOptions{Page: 1, PageSize: 50},
@@ -38,23 +38,23 @@ func TestGetCommitStatuses(t *testing.T) {
assert.Len(t, statuses, 5)
assert.Equal(t, "ci/awesomeness", statuses[0].Context)
- assert.Equal(t, structs.CommitStatusPending, statuses[0].State)
+ assert.Equal(t, commitstatus.CommitStatusPending, statuses[0].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[0].APIURL(db.DefaultContext))
assert.Equal(t, "cov/awesomeness", statuses[1].Context)
- assert.Equal(t, structs.CommitStatusWarning, statuses[1].State)
+ assert.Equal(t, commitstatus.CommitStatusWarning, statuses[1].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[1].APIURL(db.DefaultContext))
assert.Equal(t, "cov/awesomeness", statuses[2].Context)
- assert.Equal(t, structs.CommitStatusSuccess, statuses[2].State)
+ assert.Equal(t, commitstatus.CommitStatusSuccess, statuses[2].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[2].APIURL(db.DefaultContext))
assert.Equal(t, "ci/awesomeness", statuses[3].Context)
- assert.Equal(t, structs.CommitStatusFailure, statuses[3].State)
+ assert.Equal(t, commitstatus.CommitStatusFailure, statuses[3].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[3].APIURL(db.DefaultContext))
assert.Equal(t, "deploy/awesomeness", statuses[4].Context)
- assert.Equal(t, structs.CommitStatusError, statuses[4].State)
+ assert.Equal(t, commitstatus.CommitStatusError, statuses[4].State)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL(db.DefaultContext))
statuses, maxResults, err = db.FindAndCount[git_model.CommitStatus](db.DefaultContext, &git_model.CommitStatusOptions{
@@ -75,110 +75,110 @@ func Test_CalcCommitStatus(t *testing.T) {
{
statuses: []*git_model.CommitStatus{
{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
},
expected: &git_model.CommitStatus{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
},
{
statuses: []*git_model.CommitStatus{
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
},
expected: &git_model.CommitStatus{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
},
{
statuses: []*git_model.CommitStatus{
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
},
expected: &git_model.CommitStatus{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
},
{
statuses: []*git_model.CommitStatus{
{
- State: structs.CommitStatusError,
+ State: commitstatus.CommitStatusError,
},
{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
},
expected: &git_model.CommitStatus{
- State: structs.CommitStatusError,
+ State: commitstatus.CommitStatusFailure,
},
},
{
statuses: []*git_model.CommitStatus{
{
- State: structs.CommitStatusWarning,
+ State: commitstatus.CommitStatusWarning,
},
{
- State: structs.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
},
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
},
expected: &git_model.CommitStatus{
- State: structs.CommitStatusWarning,
+ State: commitstatus.CommitStatusPending,
},
},
{
statuses: []*git_model.CommitStatus{
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
},
expected: &git_model.CommitStatus{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
},
},
{
statuses: []*git_model.CommitStatus{
{
- State: structs.CommitStatusFailure,
+ State: commitstatus.CommitStatusFailure,
},
{
- State: structs.CommitStatusError,
+ State: commitstatus.CommitStatusError,
},
{
- State: structs.CommitStatusWarning,
+ State: commitstatus.CommitStatusWarning,
},
},
expected: &git_model.CommitStatus{
- State: structs.CommitStatusError,
+ State: commitstatus.CommitStatusFailure,
},
},
}
for _, kase := range kases {
- assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses))
+ assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses), "statuses: %v", kase.statuses)
}
}
@@ -208,7 +208,7 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) {
Creator: user2,
SHA: commit.ID,
CommitStatus: &git_model.CommitStatus{
- State: structs.CommitStatusFailure,
+ State: commitstatus.CommitStatusFailure,
TargetURL: "https://example.com/tests/",
Context: "compliance/lint-backend",
},
@@ -220,7 +220,7 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) {
Creator: user2,
SHA: commit.ID,
CommitStatus: &git_model.CommitStatus{
- State: structs.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: "https://example.com/tests/",
Context: "compliance/lint-backend",
},
@@ -256,3 +256,26 @@ func TestCommitStatusesHideActionsURL(t *testing.T) {
assert.Empty(t, statuses[0].TargetURL)
assert.Equal(t, "https://mycicd.org/1", statuses[1].TargetURL)
}
+
+func TestGetCountLatestCommitStatus(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+
+ sha1 := "1234123412341234123412341234123412341234" // the mocked commit ID in test fixtures
+
+ commitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo1.ID, sha1, db.ListOptions{
+ Page: 1,
+ PageSize: 2,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, commitStatuses, 2)
+ assert.Equal(t, commitstatus.CommitStatusFailure, commitStatuses[0].State)
+ assert.Equal(t, "ci/awesomeness", commitStatuses[0].Context)
+ assert.Equal(t, commitstatus.CommitStatusError, commitStatuses[1].State)
+ assert.Equal(t, "deploy/awesomeness", commitStatuses[1].Context)
+
+ count, err := git_model.CountLatestCommitStatus(db.DefaultContext, repo1.ID, sha1)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 3, count)
+}
diff --git a/models/git/lfs.go b/models/git/lfs.go
index bb6361050a..e4fa2b446a 100644
--- a/models/git/lfs.go
+++ b/models/git/lfs.go
@@ -112,7 +112,6 @@ type LFSMetaObject struct {
ID int64 `xorm:"pk autoincr"`
lfs.Pointer `xorm:"extends"`
RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
- Existing bool `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
@@ -146,7 +145,6 @@ func NewLFSMetaObject(ctx context.Context, repoID int64, p lfs.Pointer) (*LFSMet
if err != nil {
return nil, err
} else if exist {
- m.Existing = true
return m, committer.Commit()
}
diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go
index a3caed73c4..55bbe6938c 100644
--- a/models/git/protected_branch.go
+++ b/models/git/protected_branch.go
@@ -246,7 +246,7 @@ func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob {
func getFilePatterns(filePatterns string) []glob.Glob {
extarr := make([]glob.Glob, 0, 10)
- for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") {
+ for expr := range strings.SplitSeq(strings.ToLower(filePatterns), ";") {
expr = strings.TrimSpace(expr)
if expr != "" {
if g, err := glob.Compile(expr, '.', '/'); err != nil {
@@ -518,7 +518,7 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre
return currentWhitelist, nil
}
- teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
+ teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
if err != nil {
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
}
diff --git a/models/issues/comment.go b/models/issues/comment.go
index ab9b2042f3..4fdb0c1808 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -9,6 +9,7 @@ import (
"context"
"fmt"
"html/template"
+ "slices"
"strconv"
"unicode/utf8"
@@ -196,12 +197,7 @@ func (t CommentType) HasMailReplySupport() bool {
}
func (t CommentType) CountedAsConversation() bool {
- for _, ct := range ConversationCountedCommentType() {
- if t == ct {
- return true
- }
- }
- return false
+ return slices.Contains(ConversationCountedCommentType(), t)
}
// ConversationCountedCommentType returns the comment types that are counted as a conversation
@@ -614,7 +610,7 @@ func UpdateCommentAttachments(ctx context.Context, c *Comment, uuids []string) e
if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
}
- for i := 0; i < len(attachments); i++ {
+ for i := range attachments {
attachments[i].IssueID = c.IssueID
attachments[i].CommentID = c.ID
if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
@@ -719,7 +715,8 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
return nil
}
-func (c *Comment) loadReview(ctx context.Context) (err error) {
+// LoadReview loads the associated review
+func (c *Comment) LoadReview(ctx context.Context) (err error) {
if c.ReviewID == 0 {
return nil
}
@@ -736,11 +733,6 @@ func (c *Comment) loadReview(ctx context.Context) (err error) {
return nil
}
-// LoadReview loads the associated review
-func (c *Comment) LoadReview(ctx context.Context) error {
- return c.loadReview(ctx)
-}
-
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
func (c *Comment) DiffSide() string {
if c.Line < 0 {
@@ -860,7 +852,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
}
if comment.ReviewID != 0 {
if comment.Review == nil {
- if err := comment.loadReview(ctx); err != nil {
+ if err := comment.LoadReview(ctx); err != nil {
return err
}
}
diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go
index b562aab500..55e67a1243 100644
--- a/models/issues/comment_code.go
+++ b/models/issues/comment_code.go
@@ -5,6 +5,7 @@ package issues
import (
"context"
+ "strconv"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/renderhelper"
@@ -114,7 +115,9 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
}
var err error
- rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{
+ FootnoteContextID: strconv.FormatInt(comment.ID, 10),
+ })
if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil {
return nil, err
}
diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go
index c483ada75a..f6c485449f 100644
--- a/models/issues/comment_list.go
+++ b/models/issues/comment_list.go
@@ -57,10 +57,7 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
commentLabels := make(map[int64]*Label, len(labelIDs))
left := len(labelIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", labelIDs[:limit]).
Rows(new(Label))
@@ -107,10 +104,7 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestoneMaps)
@@ -146,10 +140,7 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestoneMaps)
@@ -184,10 +175,7 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
assignees := make(map[int64]*user_model.User, len(assigneeIDs))
left := len(assigneeIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", assigneeIDs[:limit]).
Rows(new(user_model.User))
@@ -256,10 +244,7 @@ func (comments CommentList) LoadIssues(ctx context.Context) error {
issues := make(map[int64]*Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", issueIDs[:limit]).
Rows(new(Issue))
@@ -313,10 +298,7 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
issues := make(map[int64]*Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := e.
In("id", issueIDs[:limit]).
Rows(new(Issue))
@@ -392,10 +374,7 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
commentsIDs := comments.getAttachmentCommentIDs()
left := len(commentsIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("comment_id", commentsIDs[:limit]).
Rows(new(repo_model.Attachment))
diff --git a/models/issues/issue_label.go b/models/issues/issue_label.go
index 10fc821454..f082079e07 100644
--- a/models/issues/issue_label.go
+++ b/models/issues/issue_label.go
@@ -206,6 +206,7 @@ func DeleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *use
}
issue.Labels = nil
+ issue.isLabelsLoaded = false
return issue.LoadLabels(ctx)
}
diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go
index 6c74b533b3..26b93189b8 100644
--- a/models/issues/issue_list.go
+++ b/models/issues/issue_list.go
@@ -42,10 +42,7 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi
repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
left := len(repoIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", repoIDs[:limit]).
Find(&repoMaps)
@@ -116,10 +113,7 @@ func (issues IssueList) LoadLabels(ctx context.Context) error {
issueIDs := issues.getIssueIDs()
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("label").
Join("LEFT", "issue_label", "issue_label.label_id = label.id").
In("issue_label.issue_id", issueIDs[:limit]).
@@ -171,10 +165,7 @@ func (issues IssueList) LoadMilestones(ctx context.Context) error {
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestoneMaps)
@@ -203,10 +194,7 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
}
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
projects := make([]*projectWithIssueID, 0, limit)
err := db.GetEngine(ctx).
@@ -245,10 +233,7 @@ func (issues IssueList) LoadAssignees(ctx context.Context) error {
issueIDs := issues.getIssueIDs()
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("issue_assignees").
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issueIDs[:limit]).OrderBy(user_model.GetOrderByName()).
@@ -306,10 +291,7 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error {
pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
left := len(issuesIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("issue_id", issuesIDs[:limit]).
Rows(new(PullRequest))
@@ -354,10 +336,7 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) {
issuesIDs := issues.getIssueIDs()
left := len(issuesIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("issue_id", issuesIDs[:limit]).
Rows(new(repo_model.Attachment))
@@ -399,10 +378,7 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er
issuesIDs := issues.getIssueIDs()
left := len(issuesIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("comment").
Join("INNER", "issue", "issue.id = comment.issue_id").
In("issue.id", issuesIDs[:limit]).
@@ -466,10 +442,7 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
left := len(ids)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
// select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
rows, err := db.GetEngine(ctx).Table("tracked_time").
diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go
index f9e1fbeb14..79bd6a19b0 100644
--- a/models/issues/issue_search.go
+++ b/models/issues/issue_search.go
@@ -24,7 +24,7 @@ import (
const ScopeSortPrefix = "scope-"
// IssuesOptions represents options of an issue.
-type IssuesOptions struct { //nolint
+type IssuesOptions struct { //nolint:revive // export stutter
Paginator *db.ListOptions
RepoIDs []int64 // overwrites RepoCond if the length is not 0
AllPublic bool // include also all public repositories
@@ -73,8 +73,8 @@ func (o *IssuesOptions) Copy(edit ...func(options *IssuesOptions)) *IssuesOption
// sortType string
func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
// Since this sortType is dynamically created, it has to be treated specially.
- if strings.HasPrefix(sortType, ScopeSortPrefix) {
- scope := strings.TrimPrefix(sortType, ScopeSortPrefix)
+ if after, ok := strings.CutPrefix(sortType, ScopeSortPrefix); ok {
+ scope := after
sess.Join("LEFT", "issue_label", "issue.id = issue_label.issue_id")
// "exclusive_order=0" means "no order is set", so exclude it from the JOIN criteria and then "LEFT JOIN" result is also null
sess.Join("LEFT", "label", "label.id = issue_label.label_id AND label.exclusive_order <> 0 AND label.name LIKE ?", scope+"/%")
@@ -88,6 +88,8 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
sess.Asc("issue.created_unix").Asc("issue.id")
case "recentupdate":
sess.Desc("issue.updated_unix").Desc("issue.created_unix").Desc("issue.id")
+ case "recentclose":
+ sess.Desc("issue.closed_unix").Desc("issue.created_unix").Desc("issue.id")
case "leastupdate":
sess.Asc("issue.updated_unix").Asc("issue.created_unix").Asc("issue.id")
case "mostcomment":
diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go
index 50409fbbd8..adedaa3d3a 100644
--- a/models/issues/issue_stats.go
+++ b/models/issues/issue_stats.go
@@ -94,10 +94,7 @@ func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error
// ids in a temporary table and join from them.
accum := &IssueStats{}
for i := 0; i < len(opts.IssueIDs); {
- chunk := i + MaxQueryParameters
- if chunk > len(opts.IssueIDs) {
- chunk = len(opts.IssueIDs)
- }
+ chunk := min(i+MaxQueryParameters, len(opts.IssueIDs))
stats, err := getIssueStatsChunk(ctx, opts, opts.IssueIDs[i:chunk])
if err != nil {
return nil, err
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index 18571e3aaa..1c5db55bbc 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -5,6 +5,7 @@ package issues_test
import (
"fmt"
+ "slices"
"sort"
"sync"
"testing"
@@ -270,7 +271,7 @@ func TestIssue_ResolveMentions(t *testing.T) {
for i, user := range resolved {
ids[i] = user.ID
}
- sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
+ slices.Sort(ids)
assert.Equal(t, expected, ids)
}
@@ -292,7 +293,7 @@ func TestResourceIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
var wg sync.WaitGroup
- for i := 0; i < 100; i++ {
+ for i := range 100 {
wg.Add(1)
go func(i int) {
testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0)
@@ -314,7 +315,7 @@ func TestCorrectIssueStats(t *testing.T) {
issueAmount := issues_model.MaxQueryParameters + 10
var wg sync.WaitGroup
- for i := 0; i < issueAmount; i++ {
+ for i := range issueAmount {
wg.Add(1)
go func(i int) {
testInsertIssue(t, fmt.Sprintf("Issue %d", i+1), "Bugs are nasty", 0)
diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go
index 7ddf7ee901..9b99787e3b 100644
--- a/models/issues/issue_update.go
+++ b/models/issues/issue_update.go
@@ -12,9 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
- project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
- system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
@@ -306,7 +304,7 @@ func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string)
if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
}
- for i := 0; i < len(attachments); i++ {
+ for i := range attachments {
attachments[i].IssueID = issueID
if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
@@ -715,138 +713,13 @@ func UpdateReactionsMigrationsByType(ctx context.Context, gitServiceType api.Git
return err
}
-// DeleteIssuesByRepoID deletes issues by repositories id
-func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths []string, err error) {
- // MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
- // so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
- sess := db.GetEngine(ctx)
-
- for {
- issueIDs := make([]int64, 0, db.DefaultMaxInSize)
-
- err := sess.Table(&Issue{}).Where("repo_id = ?", repoID).OrderBy("id").Limit(db.DefaultMaxInSize).Cols("id").Find(&issueIDs)
- if err != nil {
- return nil, err
- }
-
- if len(issueIDs) == 0 {
- break
- }
-
- // Delete content histories
- _, err = sess.In("issue_id", issueIDs).Delete(&ContentHistory{})
- if err != nil {
- return nil, err
- }
-
- // Delete comments and attachments
- _, err = sess.In("issue_id", issueIDs).Delete(&Comment{})
- if err != nil {
- return nil, err
- }
-
- // Dependencies for issues in this repository
- _, err = sess.In("issue_id", issueIDs).Delete(&IssueDependency{})
- if err != nil {
- return nil, err
- }
-
- // Delete dependencies for issues in other repositories
- _, err = sess.In("dependency_id", issueIDs).Delete(&IssueDependency{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&Stopwatch{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&TrackedTime{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&project_model.ProjectIssue{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("dependent_issue_id", issueIDs).Delete(&Comment{})
- if err != nil {
- return nil, err
- }
-
- var attachments []*repo_model.Attachment
- err = sess.In("issue_id", issueIDs).Find(&attachments)
- if err != nil {
- return nil, err
- }
-
- for j := range attachments {
- attachmentPaths = append(attachmentPaths, attachments[j].RelativePath())
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&repo_model.Attachment{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("id", issueIDs).Delete(&Issue{})
- if err != nil {
- return nil, err
- }
- }
-
- return attachmentPaths, err
-}
-
-// DeleteOrphanedIssues delete issues without a repo
-func DeleteOrphanedIssues(ctx context.Context) error {
- var attachmentPaths []string
- err := db.WithTx(ctx, func(ctx context.Context) error {
- var ids []int64
-
- if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id").
- Join("LEFT", "repository", "issue.repo_id=repository.id").
- Where(builder.IsNull{"repository.id"}).GroupBy("issue.repo_id").
- Find(&ids); err != nil {
- return err
- }
-
- for i := range ids {
- paths, err := DeleteIssuesByRepoID(ctx, ids[i])
- if err != nil {
- return err
- }
- attachmentPaths = append(attachmentPaths, paths...)
- }
-
- return nil
- })
- if err != nil {
- return err
- }
-
- // Remove issue attachment files.
- for i := range attachmentPaths {
- // FIXME: it's not right, because the attachment might not be on local filesystem
- system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i])
+func GetOrphanedIssueRepoIDs(ctx context.Context) ([]int64, error) {
+ var repoIDs []int64
+ if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id").
+ Join("LEFT", "repository", "issue.repo_id=repository.id").
+ Where(builder.IsNull{"repository.id"}).
+ Find(&repoIDs); err != nil {
+ return nil, err
}
- return nil
+ return repoIDs, nil
}
diff --git a/models/issues/pull.go b/models/issues/pull.go
index e65b214dab..0ff32e2473 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -649,12 +649,6 @@ func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*P
return pulls, err
}
-// Update updates all fields of pull request.
-func (pr *PullRequest) Update(ctx context.Context) error {
- _, err := db.GetEngine(ctx).ID(pr.ID).AllCols().Update(pr)
- return err
-}
-
// UpdateCols updates specific fields of pull request.
func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error {
_, err := db.GetEngine(ctx).ID(pr.ID).Cols(cols...).Update(pr)
diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go
index b685175f8e..84f9f6166d 100644
--- a/models/issues/pull_list.go
+++ b/models/issues/pull_list.go
@@ -152,7 +152,8 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
applySorts(findSession, opts.SortType, 0)
findSession = db.SetSessionPagination(findSession, opts)
prs := make([]*PullRequest, 0, opts.PageSize)
- return prs, maxResults, findSession.Find(&prs)
+ found := findSession.Find(&prs)
+ return prs, maxResults, found
}
// PullRequestList defines a list of pull requests
diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go
index 8e09030215..39efaa5792 100644
--- a/models/issues/pull_test.go
+++ b/models/issues/pull_test.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPullRequest_LoadAttributes(t *testing.T) {
@@ -76,6 +77,47 @@ func TestPullRequestsNewest(t *testing.T) {
}
}
+func TestPullRequests_Closed_RecentSortType(t *testing.T) {
+ // Issue ID | Closed At. | Updated At
+ // 2 | 1707270001 | 1707270001
+ // 3 | 1707271000 | 1707279999
+ // 11 | 1707279999 | 1707275555
+ tests := []struct {
+ sortType string
+ expectedIssueIDOrder []int64
+ }{
+ {"recentupdate", []int64{3, 11, 2}},
+ {"recentclose", []int64{11, 3, 2}},
+ }
+
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ _, err := db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707270001, updated_unix = 1707270001, is_closed = true WHERE id = 2")
+ require.NoError(t, err)
+ _, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707271000, updated_unix = 1707279999, is_closed = true WHERE id = 3")
+ require.NoError(t, err)
+ _, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707279999, updated_unix = 1707275555, is_closed = true WHERE id = 11")
+ require.NoError(t, err)
+
+ for _, test := range tests {
+ t.Run(test.sortType, func(t *testing.T) {
+ prs, _, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{
+ ListOptions: db.ListOptions{
+ Page: 1,
+ },
+ State: "closed",
+ SortType: test.sortType,
+ })
+ require.NoError(t, err)
+
+ if assert.Len(t, prs, len(test.expectedIssueIDOrder)) {
+ for i := range test.expectedIssueIDOrder {
+ assert.Equal(t, test.expectedIssueIDOrder[i], prs[i].IssueID)
+ }
+ }
+ })
+ }
+}
+
func TestLoadRequestedReviewers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
@@ -206,19 +248,6 @@ func TestGetPullRequestByIssueID(t *testing.T) {
assert.True(t, issues_model.IsErrPullRequestNotExist(err))
}
-func TestPullRequest_Update(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
- pr.BaseBranch = "baseBranch"
- pr.HeadBranch = "headBranch"
- pr.Update(db.DefaultContext)
-
- pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
- assert.Equal(t, "baseBranch", pr.BaseBranch)
- assert.Equal(t, "headBranch", pr.HeadBranch)
- unittest.CheckConsistencyFor(t, pr)
-}
-
func TestPullRequest_UpdateCols(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
pr := &issues_model.PullRequest{
diff --git a/models/issues/review_list.go b/models/issues/review_list.go
index 928f24fb2d..bbb8c489fa 100644
--- a/models/issues/review_list.go
+++ b/models/issues/review_list.go
@@ -22,7 +22,7 @@ type ReviewList []*Review
// LoadReviewers loads reviewers
func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
reviewerIDs := make([]int64, len(reviews))
- for i := 0; i < len(reviews); i++ {
+ for i := range reviews {
reviewerIDs[i] = reviews[i].ReviewerID
}
reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs)
diff --git a/models/issues/stopwatch.go b/models/issues/stopwatch.go
index 7c05a3a883..761b8f91a0 100644
--- a/models/issues/stopwatch.go
+++ b/models/issues/stopwatch.go
@@ -5,7 +5,6 @@ package issues
import (
"context"
- "fmt"
"time"
"code.gitea.io/gitea/models/db"
@@ -15,20 +14,6 @@ import (
"code.gitea.io/gitea/modules/util"
)
-// ErrIssueStopwatchNotExist represents an error that stopwatch is not exist
-type ErrIssueStopwatchNotExist struct {
- UserID int64
- IssueID int64
-}
-
-func (err ErrIssueStopwatchNotExist) Error() string {
- return fmt.Sprintf("issue stopwatch doesn't exist[uid: %d, issue_id: %d", err.UserID, err.IssueID)
-}
-
-func (err ErrIssueStopwatchNotExist) Unwrap() error {
- return util.ErrNotExist
-}
-
// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
ID int64 `xorm:"pk autoincr"`
@@ -55,13 +40,11 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex
return sw, exists, err
}
-// UserIDCount is a simple coalition of UserID and Count
type UserStopwatch struct {
UserID int64
StopWatches []*Stopwatch
}
-// GetUIDsAndNotificationCounts between the two provided times
func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
sws := []*Stopwatch{}
if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil {
@@ -87,7 +70,7 @@ func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
return res, nil
}
-// GetUserStopwatches return list of all stopwatches of a user
+// GetUserStopwatches return list of the user's all stopwatches
func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
sws := make([]*Stopwatch, 0, 8)
sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID)
@@ -102,7 +85,7 @@ func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOp
return sws, nil
}
-// CountUserStopwatches return count of all stopwatches of a user
+// CountUserStopwatches return count of the user's all stopwatches
func CountUserStopwatches(ctx context.Context, userID int64) (int64, error) {
return db.GetEngine(ctx).Where("user_id = ?", userID).Count(&Stopwatch{})
}
@@ -136,43 +119,21 @@ func HasUserStopwatch(ctx context.Context, userID int64) (exists bool, sw *Stopw
return exists, sw, issue, err
}
-// FinishIssueStopwatchIfPossible if stopwatch exist then finish it otherwise ignore
-func FinishIssueStopwatchIfPossible(ctx context.Context, user *user_model.User, issue *Issue) error {
- _, exists, err := getStopwatch(ctx, user.ID, issue.ID)
- if err != nil {
- return err
- }
- if !exists {
- return nil
- }
- return FinishIssueStopwatch(ctx, user, issue)
-}
-
-// CreateOrStopIssueStopwatch create an issue stopwatch if it's not exist, otherwise finish it
-func CreateOrStopIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- _, exists, err := getStopwatch(ctx, user.ID, issue.ID)
- if err != nil {
- return err
- }
- if exists {
- return FinishIssueStopwatch(ctx, user, issue)
- }
- return CreateIssueStopwatch(ctx, user, issue)
-}
-
-// FinishIssueStopwatch if stopwatch exist then finish it otherwise return an error
-func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
+// FinishIssueStopwatch if stopwatch exists, then finish it.
+func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
if err != nil {
- return err
+ return false, err
+ } else if !exists {
+ return false, nil
}
- if !exists {
- return ErrIssueStopwatchNotExist{
- UserID: user.ID,
- IssueID: issue.ID,
- }
+ if err = finishIssueStopwatch(ctx, user, issue, sw); err != nil {
+ return false, err
}
+ return true, nil
+}
+func finishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue, sw *Stopwatch) error {
// Create tracked time out of the time difference between start date and actual date
timediff := time.Now().Unix() - int64(sw.CreatedUnix)
@@ -184,14 +145,12 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
Time: timediff,
}
- if err := db.Insert(ctx, tt); err != nil {
+ if err := issue.LoadRepo(ctx); err != nil {
return err
}
-
- if err := issue.LoadRepo(ctx); err != nil {
+ if err := db.Insert(ctx, tt); err != nil {
return err
}
-
if _, err := CreateComment(ctx, &CreateCommentOptions{
Doer: user,
Issue: issue,
@@ -202,83 +161,65 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
}); err != nil {
return err
}
- _, err = db.DeleteByBean(ctx, sw)
+ _, err := db.DeleteByBean(ctx, sw)
return err
}
-// CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error
-func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- if err := issue.LoadRepo(ctx); err != nil {
- return err
- }
-
- // if another stopwatch is running: stop it
- exists, _, otherIssue, err := HasUserStopwatch(ctx, user.ID)
- if err != nil {
- return err
- }
- if exists {
- if err := FinishIssueStopwatch(ctx, user, otherIssue); err != nil {
- return err
+// CreateIssueStopwatch creates a stopwatch if the issue doesn't have the user's stopwatch.
+// It also stops any other stopwatch that might be running for the user.
+func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
+ { // if another issue's stopwatch is running: stop it; if this issue has a stopwatch: return an error.
+ exists, otherStopWatch, otherIssue, err := HasUserStopwatch(ctx, user.ID)
+ if err != nil {
+ return false, err
+ }
+ if exists {
+ if otherStopWatch.IssueID == issue.ID {
+ // don't allow starting stopwatch for the same issue
+ return false, nil
+ }
+ // stop the other issue's stopwatch
+ if err = finishIssueStopwatch(ctx, user, otherIssue, otherStopWatch); err != nil {
+ return false, err
+ }
}
}
- // Create stopwatch
- sw := &Stopwatch{
- UserID: user.ID,
- IssueID: issue.ID,
+ if err = issue.LoadRepo(ctx); err != nil {
+ return false, err
}
-
- if err := db.Insert(ctx, sw); err != nil {
- return err
+ if err = db.Insert(ctx, &Stopwatch{UserID: user.ID, IssueID: issue.ID}); err != nil {
+ return false, err
}
-
- if err := issue.LoadRepo(ctx); err != nil {
- return err
- }
-
- if _, err := CreateComment(ctx, &CreateCommentOptions{
+ if _, err = CreateComment(ctx, &CreateCommentOptions{
Doer: user,
Issue: issue,
Repo: issue.Repo,
Type: CommentTypeStartTracking,
}); err != nil {
- return err
+ return false, err
}
-
- return nil
+ return true, nil
}
// CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
-func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- if err := cancelStopwatch(ctx, user, issue); err != nil {
- return err
- }
- return committer.Commit()
-}
-
-func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- e := db.GetEngine(ctx)
- sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
- if err != nil {
- return err
- }
-
- if exists {
- if _, err := e.Delete(sw); err != nil {
+func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
+ err = db.WithTx(ctx, func(ctx context.Context) error {
+ e := db.GetEngine(ctx)
+ sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
+ if err != nil {
return err
+ } else if !exists {
+ return nil
}
- if err := issue.LoadRepo(ctx); err != nil {
+ if err = issue.LoadRepo(ctx); err != nil {
return err
}
-
- if _, err := CreateComment(ctx, &CreateCommentOptions{
+ if _, err = e.Delete(sw); err != nil {
+ return err
+ }
+ if _, err = CreateComment(ctx, &CreateCommentOptions{
Doer: user,
Issue: issue,
Repo: issue.Repo,
@@ -286,6 +227,8 @@ func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) e
}); err != nil {
return err
}
- }
- return nil
+ ok = true
+ return nil
+ })
+ return ok, err
}
diff --git a/models/issues/stopwatch_test.go b/models/issues/stopwatch_test.go
index a1bf9dc931..6333c10234 100644
--- a/models/issues/stopwatch_test.go
+++ b/models/issues/stopwatch_test.go
@@ -10,7 +10,6 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
)
@@ -18,26 +17,22 @@ import (
func TestCancelStopwatch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- user1, err := user_model.GetUserByID(db.DefaultContext, 1)
- assert.NoError(t, err)
-
- issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1)
- assert.NoError(t, err)
- issue2, err := issues_model.GetIssueByID(db.DefaultContext, 2)
- assert.NoError(t, err)
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
- err = issues_model.CancelStopwatch(db.DefaultContext, user1, issue1)
+ ok, err := issues_model.CancelStopwatch(db.DefaultContext, user1, issue1)
assert.NoError(t, err)
+ assert.True(t, ok)
unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: user1.ID, IssueID: issue1.ID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID})
- _ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID})
-
- assert.NoError(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2))
+ ok, err = issues_model.CancelStopwatch(db.DefaultContext, user1, issue1)
+ assert.NoError(t, err)
+ assert.False(t, ok)
}
func TestStopwatchExists(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
-
assert.True(t, issues_model.StopwatchExists(db.DefaultContext, 1, 1))
assert.False(t, issues_model.StopwatchExists(db.DefaultContext, 1, 2))
}
@@ -58,21 +53,35 @@ func TestHasUserStopwatch(t *testing.T) {
func TestCreateOrStopIssueStopwatch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- user2, err := user_model.GetUserByID(db.DefaultContext, 2)
+ user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
+ issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+ issue3 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
+
+ // create a new stopwatch
+ ok, err := issues_model.CreateIssueStopwatch(db.DefaultContext, user4, issue1)
assert.NoError(t, err)
- org3, err := user_model.GetUserByID(db.DefaultContext, 3)
+ assert.True(t, ok)
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: user4.ID, IssueID: issue1.ID})
+ // should not create a second stopwatch for the same issue
+ ok, err = issues_model.CreateIssueStopwatch(db.DefaultContext, user4, issue1)
assert.NoError(t, err)
-
- issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1)
+ assert.False(t, ok)
+ // on a different issue, it will finish the existing stopwatch and create a new one
+ ok, err = issues_model.CreateIssueStopwatch(db.DefaultContext, user4, issue3)
assert.NoError(t, err)
- issue2, err := issues_model.GetIssueByID(db.DefaultContext, 2)
+ assert.True(t, ok)
+ unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: user4.ID, IssueID: issue1.ID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: user4.ID, IssueID: issue3.ID})
+
+ // user2 already has a stopwatch in test fixture
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+ ok, err = issues_model.FinishIssueStopwatch(db.DefaultContext, user2, issue2)
assert.NoError(t, err)
-
- assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(db.DefaultContext, org3, issue1))
- sw := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: 3, IssueID: 1})
- assert.LessOrEqual(t, sw.CreatedUnix, timeutil.TimeStampNow())
-
- assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(db.DefaultContext, user2, issue2))
- unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: 2, IssueID: 2})
- unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 2, IssueID: 2})
+ assert.True(t, ok)
+ unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: user2.ID, IssueID: issue2.ID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: user2.ID, IssueID: issue2.ID})
+ ok, err = issues_model.FinishIssueStopwatch(db.DefaultContext, user2, issue2)
+ assert.NoError(t, err)
+ assert.False(t, ok)
}
diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index ea404d36cd..2afbe272ed 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -350,10 +350,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
// we get the statistics in smaller chunks and get accumulates
var accum int64
for i := 0; i < len(opts.IssueIDs); {
- chunk := i + MaxQueryParameters
- if chunk > len(opts.IssueIDs) {
- chunk = len(opts.IssueIDs)
- }
+ chunk := min(i+MaxQueryParameters, len(opts.IssueIDs))
time, err := getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs[i:chunk])
if err != nil {
return 0, err
diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go
index 4ecc930f10..479a46379c 100644
--- a/models/migrations/base/db.go
+++ b/models/migrations/base/db.go
@@ -518,7 +518,7 @@ func ModifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error {
func removeAllWithRetry(dir string) error {
var err error
- for i := 0; i < 20; i++ {
+ for range 20 {
err = os.RemoveAll(dir)
if err == nil {
break
diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go
index 7da426fef0..33fd1df707 100644
--- a/models/migrations/base/tests.go
+++ b/models/migrations/base/tests.go
@@ -1,7 +1,6 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-//nolint:forbidigo
package base
import (
@@ -106,7 +105,7 @@ func MainTest(m *testing.M) {
giteaConf := os.Getenv("GITEA_CONF")
if giteaConf == "" {
giteaConf = filepath.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
- fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
+ _, _ = fmt.Fprintf(os.Stderr, "Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
}
if !filepath.IsAbs(giteaConf) {
@@ -134,7 +133,7 @@ func MainTest(m *testing.M) {
exitStatus := m.Run()
if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
- fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
+ _, _ = fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
}
os.Exit(exitStatus)
}
diff --git a/models/migrations/v1_10/v100.go b/models/migrations/v1_10/v100.go
index 5d2fd8e244..1742bea296 100644
--- a/models/migrations/v1_10/v100.go
+++ b/models/migrations/v1_10/v100.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import (
"net/url"
diff --git a/models/migrations/v1_10/v101.go b/models/migrations/v1_10/v101.go
index f023a2a0e7..6c8dfe2486 100644
--- a/models/migrations/v1_10/v101.go
+++ b/models/migrations/v1_10/v101.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_10/v88.go b/models/migrations/v1_10/v88.go
index 7e86ac364f..eb8e81c19e 100644
--- a/models/migrations/v1_10/v88.go
+++ b/models/migrations/v1_10/v88.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import (
"crypto/sha1"
diff --git a/models/migrations/v1_10/v89.go b/models/migrations/v1_10/v89.go
index d5f27ffdc6..0df2a6e17b 100644
--- a/models/migrations/v1_10/v89.go
+++ b/models/migrations/v1_10/v89.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v90.go b/models/migrations/v1_10/v90.go
index 295d4b1c1b..5521a97e32 100644
--- a/models/migrations/v1_10/v90.go
+++ b/models/migrations/v1_10/v90.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v91.go b/models/migrations/v1_10/v91.go
index 48cac2de70..08db6c2742 100644
--- a/models/migrations/v1_10/v91.go
+++ b/models/migrations/v1_10/v91.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v92.go b/models/migrations/v1_10/v92.go
index 9080108594..b6c04a9234 100644
--- a/models/migrations/v1_10/v92.go
+++ b/models/migrations/v1_10/v92.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import (
"xorm.io/builder"
diff --git a/models/migrations/v1_10/v93.go b/models/migrations/v1_10/v93.go
index ee59a8db39..c131be9a8d 100644
--- a/models/migrations/v1_10/v93.go
+++ b/models/migrations/v1_10/v93.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v94.go b/models/migrations/v1_10/v94.go
index c131af162b..13b7d7b303 100644
--- a/models/migrations/v1_10/v94.go
+++ b/models/migrations/v1_10/v94.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v95.go b/models/migrations/v1_10/v95.go
index 3b1f67fd9c..86b52026bf 100644
--- a/models/migrations/v1_10/v95.go
+++ b/models/migrations/v1_10/v95.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v96.go b/models/migrations/v1_10/v96.go
index 34c8240031..ca35a169c4 100644
--- a/models/migrations/v1_10/v96.go
+++ b/models/migrations/v1_10/v96.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import (
"path/filepath"
diff --git a/models/migrations/v1_10/v97.go b/models/migrations/v1_10/v97.go
index dee45b32e3..5872bb63e5 100644
--- a/models/migrations/v1_10/v97.go
+++ b/models/migrations/v1_10/v97.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v98.go b/models/migrations/v1_10/v98.go
index bdd9aed089..d21c326459 100644
--- a/models/migrations/v1_10/v98.go
+++ b/models/migrations/v1_10/v98.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import "xorm.io/xorm"
diff --git a/models/migrations/v1_10/v99.go b/models/migrations/v1_10/v99.go
index ebe6597f7c..223c188057 100644
--- a/models/migrations/v1_10/v99.go
+++ b/models/migrations/v1_10/v99.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_10 //nolint
+package v1_10
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_11/v102.go b/models/migrations/v1_11/v102.go
index 9358e4cef3..e52290afb0 100644
--- a/models/migrations/v1_11/v102.go
+++ b/models/migrations/v1_11/v102.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_11/v103.go b/models/migrations/v1_11/v103.go
index 53527dac58..a515710160 100644
--- a/models/migrations/v1_11/v103.go
+++ b/models/migrations/v1_11/v103.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_11/v104.go b/models/migrations/v1_11/v104.go
index 3e8ee64bc1..3b0d3c64b2 100644
--- a/models/migrations/v1_11/v104.go
+++ b/models/migrations/v1_11/v104.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_11/v105.go b/models/migrations/v1_11/v105.go
index b91340c30a..d86973a0f6 100644
--- a/models/migrations/v1_11/v105.go
+++ b/models/migrations/v1_11/v105.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_11/v106.go b/models/migrations/v1_11/v106.go
index ecb11cdd1e..edffe18683 100644
--- a/models/migrations/v1_11/v106.go
+++ b/models/migrations/v1_11/v106.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_11/v107.go b/models/migrations/v1_11/v107.go
index f0bfe5862c..a158e3bb50 100644
--- a/models/migrations/v1_11/v107.go
+++ b/models/migrations/v1_11/v107.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_11/v108.go b/models/migrations/v1_11/v108.go
index a85096234d..8f14504ceb 100644
--- a/models/migrations/v1_11/v108.go
+++ b/models/migrations/v1_11/v108.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_11/v109.go b/models/migrations/v1_11/v109.go
index ea565ccda3..f7616aec7b 100644
--- a/models/migrations/v1_11/v109.go
+++ b/models/migrations/v1_11/v109.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_11/v110.go b/models/migrations/v1_11/v110.go
index 81afa1331d..512f728c03 100644
--- a/models/migrations/v1_11/v110.go
+++ b/models/migrations/v1_11/v110.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_11/v111.go b/models/migrations/v1_11/v111.go
index ff108479a9..c27465f051 100644
--- a/models/migrations/v1_11/v111.go
+++ b/models/migrations/v1_11/v111.go
@@ -1,10 +1,11 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"fmt"
+ "slices"
"xorm.io/xorm"
)
@@ -344,10 +345,8 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
}
return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil
}
- for _, id := range protectedBranch.ApprovalsWhitelistUserIDs {
- if id == reviewer.ID {
- return true, nil
- }
+ if slices.Contains(protectedBranch.ApprovalsWhitelistUserIDs, reviewer.ID) {
+ return true, nil
}
// isUserInTeams
diff --git a/models/migrations/v1_11/v112.go b/models/migrations/v1_11/v112.go
index 0857663119..fe45cf9222 100644
--- a/models/migrations/v1_11/v112.go
+++ b/models/migrations/v1_11/v112.go
@@ -1,12 +1,12 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
- "fmt"
"path/filepath"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -31,7 +31,7 @@ func RemoveAttachmentMissedRepo(x *xorm.Engine) error {
for i := 0; i < len(attachments); i++ {
uuid := attachments[i].UUID
if err = util.RemoveAll(filepath.Join(setting.Attachment.Storage.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
- fmt.Printf("Error: %v", err) //nolint:forbidigo
+ log.Warn("Unable to remove attachment file by UUID %s: %v", uuid, err)
}
}
diff --git a/models/migrations/v1_11/v113.go b/models/migrations/v1_11/v113.go
index dea344a44f..a4d54f66fb 100644
--- a/models/migrations/v1_11/v113.go
+++ b/models/migrations/v1_11/v113.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"fmt"
diff --git a/models/migrations/v1_11/v114.go b/models/migrations/v1_11/v114.go
index 95adcee989..9467a8a90c 100644
--- a/models/migrations/v1_11/v114.go
+++ b/models/migrations/v1_11/v114.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"net/url"
diff --git a/models/migrations/v1_11/v115.go b/models/migrations/v1_11/v115.go
index 8c631cfd0b..5933c0520f 100644
--- a/models/migrations/v1_11/v115.go
+++ b/models/migrations/v1_11/v115.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"crypto/md5"
@@ -146,7 +146,7 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error)
return "", fmt.Errorf("io.ReadAll: %w", err)
}
- newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data)))))
+ newAvatar := fmt.Sprintf("%x", md5.Sum(fmt.Appendf(nil, "%d-%x", userID, md5.Sum(data))))
if newAvatar == oldAvatar {
return newAvatar, nil
}
diff --git a/models/migrations/v1_11/v116.go b/models/migrations/v1_11/v116.go
index 85aa76c1e0..729fbad18b 100644
--- a/models/migrations/v1_11/v116.go
+++ b/models/migrations/v1_11/v116.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_11 //nolint
+package v1_11
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v117.go b/models/migrations/v1_12/v117.go
index 8eadcdef2b..73b58ca34b 100644
--- a/models/migrations/v1_12/v117.go
+++ b/models/migrations/v1_12/v117.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v118.go b/models/migrations/v1_12/v118.go
index eb022dc5e4..e8b4249743 100644
--- a/models/migrations/v1_12/v118.go
+++ b/models/migrations/v1_12/v118.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v119.go b/models/migrations/v1_12/v119.go
index 60bfe6a57d..b4bf29a935 100644
--- a/models/migrations/v1_12/v119.go
+++ b/models/migrations/v1_12/v119.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v120.go b/models/migrations/v1_12/v120.go
index 3f7ed8d373..14d515f5a7 100644
--- a/models/migrations/v1_12/v120.go
+++ b/models/migrations/v1_12/v120.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v121.go b/models/migrations/v1_12/v121.go
index 175ec9164d..a28ae4e1c9 100644
--- a/models/migrations/v1_12/v121.go
+++ b/models/migrations/v1_12/v121.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import "xorm.io/xorm"
diff --git a/models/migrations/v1_12/v122.go b/models/migrations/v1_12/v122.go
index 6e31d863a1..bc1b175f6a 100644
--- a/models/migrations/v1_12/v122.go
+++ b/models/migrations/v1_12/v122.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v123.go b/models/migrations/v1_12/v123.go
index b0c3af07a3..52b10bb850 100644
--- a/models/migrations/v1_12/v123.go
+++ b/models/migrations/v1_12/v123.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v124.go b/models/migrations/v1_12/v124.go
index d2ba03ffe0..9a93f436d4 100644
--- a/models/migrations/v1_12/v124.go
+++ b/models/migrations/v1_12/v124.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v125.go b/models/migrations/v1_12/v125.go
index ec4ffaab25..7f582ecff5 100644
--- a/models/migrations/v1_12/v125.go
+++ b/models/migrations/v1_12/v125.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v126.go b/models/migrations/v1_12/v126.go
index ca9ec3aa3f..64fd7f7478 100644
--- a/models/migrations/v1_12/v126.go
+++ b/models/migrations/v1_12/v126.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/builder"
diff --git a/models/migrations/v1_12/v127.go b/models/migrations/v1_12/v127.go
index 00e391dc87..9bd78db95e 100644
--- a/models/migrations/v1_12/v127.go
+++ b/models/migrations/v1_12/v127.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v128.go b/models/migrations/v1_12/v128.go
index cba64711d0..e7dbff3766 100644
--- a/models/migrations/v1_12/v128.go
+++ b/models/migrations/v1_12/v128.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v129.go b/models/migrations/v1_12/v129.go
index cf228242b9..3e4d3aca68 100644
--- a/models/migrations/v1_12/v129.go
+++ b/models/migrations/v1_12/v129.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v130.go b/models/migrations/v1_12/v130.go
index 391810c7ca..107bb756fd 100644
--- a/models/migrations/v1_12/v130.go
+++ b/models/migrations/v1_12/v130.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"code.gitea.io/gitea/modules/json"
diff --git a/models/migrations/v1_12/v131.go b/models/migrations/v1_12/v131.go
index 5184bc3590..1266c2f185 100644
--- a/models/migrations/v1_12/v131.go
+++ b/models/migrations/v1_12/v131.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v132.go b/models/migrations/v1_12/v132.go
index 3b2b28f7ab..8b1ae6db93 100644
--- a/models/migrations/v1_12/v132.go
+++ b/models/migrations/v1_12/v132.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v133.go b/models/migrations/v1_12/v133.go
index c9087fc8c1..69e20597d8 100644
--- a/models/migrations/v1_12/v133.go
+++ b/models/migrations/v1_12/v133.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import "xorm.io/xorm"
diff --git a/models/migrations/v1_12/v134.go b/models/migrations/v1_12/v134.go
index a918d38757..09d743964d 100644
--- a/models/migrations/v1_12/v134.go
+++ b/models/migrations/v1_12/v134.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v135.go b/models/migrations/v1_12/v135.go
index 8898011df5..5df0ad7fc4 100644
--- a/models/migrations/v1_12/v135.go
+++ b/models/migrations/v1_12/v135.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v136.go b/models/migrations/v1_12/v136.go
index d91ff92feb..0f53278b46 100644
--- a/models/migrations/v1_12/v136.go
+++ b/models/migrations/v1_12/v136.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v137.go b/models/migrations/v1_12/v137.go
index 0d86b72010..9d38483488 100644
--- a/models/migrations/v1_12/v137.go
+++ b/models/migrations/v1_12/v137.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_12/v138.go b/models/migrations/v1_12/v138.go
index 8c8d353f40..4485adeb2d 100644
--- a/models/migrations/v1_12/v138.go
+++ b/models/migrations/v1_12/v138.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"fmt"
diff --git a/models/migrations/v1_12/v139.go b/models/migrations/v1_12/v139.go
index 279aa7df87..a3799841ac 100644
--- a/models/migrations/v1_12/v139.go
+++ b/models/migrations/v1_12/v139.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_12 //nolint
+package v1_12
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_13/v140.go b/models/migrations/v1_13/v140.go
index f3719e16f6..a9a047bca9 100644
--- a/models/migrations/v1_13/v140.go
+++ b/models/migrations/v1_13/v140.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"fmt"
@@ -21,12 +21,7 @@ func FixLanguageStatsToSaveSize(x *xorm.Engine) error {
// RepoIndexerType specifies the repository indexer type
type RepoIndexerType int
- const (
- // RepoIndexerTypeCode code indexer - 0
- RepoIndexerTypeCode RepoIndexerType = iota //nolint:unused
- // RepoIndexerTypeStats repository stats indexer - 1
- RepoIndexerTypeStats
- )
+ const RepoIndexerTypeStats RepoIndexerType = 1
// RepoIndexerStatus see models/repo_indexer.go
type RepoIndexerStatus struct {
diff --git a/models/migrations/v1_13/v141.go b/models/migrations/v1_13/v141.go
index ae211e0e44..b54bc1727c 100644
--- a/models/migrations/v1_13/v141.go
+++ b/models/migrations/v1_13/v141.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"fmt"
diff --git a/models/migrations/v1_13/v142.go b/models/migrations/v1_13/v142.go
index 7c7c01ad47..d08a0ae0bf 100644
--- a/models/migrations/v1_13/v142.go
+++ b/models/migrations/v1_13/v142.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"code.gitea.io/gitea/modules/log"
diff --git a/models/migrations/v1_13/v143.go b/models/migrations/v1_13/v143.go
index 885768dff3..b9a856ed0f 100644
--- a/models/migrations/v1_13/v143.go
+++ b/models/migrations/v1_13/v143.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"code.gitea.io/gitea/modules/log"
diff --git a/models/migrations/v1_13/v144.go b/models/migrations/v1_13/v144.go
index f5a0bc5751..9352d78bc8 100644
--- a/models/migrations/v1_13/v144.go
+++ b/models/migrations/v1_13/v144.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"code.gitea.io/gitea/modules/log"
diff --git a/models/migrations/v1_13/v145.go b/models/migrations/v1_13/v145.go
index bb1f40baa7..86ebb4f9d9 100644
--- a/models/migrations/v1_13/v145.go
+++ b/models/migrations/v1_13/v145.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"fmt"
diff --git a/models/migrations/v1_13/v146.go b/models/migrations/v1_13/v146.go
index 7d9a878704..355c772c26 100644
--- a/models/migrations/v1_13/v146.go
+++ b/models/migrations/v1_13/v146.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_13/v147.go b/models/migrations/v1_13/v147.go
index 510ef39b28..0059c06220 100644
--- a/models/migrations/v1_13/v147.go
+++ b/models/migrations/v1_13/v147.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_13/v148.go b/models/migrations/v1_13/v148.go
index 7bb8ab700b..d276db3d61 100644
--- a/models/migrations/v1_13/v148.go
+++ b/models/migrations/v1_13/v148.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_13/v149.go b/models/migrations/v1_13/v149.go
index 2a1db04cbb..a96b8e5ac7 100644
--- a/models/migrations/v1_13/v149.go
+++ b/models/migrations/v1_13/v149.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"fmt"
diff --git a/models/migrations/v1_13/v150.go b/models/migrations/v1_13/v150.go
index d5ba489566..590ea72903 100644
--- a/models/migrations/v1_13/v150.go
+++ b/models/migrations/v1_13/v150.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_13/v151.go b/models/migrations/v1_13/v151.go
index 1865d58f04..454929534f 100644
--- a/models/migrations/v1_13/v151.go
+++ b/models/migrations/v1_13/v151.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"context"
diff --git a/models/migrations/v1_13/v152.go b/models/migrations/v1_13/v152.go
index 502c82a40d..648e26446f 100644
--- a/models/migrations/v1_13/v152.go
+++ b/models/migrations/v1_13/v152.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import "xorm.io/xorm"
diff --git a/models/migrations/v1_13/v153.go b/models/migrations/v1_13/v153.go
index 0b2dd3eb62..e5462fc162 100644
--- a/models/migrations/v1_13/v153.go
+++ b/models/migrations/v1_13/v153.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_13/v154.go b/models/migrations/v1_13/v154.go
index 60cc56713e..5477d1b889 100644
--- a/models/migrations/v1_13/v154.go
+++ b/models/migrations/v1_13/v154.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_13 //nolint
+package v1_13
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_14/main_test.go b/models/migrations/v1_14/main_test.go
index 7a091b9b9a..978f88577c 100644
--- a/models/migrations/v1_14/main_test.go
+++ b/models/migrations/v1_14/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"testing"
diff --git a/models/migrations/v1_14/v155.go b/models/migrations/v1_14/v155.go
index e814f59938..505a9ae033 100644
--- a/models/migrations/v1_14/v155.go
+++ b/models/migrations/v1_14/v155.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v156.go b/models/migrations/v1_14/v156.go
index 2cf4954a15..2fa5819610 100644
--- a/models/migrations/v1_14/v156.go
+++ b/models/migrations/v1_14/v156.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v157.go b/models/migrations/v1_14/v157.go
index 7187278d29..2c5625ebbd 100644
--- a/models/migrations/v1_14/v157.go
+++ b/models/migrations/v1_14/v157.go
@@ -1,24 +1,13 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"xorm.io/xorm"
)
func FixRepoTopics(x *xorm.Engine) error {
- type Topic struct { //nolint:unused
- ID int64 `xorm:"pk autoincr"`
- Name string `xorm:"UNIQUE VARCHAR(25)"`
- RepoCount int
- }
-
- type RepoTopic struct { //nolint:unused
- RepoID int64 `xorm:"pk"`
- TopicID int64 `xorm:"pk"`
- }
-
type Repository struct {
ID int64 `xorm:"pk autoincr"`
Topics []string `xorm:"TEXT JSON"`
diff --git a/models/migrations/v1_14/v158.go b/models/migrations/v1_14/v158.go
index a849ddf27e..3c57e8e3da 100644
--- a/models/migrations/v1_14/v158.go
+++ b/models/migrations/v1_14/v158.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"errors"
diff --git a/models/migrations/v1_14/v159.go b/models/migrations/v1_14/v159.go
index 149ae0f6a8..e6f6f0f061 100644
--- a/models/migrations/v1_14/v159.go
+++ b/models/migrations/v1_14/v159.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_14/v160.go b/models/migrations/v1_14/v160.go
index 4dea91b514..73f3798954 100644
--- a/models/migrations/v1_14/v160.go
+++ b/models/migrations/v1_14/v160.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_14/v161.go b/models/migrations/v1_14/v161.go
index ac7e821a80..eb92dee77c 100644
--- a/models/migrations/v1_14/v161.go
+++ b/models/migrations/v1_14/v161.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"context"
diff --git a/models/migrations/v1_14/v162.go b/models/migrations/v1_14/v162.go
index 2e4e0b8eb0..a0ddd36d55 100644
--- a/models/migrations/v1_14/v162.go
+++ b/models/migrations/v1_14/v162.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_14/v163.go b/models/migrations/v1_14/v163.go
index 0cd8ba68c8..84c35190b7 100644
--- a/models/migrations/v1_14/v163.go
+++ b/models/migrations/v1_14/v163.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_14/v164.go b/models/migrations/v1_14/v164.go
index 54f6951427..d2fd9b8464 100644
--- a/models/migrations/v1_14/v164.go
+++ b/models/migrations/v1_14/v164.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v165.go b/models/migrations/v1_14/v165.go
index 926350cdf7..6e1b34156b 100644
--- a/models/migrations/v1_14/v165.go
+++ b/models/migrations/v1_14/v165.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"code.gitea.io/gitea/models/migrations/base"
@@ -16,10 +16,7 @@ func ConvertHookTaskTypeToVarcharAndTrim(x *xorm.Engine) error {
return nil
}
- type HookTask struct { //nolint:unused
- Typ string `xorm:"VARCHAR(16) index"`
- }
-
+ // HookTask: Typ string `xorm:"VARCHAR(16) index"`
if err := base.ModifyColumn(x, "hook_task", &schemas.Column{
Name: "typ",
SQLType: schemas.SQLType{
@@ -42,10 +39,7 @@ func ConvertHookTaskTypeToVarcharAndTrim(x *xorm.Engine) error {
return err
}
- type Webhook struct { //nolint:unused
- Type string `xorm:"VARCHAR(16) index"`
- }
-
+ // Webhook: Type string `xorm:"VARCHAR(16) index"`
if err := base.ModifyColumn(x, "webhook", &schemas.Column{
Name: "type",
SQLType: schemas.SQLType{
diff --git a/models/migrations/v1_14/v166.go b/models/migrations/v1_14/v166.go
index e5731582fd..4c106bd7da 100644
--- a/models/migrations/v1_14/v166.go
+++ b/models/migrations/v1_14/v166.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"crypto/sha256"
diff --git a/models/migrations/v1_14/v167.go b/models/migrations/v1_14/v167.go
index 9d416f6a32..d77bbc401e 100644
--- a/models/migrations/v1_14/v167.go
+++ b/models/migrations/v1_14/v167.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v168.go b/models/migrations/v1_14/v168.go
index a30a8859f7..aa93eec19b 100644
--- a/models/migrations/v1_14/v168.go
+++ b/models/migrations/v1_14/v168.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import "xorm.io/xorm"
diff --git a/models/migrations/v1_14/v169.go b/models/migrations/v1_14/v169.go
index 5b81bb58b1..4f9df0d96f 100644
--- a/models/migrations/v1_14/v169.go
+++ b/models/migrations/v1_14/v169.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_14/v170.go b/models/migrations/v1_14/v170.go
index 7b6498a3e9..a2ff4623e1 100644
--- a/models/migrations/v1_14/v170.go
+++ b/models/migrations/v1_14/v170.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v171.go b/models/migrations/v1_14/v171.go
index 51a35a02ad..7b200e960a 100644
--- a/models/migrations/v1_14/v171.go
+++ b/models/migrations/v1_14/v171.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v172.go b/models/migrations/v1_14/v172.go
index 0f9bef902a..bbd61d87b2 100644
--- a/models/migrations/v1_14/v172.go
+++ b/models/migrations/v1_14/v172.go
@@ -1,7 +1,7 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_14/v173.go b/models/migrations/v1_14/v173.go
index 2d9eee9197..7752fbe966 100644
--- a/models/migrations/v1_14/v173.go
+++ b/models/migrations/v1_14/v173.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v174.go b/models/migrations/v1_14/v174.go
index c839e15db8..4049e43070 100644
--- a/models/migrations/v1_14/v174.go
+++ b/models/migrations/v1_14/v174.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v175.go b/models/migrations/v1_14/v175.go
index 70d72b2600..92ed130473 100644
--- a/models/migrations/v1_14/v175.go
+++ b/models/migrations/v1_14/v175.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v176.go b/models/migrations/v1_14/v176.go
index 1ed49f75fa..ef5dce9a02 100644
--- a/models/migrations/v1_14/v176.go
+++ b/models/migrations/v1_14/v176.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_14/v176_test.go b/models/migrations/v1_14/v176_test.go
index ea3e750d7f..5c1db4db71 100644
--- a/models/migrations/v1_14/v176_test.go
+++ b/models/migrations/v1_14/v176_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"testing"
diff --git a/models/migrations/v1_14/v177.go b/models/migrations/v1_14/v177.go
index 6e1838f369..96676bf8d9 100644
--- a/models/migrations/v1_14/v177.go
+++ b/models/migrations/v1_14/v177.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"fmt"
diff --git a/models/migrations/v1_14/v177_test.go b/models/migrations/v1_14/v177_test.go
index 5568a18fec..263f69f338 100644
--- a/models/migrations/v1_14/v177_test.go
+++ b/models/migrations/v1_14/v177_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_14 //nolint
+package v1_14
import (
"testing"
diff --git a/models/migrations/v1_15/main_test.go b/models/migrations/v1_15/main_test.go
index 366f19788e..d01585e997 100644
--- a/models/migrations/v1_15/main_test.go
+++ b/models/migrations/v1_15/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"testing"
diff --git a/models/migrations/v1_15/v178.go b/models/migrations/v1_15/v178.go
index 6d236eb049..ca3a5c262e 100644
--- a/models/migrations/v1_15/v178.go
+++ b/models/migrations/v1_15/v178.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_15/v179.go b/models/migrations/v1_15/v179.go
index f6b142eb42..d6fb86ffec 100644
--- a/models/migrations/v1_15/v179.go
+++ b/models/migrations/v1_15/v179.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_15/v180.go b/models/migrations/v1_15/v180.go
index c71e771861..dd132f8330 100644
--- a/models/migrations/v1_15/v180.go
+++ b/models/migrations/v1_15/v180.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"code.gitea.io/gitea/modules/json"
diff --git a/models/migrations/v1_15/v181.go b/models/migrations/v1_15/v181.go
index 2185ed0213..fb1d3d7a75 100644
--- a/models/migrations/v1_15/v181.go
+++ b/models/migrations/v1_15/v181.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"strings"
diff --git a/models/migrations/v1_15/v181_test.go b/models/migrations/v1_15/v181_test.go
index 7295aa4180..73b5c1f3d6 100644
--- a/models/migrations/v1_15/v181_test.go
+++ b/models/migrations/v1_15/v181_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"strings"
diff --git a/models/migrations/v1_15/v182.go b/models/migrations/v1_15/v182.go
index 9ca500c0f9..f53ff11df9 100644
--- a/models/migrations/v1_15/v182.go
+++ b/models/migrations/v1_15/v182.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_15/v182_test.go b/models/migrations/v1_15/v182_test.go
index 75ef8e1cd8..5fc6a0c467 100644
--- a/models/migrations/v1_15/v182_test.go
+++ b/models/migrations/v1_15/v182_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"testing"
diff --git a/models/migrations/v1_15/v183.go b/models/migrations/v1_15/v183.go
index effad1b467..5d0582f03d 100644
--- a/models/migrations/v1_15/v183.go
+++ b/models/migrations/v1_15/v183.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"fmt"
diff --git a/models/migrations/v1_15/v184.go b/models/migrations/v1_15/v184.go
index 4b3dd1467a..2823bc1f7a 100644
--- a/models/migrations/v1_15/v184.go
+++ b/models/migrations/v1_15/v184.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"context"
diff --git a/models/migrations/v1_15/v185.go b/models/migrations/v1_15/v185.go
index e5878ec193..60af59edca 100644
--- a/models/migrations/v1_15/v185.go
+++ b/models/migrations/v1_15/v185.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_15/v186.go b/models/migrations/v1_15/v186.go
index 01aab3add5..67dc97d13d 100644
--- a/models/migrations/v1_15/v186.go
+++ b/models/migrations/v1_15/v186.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_15/v187.go b/models/migrations/v1_15/v187.go
index 21cd6772b7..5fd90c65fb 100644
--- a/models/migrations/v1_15/v187.go
+++ b/models/migrations/v1_15/v187.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_15/v188.go b/models/migrations/v1_15/v188.go
index 71e45cab0e..4494e6ff05 100644
--- a/models/migrations/v1_15/v188.go
+++ b/models/migrations/v1_15/v188.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_15 //nolint
+package v1_15
import "xorm.io/xorm"
diff --git a/models/migrations/v1_16/main_test.go b/models/migrations/v1_16/main_test.go
index 817a0c13a4..7f93d6e9e5 100644
--- a/models/migrations/v1_16/main_test.go
+++ b/models/migrations/v1_16/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"testing"
diff --git a/models/migrations/v1_16/v189.go b/models/migrations/v1_16/v189.go
index 5649645051..6bc99e58ab 100644
--- a/models/migrations/v1_16/v189.go
+++ b/models/migrations/v1_16/v189.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"encoding/binary"
diff --git a/models/migrations/v1_16/v189_test.go b/models/migrations/v1_16/v189_test.go
index 2a73bfae03..fb56ac8e11 100644
--- a/models/migrations/v1_16/v189_test.go
+++ b/models/migrations/v1_16/v189_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"testing"
diff --git a/models/migrations/v1_16/v190.go b/models/migrations/v1_16/v190.go
index 5953802849..1eb6b6ddb4 100644
--- a/models/migrations/v1_16/v190.go
+++ b/models/migrations/v1_16/v190.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v191.go b/models/migrations/v1_16/v191.go
index c618783c08..957c82e484 100644
--- a/models/migrations/v1_16/v191.go
+++ b/models/migrations/v1_16/v191.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_16/v192.go b/models/migrations/v1_16/v192.go
index 2d5d158a09..9d03fbe3c8 100644
--- a/models/migrations/v1_16/v192.go
+++ b/models/migrations/v1_16/v192.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_16/v193.go b/models/migrations/v1_16/v193.go
index 8d3ce7a558..a5af2de380 100644
--- a/models/migrations/v1_16/v193.go
+++ b/models/migrations/v1_16/v193.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go
index 7f43846bc3..2e827f0550 100644
--- a/models/migrations/v1_16/v193_test.go
+++ b/models/migrations/v1_16/v193_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"testing"
diff --git a/models/migrations/v1_16/v194.go b/models/migrations/v1_16/v194.go
index 6aa13c50cf..2e4ed8340e 100644
--- a/models/migrations/v1_16/v194.go
+++ b/models/migrations/v1_16/v194.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v195.go b/models/migrations/v1_16/v195.go
index 6d7e94141e..4fd42b7bd2 100644
--- a/models/migrations/v1_16/v195.go
+++ b/models/migrations/v1_16/v195.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v195_test.go b/models/migrations/v1_16/v195_test.go
index 742397bf32..946e06e399 100644
--- a/models/migrations/v1_16/v195_test.go
+++ b/models/migrations/v1_16/v195_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"testing"
diff --git a/models/migrations/v1_16/v196.go b/models/migrations/v1_16/v196.go
index 7cbafc61e5..6c9caa100f 100644
--- a/models/migrations/v1_16/v196.go
+++ b/models/migrations/v1_16/v196.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v197.go b/models/migrations/v1_16/v197.go
index 97888b2847..862bdfdcbd 100644
--- a/models/migrations/v1_16/v197.go
+++ b/models/migrations/v1_16/v197.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_16/v198.go b/models/migrations/v1_16/v198.go
index 115bb313a0..f35ede138a 100644
--- a/models/migrations/v1_16/v198.go
+++ b/models/migrations/v1_16/v198.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v199.go b/models/migrations/v1_16/v199.go
index 6adcf890af..4020352f2b 100644
--- a/models/migrations/v1_16/v199.go
+++ b/models/migrations/v1_16/v199.go
@@ -1,6 +1,6 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
// We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
diff --git a/models/migrations/v1_16/v200.go b/models/migrations/v1_16/v200.go
index c08c20e51d..de57fad8fe 100644
--- a/models/migrations/v1_16/v200.go
+++ b/models/migrations/v1_16/v200.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v201.go b/models/migrations/v1_16/v201.go
index 35e0c9f2fb..2c43698b0c 100644
--- a/models/migrations/v1_16/v201.go
+++ b/models/migrations/v1_16/v201.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_16/v202.go b/models/migrations/v1_16/v202.go
index 6ba36152f1..d8c8fdcadc 100644
--- a/models/migrations/v1_16/v202.go
+++ b/models/migrations/v1_16/v202.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v203.go b/models/migrations/v1_16/v203.go
index e8e6b52453..c3241cba57 100644
--- a/models/migrations/v1_16/v203.go
+++ b/models/migrations/v1_16/v203.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_16/v204.go b/models/migrations/v1_16/v204.go
index ece03e1305..4d375307e7 100644
--- a/models/migrations/v1_16/v204.go
+++ b/models/migrations/v1_16/v204.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import "xorm.io/xorm"
diff --git a/models/migrations/v1_16/v205.go b/models/migrations/v1_16/v205.go
index d6c577083c..78241bad5b 100644
--- a/models/migrations/v1_16/v205.go
+++ b/models/migrations/v1_16/v205.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_16/v206.go b/models/migrations/v1_16/v206.go
index 581a7d76e9..01a9c386eb 100644
--- a/models/migrations/v1_16/v206.go
+++ b/models/migrations/v1_16/v206.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"fmt"
diff --git a/models/migrations/v1_16/v207.go b/models/migrations/v1_16/v207.go
index 91208f066c..19126ead1f 100644
--- a/models/migrations/v1_16/v207.go
+++ b/models/migrations/v1_16/v207.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_16/v208.go b/models/migrations/v1_16/v208.go
index 1a11ef096a..fb643324f4 100644
--- a/models/migrations/v1_16/v208.go
+++ b/models/migrations/v1_16/v208.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_16/v209.go b/models/migrations/v1_16/v209.go
index be3100e02a..230838647b 100644
--- a/models/migrations/v1_16/v209.go
+++ b/models/migrations/v1_16/v209.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go
index 51b7d81e99..0b94baf8e3 100644
--- a/models/migrations/v1_16/v210.go
+++ b/models/migrations/v1_16/v210.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"encoding/base32"
diff --git a/models/migrations/v1_16/v210_test.go b/models/migrations/v1_16/v210_test.go
index 7917301c98..3b4ac7aa4b 100644
--- a/models/migrations/v1_16/v210_test.go
+++ b/models/migrations/v1_16/v210_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_16 //nolint
+package v1_16
import (
"testing"
diff --git a/models/migrations/v1_17/main_test.go b/models/migrations/v1_17/main_test.go
index 79cb3fa078..571a4f55a3 100644
--- a/models/migrations/v1_17/main_test.go
+++ b/models/migrations/v1_17/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"testing"
diff --git a/models/migrations/v1_17/v211.go b/models/migrations/v1_17/v211.go
index 9b72c8610b..517cf19388 100644
--- a/models/migrations/v1_17/v211.go
+++ b/models/migrations/v1_17/v211.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_17/v212.go b/models/migrations/v1_17/v212.go
index e3f9437121..788792211f 100644
--- a/models/migrations/v1_17/v212.go
+++ b/models/migrations/v1_17/v212.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_17/v213.go b/models/migrations/v1_17/v213.go
index bb3f466e52..b2bbdf7279 100644
--- a/models/migrations/v1_17/v213.go
+++ b/models/migrations/v1_17/v213.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_17/v214.go b/models/migrations/v1_17/v214.go
index 2268164919..1925324f0f 100644
--- a/models/migrations/v1_17/v214.go
+++ b/models/migrations/v1_17/v214.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_17/v215.go b/models/migrations/v1_17/v215.go
index b338f85417..748539225d 100644
--- a/models/migrations/v1_17/v215.go
+++ b/models/migrations/v1_17/v215.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"code.gitea.io/gitea/models/pull"
diff --git a/models/migrations/v1_17/v216.go b/models/migrations/v1_17/v216.go
index 268f472a42..37aeacb6fc 100644
--- a/models/migrations/v1_17/v216.go
+++ b/models/migrations/v1_17/v216.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
// This migration added non-ideal indices to the action table which on larger datasets slowed things down
// it has been superseded by v218.go
diff --git a/models/migrations/v1_17/v217.go b/models/migrations/v1_17/v217.go
index 3f970b68a5..04626bcbc5 100644
--- a/models/migrations/v1_17/v217.go
+++ b/models/migrations/v1_17/v217.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_17/v218.go b/models/migrations/v1_17/v218.go
index 4c05a9b539..17d4cd89d4 100644
--- a/models/migrations/v1_17/v218.go
+++ b/models/migrations/v1_17/v218.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_17/v219.go b/models/migrations/v1_17/v219.go
index d266029fd9..6e335cb813 100644
--- a/models/migrations/v1_17/v219.go
+++ b/models/migrations/v1_17/v219.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"time"
diff --git a/models/migrations/v1_17/v220.go b/models/migrations/v1_17/v220.go
index 904ddc5192..4ac8c58e1e 100644
--- a/models/migrations/v1_17/v220.go
+++ b/models/migrations/v1_17/v220.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
packages_model "code.gitea.io/gitea/models/packages"
diff --git a/models/migrations/v1_17/v221.go b/models/migrations/v1_17/v221.go
index 9e159388bd..9e6a67eb18 100644
--- a/models/migrations/v1_17/v221.go
+++ b/models/migrations/v1_17/v221.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"encoding/base32"
diff --git a/models/migrations/v1_17/v221_test.go b/models/migrations/v1_17/v221_test.go
index 9ca54142e2..a2dc0fae55 100644
--- a/models/migrations/v1_17/v221_test.go
+++ b/models/migrations/v1_17/v221_test.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"encoding/base32"
diff --git a/models/migrations/v1_17/v222.go b/models/migrations/v1_17/v222.go
index 6c28f8102b..a5ea537d8a 100644
--- a/models/migrations/v1_17/v222.go
+++ b/models/migrations/v1_17/v222.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"context"
diff --git a/models/migrations/v1_17/v223.go b/models/migrations/v1_17/v223.go
index 018451ee4c..b2bfb76551 100644
--- a/models/migrations/v1_17/v223.go
+++ b/models/migrations/v1_17/v223.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_17 //nolint
+package v1_17
import (
"context"
diff --git a/models/migrations/v1_18/main_test.go b/models/migrations/v1_18/main_test.go
index f71a21d1fb..ebcfb45a94 100644
--- a/models/migrations/v1_18/main_test.go
+++ b/models/migrations/v1_18/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"testing"
diff --git a/models/migrations/v1_18/v224.go b/models/migrations/v1_18/v224.go
index f3d522b91a..6dc12020ea 100644
--- a/models/migrations/v1_18/v224.go
+++ b/models/migrations/v1_18/v224.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_18/v225.go b/models/migrations/v1_18/v225.go
index b0ac3777fc..bc6117e38f 100644
--- a/models/migrations/v1_18/v225.go
+++ b/models/migrations/v1_18/v225.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_18/v226.go b/models/migrations/v1_18/v226.go
index f87e24b11d..8ed9761476 100644
--- a/models/migrations/v1_18/v226.go
+++ b/models/migrations/v1_18/v226.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"xorm.io/builder"
diff --git a/models/migrations/v1_18/v227.go b/models/migrations/v1_18/v227.go
index 5fe5dcd0c9..3aca686d59 100644
--- a/models/migrations/v1_18/v227.go
+++ b/models/migrations/v1_18/v227.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_18/v228.go b/models/migrations/v1_18/v228.go
index 3e7a36de15..b13f6461bd 100644
--- a/models/migrations/v1_18/v228.go
+++ b/models/migrations/v1_18/v228.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_18/v229.go b/models/migrations/v1_18/v229.go
index 10d9f35097..bc15e01390 100644
--- a/models/migrations/v1_18/v229.go
+++ b/models/migrations/v1_18/v229.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"fmt"
diff --git a/models/migrations/v1_18/v229_test.go b/models/migrations/v1_18/v229_test.go
index d489328c00..5722dd3557 100644
--- a/models/migrations/v1_18/v229_test.go
+++ b/models/migrations/v1_18/v229_test.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"testing"
diff --git a/models/migrations/v1_18/v230.go b/models/migrations/v1_18/v230.go
index ea5b4d02e1..078fce7643 100644
--- a/models/migrations/v1_18/v230.go
+++ b/models/migrations/v1_18/v230.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_18/v230_test.go b/models/migrations/v1_18/v230_test.go
index 40db4c2ffe..25b2f6525d 100644
--- a/models/migrations/v1_18/v230_test.go
+++ b/models/migrations/v1_18/v230_test.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_18 //nolint
+package v1_18
import (
"testing"
diff --git a/models/migrations/v1_19/main_test.go b/models/migrations/v1_19/main_test.go
index 59f42af111..87e807be6e 100644
--- a/models/migrations/v1_19/main_test.go
+++ b/models/migrations/v1_19/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"testing"
diff --git a/models/migrations/v1_19/v231.go b/models/migrations/v1_19/v231.go
index 79e46132f0..8ef1e4e743 100644
--- a/models/migrations/v1_19/v231.go
+++ b/models/migrations/v1_19/v231.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_19/v232.go b/models/migrations/v1_19/v232.go
index 9caf587c1e..493dbc6df8 100644
--- a/models/migrations/v1_19/v232.go
+++ b/models/migrations/v1_19/v232.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_19/v233.go b/models/migrations/v1_19/v233.go
index ba4cd8e20b..9eb6d40509 100644
--- a/models/migrations/v1_19/v233.go
+++ b/models/migrations/v1_19/v233.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"fmt"
diff --git a/models/migrations/v1_19/v233_test.go b/models/migrations/v1_19/v233_test.go
index 5d445d5132..7436ff7483 100644
--- a/models/migrations/v1_19/v233_test.go
+++ b/models/migrations/v1_19/v233_test.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"testing"
diff --git a/models/migrations/v1_19/v234.go b/models/migrations/v1_19/v234.go
index 728a580807..3475384d6f 100644
--- a/models/migrations/v1_19/v234.go
+++ b/models/migrations/v1_19/v234.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_19/v235.go b/models/migrations/v1_19/v235.go
index 3715de3920..297d90f65a 100644
--- a/models/migrations/v1_19/v235.go
+++ b/models/migrations/v1_19/v235.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_19/v236.go b/models/migrations/v1_19/v236.go
index f172a85b1f..0ed4d97a27 100644
--- a/models/migrations/v1_19/v236.go
+++ b/models/migrations/v1_19/v236.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_19/v237.go b/models/migrations/v1_19/v237.go
index b23c765aa5..cf30226ccd 100644
--- a/models/migrations/v1_19/v237.go
+++ b/models/migrations/v1_19/v237.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_19/v238.go b/models/migrations/v1_19/v238.go
index 266e6cea58..de681bfc7a 100644
--- a/models/migrations/v1_19/v238.go
+++ b/models/migrations/v1_19/v238.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_19/v239.go b/models/migrations/v1_19/v239.go
index 10076f2401..8f4a65be95 100644
--- a/models/migrations/v1_19/v239.go
+++ b/models/migrations/v1_19/v239.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_19/v240.go b/models/migrations/v1_19/v240.go
index 4505f86299..7fdbaeb9dc 100644
--- a/models/migrations/v1_19/v240.go
+++ b/models/migrations/v1_19/v240.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"code.gitea.io/gitea/models/db"
diff --git a/models/migrations/v1_19/v241.go b/models/migrations/v1_19/v241.go
index a617d6fd2f..e35801a057 100644
--- a/models/migrations/v1_19/v241.go
+++ b/models/migrations/v1_19/v241.go
@@ -1,7 +1,7 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_19/v242.go b/models/migrations/v1_19/v242.go
index 4470835214..e9e759eaaa 100644
--- a/models/migrations/v1_19/v242.go
+++ b/models/migrations/v1_19/v242.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_19/v243.go b/models/migrations/v1_19/v243.go
index 55bbfafb2f..9c3f372594 100644
--- a/models/migrations/v1_19/v243.go
+++ b/models/migrations/v1_19/v243.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_19 //nolint
+package v1_19
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_20/main_test.go b/models/migrations/v1_20/main_test.go
index 92a1a9f622..2fd63a7118 100644
--- a/models/migrations/v1_20/main_test.go
+++ b/models/migrations/v1_20/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"testing"
diff --git a/models/migrations/v1_20/v244.go b/models/migrations/v1_20/v244.go
index 977566ad7d..76cdccaca5 100644
--- a/models/migrations/v1_20/v244.go
+++ b/models/migrations/v1_20/v244.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_20/v245.go b/models/migrations/v1_20/v245.go
index 5a195d2ccd..4acb11416c 100644
--- a/models/migrations/v1_20/v245.go
+++ b/models/migrations/v1_20/v245.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"context"
diff --git a/models/migrations/v1_20/v246.go b/models/migrations/v1_20/v246.go
index e6340ef079..22bf723404 100644
--- a/models/migrations/v1_20/v246.go
+++ b/models/migrations/v1_20/v246.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_20/v247.go b/models/migrations/v1_20/v247.go
index 59fc5c46b5..4f82937e18 100644
--- a/models/migrations/v1_20/v247.go
+++ b/models/migrations/v1_20/v247.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"code.gitea.io/gitea/modules/log"
diff --git a/models/migrations/v1_20/v248.go b/models/migrations/v1_20/v248.go
index 40555210e7..4f2091e4bc 100644
--- a/models/migrations/v1_20/v248.go
+++ b/models/migrations/v1_20/v248.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import "xorm.io/xorm"
diff --git a/models/migrations/v1_20/v249.go b/models/migrations/v1_20/v249.go
index 02951a74d6..c6d3a177ca 100644
--- a/models/migrations/v1_20/v249.go
+++ b/models/migrations/v1_20/v249.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_20/v250.go b/models/migrations/v1_20/v250.go
index 86388ef0b8..ec45e6e5c3 100644
--- a/models/migrations/v1_20/v250.go
+++ b/models/migrations/v1_20/v250.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"strings"
diff --git a/models/migrations/v1_20/v251.go b/models/migrations/v1_20/v251.go
index 7743248a3f..a274c22a73 100644
--- a/models/migrations/v1_20/v251.go
+++ b/models/migrations/v1_20/v251.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"code.gitea.io/gitea/modules/log"
diff --git a/models/migrations/v1_20/v252.go b/models/migrations/v1_20/v252.go
index ab61cd9b8b..d6aa602753 100644
--- a/models/migrations/v1_20/v252.go
+++ b/models/migrations/v1_20/v252.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"code.gitea.io/gitea/modules/log"
diff --git a/models/migrations/v1_20/v253.go b/models/migrations/v1_20/v253.go
index 96c494bd8d..c96454dbf9 100644
--- a/models/migrations/v1_20/v253.go
+++ b/models/migrations/v1_20/v253.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"code.gitea.io/gitea/modules/log"
diff --git a/models/migrations/v1_20/v254.go b/models/migrations/v1_20/v254.go
index 1e26979a5b..9cdbfb3916 100644
--- a/models/migrations/v1_20/v254.go
+++ b/models/migrations/v1_20/v254.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_20/v255.go b/models/migrations/v1_20/v255.go
index 14b70f8f96..caf198700e 100644
--- a/models/migrations/v1_20/v255.go
+++ b/models/migrations/v1_20/v255.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_20/v256.go b/models/migrations/v1_20/v256.go
index 822153b93e..7b84c1e154 100644
--- a/models/migrations/v1_20/v256.go
+++ b/models/migrations/v1_20/v256.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_20/v257.go b/models/migrations/v1_20/v257.go
index 6c6ca4c748..9d5f7c07df 100644
--- a/models/migrations/v1_20/v257.go
+++ b/models/migrations/v1_20/v257.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_20/v258.go b/models/migrations/v1_20/v258.go
index 47174ce805..1d3faffdae 100644
--- a/models/migrations/v1_20/v258.go
+++ b/models/migrations/v1_20/v258.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_20/v259.go b/models/migrations/v1_20/v259.go
index 5b8ced4ad7..9e0dc9b61d 100644
--- a/models/migrations/v1_20/v259.go
+++ b/models/migrations/v1_20/v259.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"fmt"
@@ -329,7 +329,7 @@ func ConvertScopedAccessTokens(x *xorm.Engine) error {
for _, token := range tokens {
var scopes []string
allNewScopesMap := make(map[AccessTokenScope]bool)
- for _, oldScope := range strings.Split(token.Scope, ",") {
+ for oldScope := range strings.SplitSeq(token.Scope, ",") {
if newScopes, exists := accessTokenScopeMap[OldAccessTokenScope(oldScope)]; exists {
for _, newScope := range newScopes {
allNewScopesMap[newScope] = true
diff --git a/models/migrations/v1_20/v259_test.go b/models/migrations/v1_20/v259_test.go
index a1aeb53d5d..0bf63719e5 100644
--- a/models/migrations/v1_20/v259_test.go
+++ b/models/migrations/v1_20/v259_test.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_20 //nolint
+package v1_20
import (
"sort"
diff --git a/models/migrations/v1_21/main_test.go b/models/migrations/v1_21/main_test.go
index 9afdea1677..536a7ade08 100644
--- a/models/migrations/v1_21/main_test.go
+++ b/models/migrations/v1_21/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"testing"
diff --git a/models/migrations/v1_21/v260.go b/models/migrations/v1_21/v260.go
index 6ca52c5998..8540c58ae8 100644
--- a/models/migrations/v1_21/v260.go
+++ b/models/migrations/v1_21/v260.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_21/v261.go b/models/migrations/v1_21/v261.go
index 4ec1160d0b..122b98eb93 100644
--- a/models/migrations/v1_21/v261.go
+++ b/models/migrations/v1_21/v261.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_21/v262.go b/models/migrations/v1_21/v262.go
index 23e900572a..6e88e29b9d 100644
--- a/models/migrations/v1_21/v262.go
+++ b/models/migrations/v1_21/v262.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v263.go b/models/migrations/v1_21/v263.go
index 2c7cbadf0d..55c418bde0 100644
--- a/models/migrations/v1_21/v263.go
+++ b/models/migrations/v1_21/v263.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"fmt"
diff --git a/models/migrations/v1_21/v264.go b/models/migrations/v1_21/v264.go
index d737ef03b3..7fc0ec6024 100644
--- a/models/migrations/v1_21/v264.go
+++ b/models/migrations/v1_21/v264.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"context"
diff --git a/models/migrations/v1_21/v265.go b/models/migrations/v1_21/v265.go
index 800eb95f72..b6892acc27 100644
--- a/models/migrations/v1_21/v265.go
+++ b/models/migrations/v1_21/v265.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v266.go b/models/migrations/v1_21/v266.go
index 79a5f5e14c..440549e868 100644
--- a/models/migrations/v1_21/v266.go
+++ b/models/migrations/v1_21/v266.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v267.go b/models/migrations/v1_21/v267.go
index bc0e954bdc..394139a17e 100644
--- a/models/migrations/v1_21/v267.go
+++ b/models/migrations/v1_21/v267.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_21/v268.go b/models/migrations/v1_21/v268.go
index 332793ff07..b677d2383e 100644
--- a/models/migrations/v1_21/v268.go
+++ b/models/migrations/v1_21/v268.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v269.go b/models/migrations/v1_21/v269.go
index 475ec02380..042040927d 100644
--- a/models/migrations/v1_21/v269.go
+++ b/models/migrations/v1_21/v269.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v270.go b/models/migrations/v1_21/v270.go
index b9cc84d3ac..ab7c5660ba 100644
--- a/models/migrations/v1_21/v270.go
+++ b/models/migrations/v1_21/v270.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go
index 098f6499d5..05e1af1351 100644
--- a/models/migrations/v1_21/v271.go
+++ b/models/migrations/v1_21/v271.go
@@ -1,7 +1,8 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
+
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_21/v272.go b/models/migrations/v1_21/v272.go
index a729c49f1b..14c1e0c4b0 100644
--- a/models/migrations/v1_21/v272.go
+++ b/models/migrations/v1_21/v272.go
@@ -1,7 +1,8 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
+
import (
"xorm.io/xorm"
)
diff --git a/models/migrations/v1_21/v273.go b/models/migrations/v1_21/v273.go
index 61c79f4a76..e614a56a7d 100644
--- a/models/migrations/v1_21/v273.go
+++ b/models/migrations/v1_21/v273.go
@@ -1,7 +1,8 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
+
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_21/v274.go b/models/migrations/v1_21/v274.go
index df5994f159..d0b557a151 100644
--- a/models/migrations/v1_21/v274.go
+++ b/models/migrations/v1_21/v274.go
@@ -1,7 +1,8 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
+
import (
"time"
diff --git a/models/migrations/v1_21/v275.go b/models/migrations/v1_21/v275.go
index 78804a59d6..2bfe5c72fa 100644
--- a/models/migrations/v1_21/v275.go
+++ b/models/migrations/v1_21/v275.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go
index 9d22c9052e..3ab7e22cd0 100644
--- a/models/migrations/v1_21/v276.go
+++ b/models/migrations/v1_21/v276.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"context"
diff --git a/models/migrations/v1_21/v277.go b/models/migrations/v1_21/v277.go
index 12529160b7..0c102eddde 100644
--- a/models/migrations/v1_21/v277.go
+++ b/models/migrations/v1_21/v277.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v278.go b/models/migrations/v1_21/v278.go
index d6a462d1e7..846f228678 100644
--- a/models/migrations/v1_21/v278.go
+++ b/models/migrations/v1_21/v278.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_21/v279.go b/models/migrations/v1_21/v279.go
index 2abd1bbe84..beb39effe1 100644
--- a/models/migrations/v1_21/v279.go
+++ b/models/migrations/v1_21/v279.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_21 //nolint
+package v1_21
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_22/main_test.go b/models/migrations/v1_22/main_test.go
index efd8dbaa8c..ac8facd6aa 100644
--- a/models/migrations/v1_22/main_test.go
+++ b/models/migrations/v1_22/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"testing"
diff --git a/models/migrations/v1_22/v280.go b/models/migrations/v1_22/v280.go
index a8ee4a3bf7..2271cb6089 100644
--- a/models/migrations/v1_22/v280.go
+++ b/models/migrations/v1_22/v280.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_22/v281.go b/models/migrations/v1_22/v281.go
index fc1866aa83..129ec2cba0 100644
--- a/models/migrations/v1_22/v281.go
+++ b/models/migrations/v1_22/v281.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_22/v282.go b/models/migrations/v1_22/v282.go
index baad9e0916..eed64c30f7 100644
--- a/models/migrations/v1_22/v282.go
+++ b/models/migrations/v1_22/v282.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go
index 0a45c51245..0eca031b65 100644
--- a/models/migrations/v1_22/v283.go
+++ b/models/migrations/v1_22/v283.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"fmt"
diff --git a/models/migrations/v1_22/v283_test.go b/models/migrations/v1_22/v283_test.go
index e89a7cbfc2..743f860466 100644
--- a/models/migrations/v1_22/v283_test.go
+++ b/models/migrations/v1_22/v283_test.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"testing"
diff --git a/models/migrations/v1_22/v284.go b/models/migrations/v1_22/v284.go
index 2b95078980..31b38f6aed 100644
--- a/models/migrations/v1_22/v284.go
+++ b/models/migrations/v1_22/v284.go
@@ -1,7 +1,8 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
+
import (
"xorm.io/xorm"
)
diff --git a/models/migrations/v1_22/v285.go b/models/migrations/v1_22/v285.go
index a55cc17c04..fed89f670e 100644
--- a/models/migrations/v1_22/v285.go
+++ b/models/migrations/v1_22/v285.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"time"
diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go
index 1fcde33202..f3ba50dbb6 100644
--- a/models/migrations/v1_22/v286.go
+++ b/models/migrations/v1_22/v286.go
@@ -1,6 +1,6 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"errors"
diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go
index 4702e4c37c..b4a50f6fcb 100644
--- a/models/migrations/v1_22/v286_test.go
+++ b/models/migrations/v1_22/v286_test.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"testing"
diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go
index c8b1593286..5fd901f9de 100644
--- a/models/migrations/v1_22/v287.go
+++ b/models/migrations/v1_22/v287.go
@@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_22/v287_test.go b/models/migrations/v1_22/v287_test.go
index 58c3152ac3..2b42a33c38 100644
--- a/models/migrations/v1_22/v287_test.go
+++ b/models/migrations/v1_22/v287_test.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"strconv"
diff --git a/models/migrations/v1_22/v288.go b/models/migrations/v1_22/v288.go
index 7c93bfcc66..26c850c218 100644
--- a/models/migrations/v1_22/v288.go
+++ b/models/migrations/v1_22/v288.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_22/v289.go b/models/migrations/v1_22/v289.go
index b9941aadd9..78689a4ffa 100644
--- a/models/migrations/v1_22/v289.go
+++ b/models/migrations/v1_22/v289.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import "xorm.io/xorm"
diff --git a/models/migrations/v1_22/v290.go b/models/migrations/v1_22/v290.go
index 9c54d4e87c..0f4d78410c 100644
--- a/models/migrations/v1_22/v290.go
+++ b/models/migrations/v1_22/v290.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_22/v291.go b/models/migrations/v1_22/v291.go
index 74726fae96..823a644a95 100644
--- a/models/migrations/v1_22/v291.go
+++ b/models/migrations/v1_22/v291.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import "xorm.io/xorm"
diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go
index beca556aee..440f48ce80 100644
--- a/models/migrations/v1_22/v292.go
+++ b/models/migrations/v1_22/v292.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
// NOTE: noop the original migration has bug which some projects will be skip, so
// these projects will have no default board.
diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go
index 53cc719294..5299b8618f 100644
--- a/models/migrations/v1_22/v293.go
+++ b/models/migrations/v1_22/v293.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_22/v293_test.go b/models/migrations/v1_22/v293_test.go
index cfe4345143..2c8f7683a8 100644
--- a/models/migrations/v1_22/v293_test.go
+++ b/models/migrations/v1_22/v293_test.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"testing"
diff --git a/models/migrations/v1_22/v294.go b/models/migrations/v1_22/v294.go
index 20e261fb1b..8776e51a16 100644
--- a/models/migrations/v1_22/v294.go
+++ b/models/migrations/v1_22/v294.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"fmt"
diff --git a/models/migrations/v1_22/v294_test.go b/models/migrations/v1_22/v294_test.go
index a1d702cb77..1cf03d6120 100644
--- a/models/migrations/v1_22/v294_test.go
+++ b/models/migrations/v1_22/v294_test.go
@@ -1,10 +1,9 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
- "slices"
"testing"
"code.gitea.io/gitea/models/migrations/base"
@@ -44,7 +43,7 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
for _, index := range tables[0].Indexes {
if index.Type == schemas.UniqueType {
found = true
- slices.Equal(index.Cols, []string{"project_id", "issue_id"})
+ assert.ElementsMatch(t, index.Cols, []string{"project_id", "issue_id"})
break
}
}
diff --git a/models/migrations/v1_22/v295.go b/models/migrations/v1_22/v295.go
index 17bdadb4ad..319b1a399b 100644
--- a/models/migrations/v1_22/v295.go
+++ b/models/migrations/v1_22/v295.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import "xorm.io/xorm"
diff --git a/models/migrations/v1_22/v296.go b/models/migrations/v1_22/v296.go
index 1ecacab95f..75350f9f65 100644
--- a/models/migrations/v1_22/v296.go
+++ b/models/migrations/v1_22/v296.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import "xorm.io/xorm"
diff --git a/models/migrations/v1_22/v297.go b/models/migrations/v1_22/v297.go
index 7d4b506925..9a4405f266 100644
--- a/models/migrations/v1_22/v297.go
+++ b/models/migrations/v1_22/v297.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import (
"code.gitea.io/gitea/models/perm"
diff --git a/models/migrations/v1_22/v298.go b/models/migrations/v1_22/v298.go
index b9f3b95ade..7700173a00 100644
--- a/models/migrations/v1_22/v298.go
+++ b/models/migrations/v1_22/v298.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_22 //nolint
+package v1_22
import "xorm.io/xorm"
diff --git a/models/migrations/v1_23/main_test.go b/models/migrations/v1_23/main_test.go
index b7948bd4dd..f7b2caed83 100644
--- a/models/migrations/v1_23/main_test.go
+++ b/models/migrations/v1_23/main_test.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"testing"
diff --git a/models/migrations/v1_23/v299.go b/models/migrations/v1_23/v299.go
index e5fde3749b..11021d8855 100644
--- a/models/migrations/v1_23/v299.go
+++ b/models/migrations/v1_23/v299.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import "xorm.io/xorm"
diff --git a/models/migrations/v1_23/v300.go b/models/migrations/v1_23/v300.go
index 51de43da5e..13c6489c5e 100644
--- a/models/migrations/v1_23/v300.go
+++ b/models/migrations/v1_23/v300.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import "xorm.io/xorm"
diff --git a/models/migrations/v1_23/v301.go b/models/migrations/v1_23/v301.go
index 99c8e3d8ea..ed8e9ef059 100644
--- a/models/migrations/v1_23/v301.go
+++ b/models/migrations/v1_23/v301.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import "xorm.io/xorm"
diff --git a/models/migrations/v1_23/v302.go b/models/migrations/v1_23/v302.go
index d7ea03eb3d..e4a50b3ec8 100644
--- a/models/migrations/v1_23/v302.go
+++ b/models/migrations/v1_23/v302.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"code.gitea.io/gitea/modules/timeutil"
@@ -14,5 +14,8 @@ func AddIndexToActionTaskStoppedLogExpired(x *xorm.Engine) error {
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
LogExpired bool `xorm:"index(stopped_log_expired)"`
}
- return x.Sync(new(ActionTask))
+ _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreDropIndices: true,
+ }, new(ActionTask))
+ return err
}
diff --git a/models/migrations/v1_23/v302_test.go b/models/migrations/v1_23/v302_test.go
new file mode 100644
index 0000000000..b008b6fc03
--- /dev/null
+++ b/models/migrations/v1_23/v302_test.go
@@ -0,0 +1,51 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_AddIndexToActionTaskStoppedLogExpired(t *testing.T) {
+ type ActionTask struct {
+ ID int64
+ JobID int64
+ Attempt int64
+ RunnerID int64 `xorm:"index"`
+ Status int `xorm:"index"`
+ Started timeutil.TimeStamp `xorm:"index"`
+ Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
+
+ RepoID int64 `xorm:"index"`
+ OwnerID int64 `xorm:"index"`
+ CommitSHA string `xorm:"index"`
+ IsForkPullRequest bool
+
+ Token string `xorm:"-"`
+ TokenHash string `xorm:"UNIQUE"` // sha256 of token
+ TokenSalt string
+ TokenLastEight string `xorm:"index token_last_eight"`
+
+ LogFilename string // file name of log
+ LogInStorage bool // read log from database or from storage
+ LogLength int64 // lines count
+ LogSize int64 // blob size
+ LogIndexes []int64 `xorm:"LONGBLOB"` // line number to offset
+ LogExpired bool `xorm:"index(stopped_log_expired)"` // files that are too old will be deleted
+
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated index"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(ActionTask))
+ defer deferable()
+
+ assert.NoError(t, AddIndexToActionTaskStoppedLogExpired(x))
+}
diff --git a/models/migrations/v1_23/v303.go b/models/migrations/v1_23/v303.go
index 1e36388930..dc541a9535 100644
--- a/models/migrations/v1_23/v303.go
+++ b/models/migrations/v1_23/v303.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_23/v304.go b/models/migrations/v1_23/v304.go
index 65cffedbd9..35d4d4881a 100644
--- a/models/migrations/v1_23/v304.go
+++ b/models/migrations/v1_23/v304.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import "xorm.io/xorm"
@@ -9,5 +9,8 @@ func AddIndexForReleaseSha1(x *xorm.Engine) error {
type Release struct {
Sha1 string `xorm:"INDEX VARCHAR(64)"`
}
- return x.Sync(new(Release))
+ _, err := x.SyncWithOptions(xorm.SyncOptions{
+ IgnoreDropIndices: true,
+ }, new(Release))
+ return err
}
diff --git a/models/migrations/v1_23/v304_test.go b/models/migrations/v1_23/v304_test.go
new file mode 100644
index 0000000000..c3dfa5e7e7
--- /dev/null
+++ b/models/migrations/v1_23/v304_test.go
@@ -0,0 +1,40 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_AddIndexForReleaseSha1(t *testing.T) {
+ type Release struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX UNIQUE(n)"`
+ PublisherID int64 `xorm:"INDEX"`
+ TagName string `xorm:"INDEX UNIQUE(n)"`
+ OriginalAuthor string
+ OriginalAuthorID int64 `xorm:"index"`
+ LowerTagName string
+ Target string
+ Title string
+ Sha1 string `xorm:"VARCHAR(64)"`
+ NumCommits int64
+ Note string `xorm:"TEXT"`
+ IsDraft bool `xorm:"NOT NULL DEFAULT false"`
+ IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
+ IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(Release))
+ defer deferable()
+
+ assert.NoError(t, AddIndexForReleaseSha1(x))
+}
diff --git a/models/migrations/v1_23/v305.go b/models/migrations/v1_23/v305.go
index 4d881192b2..3762279de1 100644
--- a/models/migrations/v1_23/v305.go
+++ b/models/migrations/v1_23/v305.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_23/v306.go b/models/migrations/v1_23/v306.go
index a1e698fe31..c5c89dbeb8 100644
--- a/models/migrations/v1_23/v306.go
+++ b/models/migrations/v1_23/v306.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import "xorm.io/xorm"
diff --git a/models/migrations/v1_23/v307.go b/models/migrations/v1_23/v307.go
index ef7f5f2c3f..54a69d250b 100644
--- a/models/migrations/v1_23/v307.go
+++ b/models/migrations/v1_23/v307.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_23/v308.go b/models/migrations/v1_23/v308.go
index 1e8a9b0af2..695fdfcc2d 100644
--- a/models/migrations/v1_23/v308.go
+++ b/models/migrations/v1_23/v308.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_23/v309.go b/models/migrations/v1_23/v309.go
index 5b39398443..e629b718a8 100644
--- a/models/migrations/v1_23/v309.go
+++ b/models/migrations/v1_23/v309.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_23/v310.go b/models/migrations/v1_23/v310.go
index c856a708f9..074b1c54d3 100644
--- a/models/migrations/v1_23/v310.go
+++ b/models/migrations/v1_23/v310.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_23/v311.go b/models/migrations/v1_23/v311.go
index 21293d83be..ef48085c79 100644
--- a/models/migrations/v1_23/v311.go
+++ b/models/migrations/v1_23/v311.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_23 //nolint
+package v1_23
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_24/v312.go b/models/migrations/v1_24/v312.go
index 367a6c4947..823b0eae40 100644
--- a/models/migrations/v1_24/v312.go
+++ b/models/migrations/v1_24/v312.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_24/v313.go b/models/migrations/v1_24/v313.go
index ee9d479340..7e6cda6bfd 100644
--- a/models/migrations/v1_24/v313.go
+++ b/models/migrations/v1_24/v313.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_24/v314.go b/models/migrations/v1_24/v314.go
index e537be13b5..51cb2e34aa 100644
--- a/models/migrations/v1_24/v314.go
+++ b/models/migrations/v1_24/v314.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_24/v315.go b/models/migrations/v1_24/v315.go
index 22a72c31e9..52b9b44785 100644
--- a/models/migrations/v1_24/v315.go
+++ b/models/migrations/v1_24/v315.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_24/v316.go b/models/migrations/v1_24/v316.go
index e7f04333cc..14e888f9ee 100644
--- a/models/migrations/v1_24/v316.go
+++ b/models/migrations/v1_24/v316.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_24/v317.go b/models/migrations/v1_24/v317.go
index 3da5a4a078..a13db2dd27 100644
--- a/models/migrations/v1_24/v317.go
+++ b/models/migrations/v1_24/v317.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_24/v318.go b/models/migrations/v1_24/v318.go
index 3e08c3d504..9b4a540960 100644
--- a/models/migrations/v1_24/v318.go
+++ b/models/migrations/v1_24/v318.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"code.gitea.io/gitea/models/perm"
diff --git a/models/migrations/v1_24/v319.go b/models/migrations/v1_24/v319.go
index 6571ddf75b..648081f74e 100644
--- a/models/migrations/v1_24/v319.go
+++ b/models/migrations/v1_24/v319.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_24/v320.go b/models/migrations/v1_24/v320.go
index 1d34444826..ebef71939c 100644
--- a/models/migrations/v1_24/v320.go
+++ b/models/migrations/v1_24/v320.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_24 //nolint
+package v1_24
import (
"code.gitea.io/gitea/modules/json"
diff --git a/models/migrations/v1_6/v70.go b/models/migrations/v1_6/v70.go
index 74434a84a1..41f0966942 100644
--- a/models/migrations/v1_6/v70.go
+++ b/models/migrations/v1_6/v70.go
@@ -1,7 +1,7 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_6 //nolint
+package v1_6
import (
"fmt"
diff --git a/models/migrations/v1_6/v71.go b/models/migrations/v1_6/v71.go
index 586187228b..2b11f57c92 100644
--- a/models/migrations/v1_6/v71.go
+++ b/models/migrations/v1_6/v71.go
@@ -1,7 +1,7 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_6 //nolint
+package v1_6
import (
"fmt"
diff --git a/models/migrations/v1_6/v72.go b/models/migrations/v1_6/v72.go
index 04cef9a170..9fad88a1b6 100644
--- a/models/migrations/v1_6/v72.go
+++ b/models/migrations/v1_6/v72.go
@@ -1,7 +1,7 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_6 //nolint
+package v1_6
import (
"fmt"
diff --git a/models/migrations/v1_7/v73.go b/models/migrations/v1_7/v73.go
index b5a748aae3..e0b7a28537 100644
--- a/models/migrations/v1_7/v73.go
+++ b/models/migrations/v1_7/v73.go
@@ -1,7 +1,7 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_7 //nolint
+package v1_7
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_7/v74.go b/models/migrations/v1_7/v74.go
index f0567e3c9b..376be37a24 100644
--- a/models/migrations/v1_7/v74.go
+++ b/models/migrations/v1_7/v74.go
@@ -1,7 +1,7 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_7 //nolint
+package v1_7
import "xorm.io/xorm"
diff --git a/models/migrations/v1_7/v75.go b/models/migrations/v1_7/v75.go
index fa7430970c..ef11575466 100644
--- a/models/migrations/v1_7/v75.go
+++ b/models/migrations/v1_7/v75.go
@@ -1,7 +1,7 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_7 //nolint
+package v1_7
import (
"xorm.io/builder"
diff --git a/models/migrations/v1_8/v76.go b/models/migrations/v1_8/v76.go
index d3fbd94deb..81e9307549 100644
--- a/models/migrations/v1_8/v76.go
+++ b/models/migrations/v1_8/v76.go
@@ -1,7 +1,7 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_8 //nolint
+package v1_8
import (
"fmt"
diff --git a/models/migrations/v1_8/v77.go b/models/migrations/v1_8/v77.go
index 8b19993924..4fe5ebe635 100644
--- a/models/migrations/v1_8/v77.go
+++ b/models/migrations/v1_8/v77.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_8 //nolint
+package v1_8
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_8/v78.go b/models/migrations/v1_8/v78.go
index 8f041c1484..e67f464131 100644
--- a/models/migrations/v1_8/v78.go
+++ b/models/migrations/v1_8/v78.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_8 //nolint
+package v1_8
import (
"code.gitea.io/gitea/models/migrations/base"
diff --git a/models/migrations/v1_8/v79.go b/models/migrations/v1_8/v79.go
index eb3a9ed0f4..3f50114d5a 100644
--- a/models/migrations/v1_8/v79.go
+++ b/models/migrations/v1_8/v79.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_8 //nolint
+package v1_8
import (
"code.gitea.io/gitea/modules/setting"
diff --git a/models/migrations/v1_8/v80.go b/models/migrations/v1_8/v80.go
index cebbbead28..6f9df47a93 100644
--- a/models/migrations/v1_8/v80.go
+++ b/models/migrations/v1_8/v80.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_8 //nolint
+package v1_8
import "xorm.io/xorm"
diff --git a/models/migrations/v1_8/v81.go b/models/migrations/v1_8/v81.go
index a100dc1ef7..3c2acc6458 100644
--- a/models/migrations/v1_8/v81.go
+++ b/models/migrations/v1_8/v81.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_8 //nolint
+package v1_8
import (
"fmt"
diff --git a/models/migrations/v1_9/v82.go b/models/migrations/v1_9/v82.go
index 26806dd645..c685d3b86b 100644
--- a/models/migrations/v1_9/v82.go
+++ b/models/migrations/v1_9/v82.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_9 //nolint
+package v1_9
import (
"fmt"
diff --git a/models/migrations/v1_9/v83.go b/models/migrations/v1_9/v83.go
index 10e6c45875..a0cd57f7c5 100644
--- a/models/migrations/v1_9/v83.go
+++ b/models/migrations/v1_9/v83.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_9 //nolint
+package v1_9
import (
"code.gitea.io/gitea/modules/timeutil"
diff --git a/models/migrations/v1_9/v84.go b/models/migrations/v1_9/v84.go
index c7155fe9cf..423915ae57 100644
--- a/models/migrations/v1_9/v84.go
+++ b/models/migrations/v1_9/v84.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_9 //nolint
+package v1_9
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_9/v85.go b/models/migrations/v1_9/v85.go
index a23d7c5d6e..48e1cd5dc4 100644
--- a/models/migrations/v1_9/v85.go
+++ b/models/migrations/v1_9/v85.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_9 //nolint
+package v1_9
import (
"fmt"
diff --git a/models/migrations/v1_9/v86.go b/models/migrations/v1_9/v86.go
index cf2725d158..9464ff0cf6 100644
--- a/models/migrations/v1_9/v86.go
+++ b/models/migrations/v1_9/v86.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_9 //nolint
+package v1_9
import (
"xorm.io/xorm"
diff --git a/models/migrations/v1_9/v87.go b/models/migrations/v1_9/v87.go
index fa01b6e5e3..81a4ebf80d 100644
--- a/models/migrations/v1_9/v87.go
+++ b/models/migrations/v1_9/v87.go
@@ -1,7 +1,7 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package v1_9 //nolint
+package v1_9
import (
"xorm.io/xorm"
diff --git a/models/organization/org.go b/models/organization/org.go
index dc889ea17f..0f3aef146c 100644
--- a/models/organization/org.go
+++ b/models/organization/org.go
@@ -602,8 +602,3 @@ func getUserTeamIDsQueryBuilder(orgID, userID int64) *builder.Builder {
"team_user.uid": userID,
})
}
-
-// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
-func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
- return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)
-}
diff --git a/models/organization/org_list.go b/models/organization/org_list.go
index 78ac0e704a..81457191fe 100644
--- a/models/organization/org_list.go
+++ b/models/organization/org_list.go
@@ -50,8 +50,8 @@ type SearchOrganizationsOptions struct {
// FindOrgOptions finds orgs options
type FindOrgOptions struct {
db.ListOptions
- UserID int64
- IncludePrivate bool
+ UserID int64
+ IncludeVisibility structs.VisibleType
}
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
@@ -65,11 +65,10 @@ func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
func (opts FindOrgOptions) ToConds() builder.Cond {
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
if opts.UserID > 0 {
- cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
- }
- if !opts.IncludePrivate {
- cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
+ cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludeVisibility == structs.VisibleTypePrivate)))
}
+ // public=0, limited=1, private=2
+ cond = cond.And(builder.Lte{"`user`.visibility": opts.IncludeVisibility})
return cond
}
@@ -77,6 +76,16 @@ func (opts FindOrgOptions) ToOrders() string {
return "`user`.lower_name ASC"
}
+func DoerViewOtherVisibility(doer, other *user_model.User) structs.VisibleType {
+ if doer == nil || other == nil {
+ return structs.VisibleTypePublic
+ }
+ if doer.IsAdmin || doer.ID == other.ID {
+ return structs.VisibleTypePrivate
+ }
+ return structs.VisibleTypeLimited
+}
+
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
// are allowed to create repos.
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
diff --git a/models/organization/org_list_test.go b/models/organization/org_list_test.go
index e859d87c84..a2a25c6f91 100644
--- a/models/organization/org_list_test.go
+++ b/models/organization/org_list_test.go
@@ -10,25 +10,32 @@ import (
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
-func TestCountOrganizations(t *testing.T) {
+func TestOrgList(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("CountOrganizations", testCountOrganizations)
+ t.Run("FindOrgs", testFindOrgs)
+ t.Run("GetUserOrgsList", testGetUserOrgsList)
+ t.Run("LoadOrgListTeams", testLoadOrgListTeams)
+ t.Run("DoerViewOtherVisibility", testDoerViewOtherVisibility)
+}
+
+func testCountOrganizations(t *testing.T) {
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
assert.NoError(t, err)
- cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
+ cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludeVisibility: structs.VisibleTypePrivate})
assert.NoError(t, err)
assert.Equal(t, expected, cnt)
}
-func TestFindOrgs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
+func testFindOrgs(t *testing.T) {
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
- UserID: 4,
- IncludePrivate: true,
+ UserID: 4,
+ IncludeVisibility: structs.VisibleTypePrivate,
})
assert.NoError(t, err)
if assert.Len(t, orgs, 1) {
@@ -36,22 +43,20 @@ func TestFindOrgs(t *testing.T) {
}
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
- UserID: 4,
- IncludePrivate: false,
+ UserID: 4,
})
assert.NoError(t, err)
assert.Empty(t, orgs)
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
- UserID: 4,
- IncludePrivate: true,
+ UserID: 4,
+ IncludeVisibility: structs.VisibleTypePrivate,
})
assert.NoError(t, err)
assert.EqualValues(t, 1, total)
}
-func TestGetUserOrgsList(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+func testGetUserOrgsList(t *testing.T) {
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
assert.NoError(t, err)
if assert.Len(t, orgs, 1) {
@@ -61,8 +66,7 @@ func TestGetUserOrgsList(t *testing.T) {
}
}
-func TestLoadOrgListTeams(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
+func testLoadOrgListTeams(t *testing.T) {
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
assert.NoError(t, err)
assert.Len(t, orgs, 1)
@@ -71,3 +75,10 @@ func TestLoadOrgListTeams(t *testing.T) {
assert.Len(t, teamsMap, 1)
assert.Len(t, teamsMap[3], 5)
}
+
+func testDoerViewOtherVisibility(t *testing.T) {
+ assert.Equal(t, structs.VisibleTypePublic, organization.DoerViewOtherVisibility(nil, nil))
+ assert.Equal(t, structs.VisibleTypeLimited, organization.DoerViewOtherVisibility(&user_model.User{ID: 1}, &user_model.User{ID: 2}))
+ assert.Equal(t, structs.VisibleTypePrivate, organization.DoerViewOtherVisibility(&user_model.User{ID: 1}, &user_model.User{ID: 1}))
+ assert.Equal(t, structs.VisibleTypePrivate, organization.DoerViewOtherVisibility(&user_model.User{ID: 1, IsAdmin: true}, &user_model.User{ID: 2}))
+}
diff --git a/models/organization/org_test.go b/models/organization/org_test.go
index 666a6c44d4..234325a8cd 100644
--- a/models/organization/org_test.go
+++ b/models/organization/org_test.go
@@ -334,7 +334,7 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
testSuccess := func(userID int64, expectedRepoIDs []int64) {
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
assert.NoError(t, err)
- repoIDs, err := env.RepoIDs(db.DefaultContext, 1, 100)
+ repoIDs, err := env.RepoIDs(db.DefaultContext)
assert.NoError(t, err)
assert.Equal(t, expectedRepoIDs, repoIDs)
}
@@ -342,25 +342,6 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
testSuccess(4, []int64{3, 32})
}
-func TestAccessibleReposEnv_Repos(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
- testSuccess := func(userID int64, expectedRepoIDs []int64) {
- env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
- assert.NoError(t, err)
- repos, err := env.Repos(db.DefaultContext, 1, 100)
- assert.NoError(t, err)
- expectedRepos := make(repo_model.RepositoryList, len(expectedRepoIDs))
- for i, repoID := range expectedRepoIDs {
- expectedRepos[i] = unittest.AssertExistsAndLoadBean(t,
- &repo_model.Repository{ID: repoID})
- }
- assert.Equal(t, expectedRepos, repos)
- }
- testSuccess(2, []int64{3, 5, 32})
- testSuccess(4, []int64{3, 32})
-}
-
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
diff --git a/models/organization/team_repo.go b/models/organization/team_repo.go
index 53edd203a8..b3e266dbc7 100644
--- a/models/organization/team_repo.go
+++ b/models/organization/team_repo.go
@@ -9,6 +9,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
+
+ "xorm.io/builder"
)
// TeamRepo represents an team-repository relation.
@@ -48,26 +50,27 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
return err
}
-// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository.
-func GetTeamsWithAccessToRepo(ctx context.Context, orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) {
+// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
+// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
+// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
+func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) {
teams := make([]*Team, 0, 5)
- return teams, db.GetEngine(ctx).Where("team.authorize >= ?", mode).
- Join("INNER", "team_repo", "team_repo.team_id = team.id").
- And("team_repo.org_id = ?", orgID).
- And("team_repo.repo_id = ?", repoID).
- OrderBy("name").
- Find(&teams)
-}
-// GetTeamsWithAccessToRepoUnit returns all teams in an organization that have given access level to the repository special unit.
-func GetTeamsWithAccessToRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type) ([]*Team, error) {
- teams := make([]*Team, 0, 5)
- return teams, db.GetEngine(ctx).Where("team_unit.access_mode >= ?", mode).
+ sub := builder.Select("team_id").From("team_unit").
+ Where(builder.Expr("team_unit.team_id = team.id")).
+ And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
+ And(builder.Expr("team_unit.access_mode >= ?", mode))
+
+ err := db.GetEngine(ctx).
Join("INNER", "team_repo", "team_repo.team_id = team.id").
- Join("INNER", "team_unit", "team_unit.team_id = team.id").
And("team_repo.org_id = ?", orgID).
And("team_repo.repo_id = ?", repoID).
- And("team_unit.type = ?", unitType).
+ And(builder.Or(
+ builder.Expr("team.authorize >= ?", mode),
+ builder.In("team.id", sub),
+ )).
OrderBy("name").
Find(&teams)
+
+ return teams, err
}
diff --git a/models/organization/team_repo_test.go b/models/organization/team_repo_test.go
index c0d6750df9..73a06a93c2 100644
--- a/models/organization/team_repo_test.go
+++ b/models/organization/team_repo_test.go
@@ -22,7 +22,7 @@ func TestGetTeamsWithAccessToRepoUnit(t *testing.T) {
org41 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 41})
repo61 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 61})
- teams, err := organization.GetTeamsWithAccessToRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
+ teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
assert.NoError(t, err)
if assert.Len(t, teams, 2) {
assert.EqualValues(t, 21, teams[0].ID)
diff --git a/models/packages/container/search.go b/models/packages/container/search.go
index 5df35117ce..9321d9eb41 100644
--- a/models/packages/container/search.go
+++ b/models/packages/container/search.go
@@ -25,6 +25,7 @@ type BlobSearchOptions struct {
Digest string
Tag string
IsManifest bool
+ OnlyLead bool
Repository string
}
@@ -43,7 +44,10 @@ func (opts *BlobSearchOptions) toConds() builder.Cond {
cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Tag)})
}
if opts.IsManifest {
- cond = cond.And(builder.Eq{"package_file.lower_name": ManifestFilename})
+ cond = cond.And(builder.Eq{"package_file.lower_name": container_module.ManifestFilename})
+ }
+ if opts.OnlyLead {
+ cond = cond.And(builder.Eq{"package_file.is_lead": true})
}
if opts.Digest != "" {
var propsCond builder.Cond = builder.Eq{
@@ -73,11 +77,9 @@ func GetContainerBlob(ctx context.Context, opts *BlobSearchOptions) (*packages.P
pfds, err := getContainerBlobsLimit(ctx, opts, 1)
if err != nil {
return nil, err
- }
- if len(pfds) != 1 {
+ } else if len(pfds) == 0 {
return nil, ErrContainerBlobNotExist
}
-
return pfds[0], nil
}
@@ -233,7 +235,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack
func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
var cond builder.Cond = builder.Eq{
"package_version.is_internal": true,
- "package_version.lower_version": UploadVersion,
+ "package_version.lower_version": container_module.UploadVersion,
"package.type": packages.TypeContainer,
}
cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()})
diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go
index 1ea181c723..2d43dc3046 100644
--- a/models/packages/descriptor.go
+++ b/models/packages/descriptor.go
@@ -103,10 +103,10 @@ func (pd *PackageDescriptor) CalculateBlobSize() int64 {
// GetPackageDescriptor gets the package description for a version
func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) {
- return getPackageDescriptor(ctx, pv, cache.NewEphemeralCache())
+ return GetPackageDescriptorWithCache(ctx, pv, cache.NewEphemeralCache())
}
-func getPackageDescriptor(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) {
+func GetPackageDescriptorWithCache(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) {
p, err := cache.GetWithEphemeralCache(ctx, c, "package", pv.PackageID, GetPackageByID)
if err != nil {
return nil, err
@@ -270,7 +270,7 @@ func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*Packa
func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) {
pds := make([]*PackageDescriptor, 0, len(pvs))
for _, pv := range pvs {
- pd, err := getPackageDescriptor(ctx, pv, c)
+ pd, err := GetPackageDescriptorWithCache(ctx, pv, c)
if err != nil {
return nil, err
}
diff --git a/models/packages/nuget/search.go b/models/packages/nuget/search.go
index 7a505ff08f..a4b23f31d5 100644
--- a/models/packages/nuget/search.go
+++ b/models/packages/nuget/search.go
@@ -33,7 +33,7 @@ func SearchVersions(ctx context.Context, opts *packages_model.PackageSearchOptio
Where(cond).
OrderBy("package.name ASC")
if opts.Paginator != nil {
- skip, take := opts.GetSkipTake()
+ skip, take := opts.Paginator.GetSkipTake()
inner = inner.Limit(take, skip)
}
diff --git a/models/packages/package_file.go b/models/packages/package_file.go
index 270cb32fdf..bf877485d6 100644
--- a/models/packages/package_file.go
+++ b/models/packages/package_file.go
@@ -115,6 +115,11 @@ func DeleteFileByID(ctx context.Context, fileID int64) error {
return err
}
+func UpdateFile(ctx context.Context, pf *PackageFile, cols []string) error {
+ _, err := db.GetEngine(ctx).ID(pf.ID).Cols(cols...).Update(pf)
+ return err
+}
+
// PackageFileSearchOptions are options for SearchXXX methods
type PackageFileSearchOptions struct {
OwnerID int64
diff --git a/models/packages/package_property.go b/models/packages/package_property.go
index e0170016cf..7ddbfd97e9 100644
--- a/models/packages/package_property.go
+++ b/models/packages/package_property.go
@@ -66,6 +66,20 @@ func UpdateProperty(ctx context.Context, pp *PackageProperty) error {
return err
}
+func InsertOrUpdateProperty(ctx context.Context, refType PropertyType, refID int64, name, value string) error {
+ pp := PackageProperty{RefType: refType, RefID: refID, Name: name}
+ ok, err := db.GetEngine(ctx).Get(&pp)
+ if err != nil {
+ return err
+ }
+ if ok {
+ _, err = db.GetEngine(ctx).Where("ref_type=? AND ref_id=? AND name=?", refType, refID, name).Cols("value").Update(&PackageProperty{Value: value})
+ return err
+ }
+ _, err = InsertProperty(ctx, refType, refID, name, value)
+ return err
+}
+
// DeleteAllProperties deletes all properties of a ref
func DeleteAllProperties(ctx context.Context, refType PropertyType, refID int64) error {
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ?", refType, refID).Delete(&PackageProperty{})
@@ -78,8 +92,8 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error {
return err
}
-// DeletePropertyByName deletes properties by name
-func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
+// DeletePropertiesByName deletes properties by name
+func DeletePropertiesByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
return err
}
diff --git a/models/packages/package_version.go b/models/packages/package_version.go
index bb7fd895f8..5672e0efbf 100644
--- a/models/packages/package_version.go
+++ b/models/packages/package_version.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
+ "xorm.io/xorm"
)
// ErrDuplicatePackageVersion indicates a duplicated package version error
@@ -187,7 +188,7 @@ type PackageSearchOptions struct {
HasFileWithName string // only results are found which are associated with a file with the specific name
HasFiles optional.Option[bool] // only results are found which have associated files
Sort VersionSort
- db.Paginator
+ Paginator db.Paginator
}
func (opts *PackageSearchOptions) ToConds() builder.Cond {
@@ -282,6 +283,18 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
}
+func searchVersionsBySession(sess *xorm.Session, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
+ opts.configureOrderBy(sess)
+ pvs := make([]*PackageVersion, 0, 10)
+ if opts.Paginator != nil {
+ sess = db.SetSessionPagination(sess, opts.Paginator)
+ count, err := sess.FindAndCount(&pvs)
+ return pvs, count, err
+ }
+ err := sess.Find(&pvs)
+ return pvs, int64(len(pvs)), err
+}
+
// SearchVersions gets all versions of packages matching the search options
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
sess := db.GetEngine(ctx).
@@ -289,16 +302,7 @@ func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*Package
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Where(opts.ToConds())
-
- opts.configureOrderBy(sess)
-
- if opts.Paginator != nil {
- sess = db.SetSessionPagination(sess, opts)
- }
-
- pvs := make([]*PackageVersion, 0, 10)
- count, err := sess.FindAndCount(&pvs)
- return pvs, count, err
+ return searchVersionsBySession(sess, opts)
}
// SearchLatestVersions gets the latest version of every package matching the search options
@@ -316,15 +320,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
Join("INNER", "package", "package.id = package_version.package_id").
Where(builder.In("package_version.id", in))
- opts.configureOrderBy(sess)
-
- if opts.Paginator != nil {
- sess = db.SetSessionPagination(sess, opts)
- }
-
- pvs := make([]*PackageVersion, 0, 10)
- count, err := sess.FindAndCount(&pvs)
- return pvs, count, err
+ return searchVersionsBySession(sess, opts)
}
// ExistVersion checks if a version matching the search options exist
diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go
index 45efb192c8..7de43ecd07 100644
--- a/models/perm/access/repo_permission.go
+++ b/models/perm/access/repo_permission.go
@@ -42,6 +42,7 @@ func (p *Permission) IsAdmin() bool {
// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
// It doesn't count the "public(anonymous/everyone) access mode".
+// TODO: most calls to this function should be replaced with `HasAnyUnitAccessOrPublicAccess`
func (p *Permission) HasAnyUnitAccess() bool {
for _, v := range p.unitsMode {
if v >= perm_model.AccessModeRead {
@@ -267,7 +268,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
perm.units = repo.Units
// anonymous user visit private repo.
- // TODO: anonymous user visit public unit of private repo???
if user == nil && repo.IsPrivate {
perm.AccessMode = perm_model.AccessModeNone
return perm, nil
@@ -286,7 +286,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
}
// Prevent strangers from checking out public repo of private organization/users
- // Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself
+ // Allow user if they are a collaborator of a repo within a private user or a private organization but not a member of the organization itself
+ // TODO: rename it to "IsOwnerVisibleToDoer"
if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator {
perm.AccessMode = perm_model.AccessModeNone
return perm, nil
@@ -304,7 +305,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
return perm, nil
}
- // plain user
+ // plain user TODO: this check should be replaced, only need to check collaborator access mode
perm.AccessMode, err = accessLevel(ctx, user, repo)
if err != nil {
return perm, err
@@ -314,6 +315,19 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
return perm, nil
}
+ // now: the owner is visible to doer, if the repo is public, then the min access mode is read
+ minAccessMode := util.Iif(!repo.IsPrivate && !user.IsRestricted, perm_model.AccessModeRead, perm_model.AccessModeNone)
+ perm.AccessMode = max(perm.AccessMode, minAccessMode)
+
+ // get units mode from teams
+ teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
+ if err != nil {
+ return perm, err
+ }
+ if len(teams) == 0 {
+ return perm, nil
+ }
+
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
// Collaborators on organization
@@ -323,12 +337,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
}
}
- // get units mode from teams
- teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
- if err != nil {
- return perm, err
- }
-
// if user in an owner team
for _, team := range teams {
if team.HasAdminAccess() {
@@ -339,19 +347,12 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
}
for _, u := range repo.Units {
- var found bool
for _, team := range teams {
+ unitAccessMode := minAccessMode
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
- perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode)
- found = true
- }
- }
-
- // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
- if !found && !repo.IsPrivate && !user.IsRestricted {
- if _, ok := perm.unitsMode[u.Type]; !ok {
- perm.unitsMode[u.Type] = perm_model.AccessModeRead
+ unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode)
}
+ perm.unitsMode[u.Type] = unitAccessMode
}
}
@@ -408,13 +409,13 @@ func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *use
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
// user does not have access.
-func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint
+func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint:revive // export stutter
return AccessLevelUnit(ctx, user, repo, unit.TypeCode)
}
// AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the
// user does not have access.
-func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint
+func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint:revive // export stutter
perm, err := GetUserRepoPermission(ctx, repo, user)
if err != nil {
return perm_model.AccessModeNone, err
diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go
index 024f4400b3..c8675b1ded 100644
--- a/models/perm/access/repo_permission_test.go
+++ b/models/perm/access/repo_permission_test.go
@@ -6,12 +6,16 @@ package access
import (
"testing"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/organization"
perm_model "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
+ "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestHasAnyUnitAccess(t *testing.T) {
@@ -152,3 +156,45 @@ func TestUnitAccessMode(t *testing.T) {
}
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
}
+
+func TestGetUserRepoPermission(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ ctx := t.Context()
+ repo32 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) // org public repo
+ require.NoError(t, repo32.LoadOwner(ctx))
+ require.True(t, repo32.Owner.IsOrganization())
+
+ require.NoError(t, db.TruncateBeans(ctx, &organization.Team{}, &organization.TeamUser{}, &organization.TeamRepo{}, &organization.TeamUnit{}))
+ org := repo32.Owner
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
+ team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
+ require.NoError(t, db.Insert(ctx, team))
+
+ t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
+ require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
+ perm, err := GetUserRepoPermission(ctx, repo32, user)
+ require.NoError(t, err)
+ assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
+ assert.Nil(t, perm.unitsMode) // doer in the team, but has no access to the repo
+ })
+
+ require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo32.ID}))
+ require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
+ t.Run("DoerWithTeamUnitAccessNone", func(t *testing.T) {
+ perm, err := GetUserRepoPermission(ctx, repo32, user)
+ require.NoError(t, err)
+ assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
+ assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeCode])
+ assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
+ })
+
+ require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}))
+ require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeWrite}))
+ t.Run("DoerWithTeamUnitAccessWrite", func(t *testing.T) {
+ perm, err := GetUserRepoPermission(ctx, repo32, user)
+ require.NoError(t, err)
+ assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
+ assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
+ assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
+ })
+}
diff --git a/models/project/column_test.go b/models/project/column_test.go
index 5b93e7760f..6a615090a5 100644
--- a/models/project/column_test.go
+++ b/models/project/column_test.go
@@ -110,7 +110,7 @@ func Test_NewColumn(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, columns, 3)
- for i := 0; i < maxProjectColumns-3; i++ {
+ for i := range maxProjectColumns - 3 {
err := NewColumn(db.DefaultContext, &Column{
Title: fmt.Sprintf("column-%d", i+4),
ProjectID: project1.ID,
diff --git a/models/project/project.go b/models/project/project.go
index d27e053094..f516466854 100644
--- a/models/project/project.go
+++ b/models/project/project.go
@@ -129,11 +129,11 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) {
return err
}
-func ProjectLinkForOrg(org *user_model.User, projectID int64) string { //nolint
+func ProjectLinkForOrg(org *user_model.User, projectID int64) string { //nolint:revive // export stutter
return fmt.Sprintf("%s/-/projects/%d", org.HomeLink(), projectID)
}
-func ProjectLinkForRepo(repo *repo_model.Repository, projectID int64) string { //nolint
+func ProjectLinkForRepo(repo *repo_model.Repository, projectID int64) string { //nolint:revive // export stutter
return fmt.Sprintf("%s/projects/%d", repo.Link(), projectID)
}
diff --git a/models/pull/review_state.go b/models/pull/review_state.go
index e46a22a49d..137af00eab 100644
--- a/models/pull/review_state.go
+++ b/models/pull/review_state.go
@@ -6,6 +6,7 @@ package pull
import (
"context"
"fmt"
+ "maps"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
@@ -100,9 +101,7 @@ func mergeFiles(oldFiles, newFiles map[string]ViewedState) map[string]ViewedStat
return oldFiles
}
- for file, viewed := range newFiles {
- oldFiles[file] = viewed
- }
+ maps.Copy(oldFiles, newFiles)
return oldFiles
}
diff --git a/models/renderhelper/commit_checker.go b/models/renderhelper/commit_checker.go
index 4815643e67..407e45fb54 100644
--- a/models/renderhelper/commit_checker.go
+++ b/models/renderhelper/commit_checker.go
@@ -47,7 +47,7 @@ func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
c.gitRepo, c.gitRepoCloser = r, closer
}
- exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
+ exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashes with gogit edition.
c.commitCache[commitID] = exist
return exist
}
diff --git a/models/renderhelper/repo_comment.go b/models/renderhelper/repo_comment.go
index 7c40eded44..ae0fbf0abd 100644
--- a/models/renderhelper/repo_comment.go
+++ b/models/renderhelper/repo_comment.go
@@ -44,30 +44,31 @@ type RepoCommentOptions struct {
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
CurrentRefPath string // eg: "branch/main" or "commit/11223344"
+ FootnoteContextID string // the extra context ID for footnotes, used to avoid conflicts with other footnotes in the same page
}
func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repository, opts ...RepoCommentOptions) *markup.RenderContext {
- helper := &RepoComment{
- repoLink: repo.Link(),
- opts: util.OptionalArg(opts),
- }
+ helper := &RepoComment{opts: util.OptionalArg(opts)}
rctx := markup.NewRenderContext(ctx)
helper.ctx = rctx
+ var metas map[string]string
if repo != nil {
helper.repoLink = repo.Link()
helper.commitChecker = newCommitChecker(ctx, repo)
- rctx = rctx.WithMetas(repo.ComposeCommentMetas(ctx))
+ metas = repo.ComposeCommentMetas(ctx)
} else {
- // this is almost dead code, only to pass the incorrect tests
- helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName)
- rctx = rctx.WithMetas(map[string]string{
- "user": helper.opts.DeprecatedOwnerName,
- "repo": helper.opts.DeprecatedRepoName,
-
- "markdownNewLineHardBreak": "true",
- "markupAllowShortIssuePattern": "true",
- })
+ // repo can be nil when rendering a commit message in user's dashboard feedback whose repository has been deleted
+ metas = map[string]string{}
+ if helper.opts.DeprecatedOwnerName != "" {
+ // this is almost dead code, only to pass the incorrect tests
+ helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName)
+ metas["user"] = helper.opts.DeprecatedOwnerName
+ metas["repo"] = helper.opts.DeprecatedRepoName
+ }
+ metas["markdownNewLineHardBreak"] = "true"
+ metas["markupAllowShortIssuePattern"] = "true"
}
- rctx = rctx.WithHelper(helper)
+ metas["footnoteContextId"] = helper.opts.FootnoteContextID
+ rctx = rctx.WithMetas(metas).WithHelper(helper)
return rctx
}
diff --git a/models/renderhelper/repo_comment_test.go b/models/renderhelper/repo_comment_test.go
index 776152db96..3b13bff73c 100644
--- a/models/renderhelper/repo_comment_test.go
+++ b/models/renderhelper/repo_comment_test.go
@@ -72,4 +72,11 @@ func TestRepoComment(t *testing.T) {
<a href="/user2/repo1/commit/1234/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/commit/1234/image" alt="./image"/></a></p>
`, rendered)
})
+
+ t.Run("NoRepo", func(t *testing.T) {
+ rctx := NewRenderContextRepoComment(t.Context(), nil).WithMarkupType(markdown.MarkupName)
+ rendered, err := markup.RenderString(rctx, "any")
+ assert.NoError(t, err)
+ assert.Equal(t, "<p>any</p>\n", rendered)
+ })
}
diff --git a/models/repo/org_repo.go b/models/repo/org_repo.go
index fa519d25b1..96f21ba2ac 100644
--- a/models/repo/org_repo.go
+++ b/models/repo/org_repo.go
@@ -48,8 +48,7 @@ func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (Repo
// accessible to a particular user
type AccessibleReposEnvironment interface {
CountRepos(ctx context.Context) (int64, error)
- RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error)
- Repos(ctx context.Context, page, pageSize int) (RepositoryList, error)
+ RepoIDs(ctx context.Context) ([]int64, error)
MirrorRepos(ctx context.Context) (RepositoryList, error)
AddKeyword(keyword string)
SetSort(db.SearchOrderBy)
@@ -132,40 +131,18 @@ func (env *accessibleReposEnv) CountRepos(ctx context.Context) (int64, error) {
return repoCount, nil
}
-func (env *accessibleReposEnv) RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error) {
- if page <= 0 {
- page = 1
- }
-
- repoIDs := make([]int64, 0, pageSize)
+func (env *accessibleReposEnv) RepoIDs(ctx context.Context) ([]int64, error) {
+ var repoIDs []int64
return repoIDs, db.GetEngine(ctx).
Table("repository").
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
Where(env.cond()).
- GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]).
+ GroupBy("`repository`.id,`repository`." + strings.Fields(string(env.orderBy))[0]).
OrderBy(string(env.orderBy)).
- Limit(pageSize, (page-1)*pageSize).
Cols("`repository`.id").
Find(&repoIDs)
}
-func (env *accessibleReposEnv) Repos(ctx context.Context, page, pageSize int) (RepositoryList, error) {
- repoIDs, err := env.RepoIDs(ctx, page, pageSize)
- if err != nil {
- return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err)
- }
-
- repos := make([]*Repository, 0, len(repoIDs))
- if len(repoIDs) == 0 {
- return repos, nil
- }
-
- return repos, db.GetEngine(ctx).
- In("`repository`.id", repoIDs).
- OrderBy(string(env.orderBy)).
- Find(&repos)
-}
-
func (env *accessibleReposEnv) MirrorRepoIDs(ctx context.Context) ([]int64, error) {
repoIDs := make([]int64, 0, 10)
return repoIDs, db.GetEngine(ctx).
diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go
index e19749d93a..9fb7471147 100644
--- a/models/repo/pushmirror_test.go
+++ b/models/repo/pushmirror_test.go
@@ -39,8 +39,6 @@ func TestPushMirrorsIterate(t *testing.T) {
Interval: 0,
})
- time.Sleep(1 * time.Millisecond)
-
repo_model.PushMirrorsIterate(db.DefaultContext, 1, func(idx int, bean any) error {
m, ok := bean.(*repo_model.PushMirror)
assert.True(t, ok)
diff --git a/models/repo/release.go b/models/repo/release.go
index 663d310bc0..59f4caf5aa 100644
--- a/models/repo/release.go
+++ b/models/repo/release.go
@@ -161,6 +161,11 @@ func UpdateRelease(ctx context.Context, rel *Release) error {
return err
}
+func UpdateReleaseNumCommits(ctx context.Context, rel *Release) error {
+ _, err := db.GetEngine(ctx).ID(rel.ID).Cols("num_commits").Update(rel)
+ return err
+}
+
// AddReleaseAttachments adds a release attachments
func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
// Check attachments
@@ -175,7 +180,7 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs
}
attachments[i].ReleaseID = releaseID
// No assign value could be 0, so ignore AllCols().
- if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
+ if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Cols("release_id").Update(attachments[i]); err != nil {
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
}
}
@@ -418,8 +423,8 @@ func UpdateReleasesMigrationsByType(ctx context.Context, gitServiceType structs.
return err
}
-// PushUpdateDeleteTagsContext updates a number of delete tags with context
-func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []string) error {
+// PushUpdateDeleteTags updates a number of delete tags with context
+func PushUpdateDeleteTags(ctx context.Context, repo *Repository, tags []string) error {
if len(tags) == 0 {
return nil
}
@@ -448,58 +453,6 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s
return nil
}
-// PushUpdateDeleteTag must be called for any push actions to delete tag
-func PushUpdateDeleteTag(ctx context.Context, repo *Repository, tagName string) error {
- rel, err := GetRelease(ctx, repo.ID, tagName)
- if err != nil {
- if IsErrReleaseNotExist(err) {
- return nil
- }
- return fmt.Errorf("GetRelease: %w", err)
- }
- if rel.IsTag {
- if _, err = db.DeleteByID[Release](ctx, rel.ID); err != nil {
- return fmt.Errorf("Delete: %w", err)
- }
- } else {
- rel.IsDraft = true
- rel.NumCommits = 0
- rel.Sha1 = ""
- if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil {
- return fmt.Errorf("Update: %w", err)
- }
- }
-
- return nil
-}
-
-// SaveOrUpdateTag must be called for any push actions to add tag
-func SaveOrUpdateTag(ctx context.Context, repo *Repository, newRel *Release) error {
- rel, err := GetRelease(ctx, repo.ID, newRel.TagName)
- if err != nil && !IsErrReleaseNotExist(err) {
- return fmt.Errorf("GetRelease: %w", err)
- }
-
- if rel == nil {
- rel = newRel
- if _, err = db.GetEngine(ctx).Insert(rel); err != nil {
- return fmt.Errorf("InsertOne: %w", err)
- }
- } else {
- rel.Sha1 = newRel.Sha1
- rel.CreatedUnix = newRel.CreatedUnix
- rel.NumCommits = newRel.NumCommits
- rel.IsDraft = false
- if rel.IsTag && newRel.PublisherID > 0 {
- rel.PublisherID = newRel.PublisherID
- }
- if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil {
- return fmt.Errorf("Update: %w", err)
- }
- }
- return nil
-}
-
// RemapExternalUser ExternalUserRemappable interface
func (r *Release) RemapExternalUser(externalName string, externalID, userID int64) error {
r.OriginalAuthor = externalName
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 2977dfb9f1..34d1bf55f6 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -64,18 +64,18 @@ func (err ErrRepoIsArchived) Error() string {
}
type globalVarsStruct struct {
- validRepoNamePattern *regexp.Regexp
- invalidRepoNamePattern *regexp.Regexp
- reservedRepoNames []string
- reservedRepoPatterns []string
+ validRepoNamePattern *regexp.Regexp
+ invalidRepoNamePattern *regexp.Regexp
+ reservedRepoNames []string
+ reservedRepoNamePatterns []string
}
var globalVars = sync.OnceValue(func() *globalVarsStruct {
return &globalVarsStruct{
- validRepoNamePattern: regexp.MustCompile(`[-.\w]+`),
- invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
- reservedRepoNames: []string{".", "..", "-"},
- reservedRepoPatterns: []string{"*.git", "*.wiki", "*.rss", "*.atom"},
+ validRepoNamePattern: regexp.MustCompile(`^[-.\w]+$`),
+ invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
+ reservedRepoNames: []string{".", "..", "-"},
+ reservedRepoNamePatterns: []string{"*.wiki", "*.git", "*.rss", "*.atom"},
}
})
@@ -86,7 +86,16 @@ func IsUsableRepoName(name string) error {
// Note: usually this error is normally caught up earlier in the UI
return db.ErrNameCharsNotAllowed{Name: name}
}
- return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoPatterns, name)
+ return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns, name)
+}
+
+// IsValidSSHAccessRepoName is like IsUsableRepoName, but it allows "*.wiki" because wiki repo needs to be accessed in SSH code
+func IsValidSSHAccessRepoName(name string) bool {
+ vars := globalVars()
+ if !vars.validRepoNamePattern.MatchString(name) || vars.invalidRepoNamePattern.MatchString(name) {
+ return false
+ }
+ return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns[1:], name) == nil
}
// TrustModelType defines the types of trust model for this repository
@@ -644,7 +653,7 @@ func (repo *Repository) AllowsPulls(ctx context.Context) bool {
// CanEnableEditor returns true if repository meets the requirements of web editor.
func (repo *Repository) CanEnableEditor() bool {
- return !repo.IsMirror
+ return !repo.IsMirror && !repo.IsArchived
}
// DescriptionHTML does special handles to description and return HTML string.
diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go
index 02c228e8a0..f2cdd2f284 100644
--- a/models/repo/repo_list.go
+++ b/models/repo/repo_list.go
@@ -359,7 +359,7 @@ func UserOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond {
}
// SearchRepositoryCondition creates a query condition according search repository options
-func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
+func SearchRepositoryCondition(opts SearchRepoOptions) builder.Cond {
cond := builder.NewCond()
if opts.Private {
@@ -449,7 +449,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
if opts.Keyword != "" {
// separate keyword
subQueryCond := builder.NewCond()
- for _, v := range strings.Split(opts.Keyword, ",") {
+ for v := range strings.SplitSeq(opts.Keyword, ",") {
if opts.TopicOnly {
subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)})
} else {
@@ -464,7 +464,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
keywordCond := builder.In("id", subQuery)
if !opts.TopicOnly {
likes := builder.NewCond()
- for _, v := range strings.Split(opts.Keyword, ",") {
+ for v := range strings.SplitSeq(opts.Keyword, ",") {
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
// If the string looks like "org/repo", match against that pattern too
@@ -551,18 +551,18 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
// SearchRepository returns repositories based on search options,
// it returns results in given range and number of total results.
-func SearchRepository(ctx context.Context, opts *SearchRepoOptions) (RepositoryList, int64, error) {
+func SearchRepository(ctx context.Context, opts SearchRepoOptions) (RepositoryList, int64, error) {
cond := SearchRepositoryCondition(opts)
return SearchRepositoryByCondition(ctx, opts, cond, true)
}
// CountRepository counts repositories based on search options,
-func CountRepository(ctx context.Context, opts *SearchRepoOptions) (int64, error) {
+func CountRepository(ctx context.Context, opts SearchRepoOptions) (int64, error) {
return db.GetEngine(ctx).Where(SearchRepositoryCondition(opts)).Count(new(Repository))
}
// SearchRepositoryByCondition search repositories by condition
-func SearchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) {
+func SearchRepositoryByCondition(ctx context.Context, opts SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) {
sess, count, err := searchRepositoryByCondition(ctx, opts, cond)
if err != nil {
return nil, 0, err
@@ -590,23 +590,25 @@ func SearchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c
return repos, count, nil
}
-func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) {
- if opts.Page <= 0 {
- opts.Page = 1
+func searchRepositoryByCondition(ctx context.Context, opts SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) {
+ page := opts.Page
+ if page <= 0 {
+ page = 1
}
- if len(opts.OrderBy) == 0 {
- opts.OrderBy = db.SearchOrderByAlphabetically
+ orderBy := opts.OrderBy
+ if len(orderBy) == 0 {
+ orderBy = db.SearchOrderByAlphabetically
}
args := make([]any, 0)
if opts.PriorityOwnerID > 0 {
- opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", opts.OrderBy))
+ orderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", orderBy))
args = append(args, opts.PriorityOwnerID)
} else if strings.Count(opts.Keyword, "/") == 1 {
// With "owner/repo" search times, prioritise results which match the owner field
orgName := strings.Split(opts.Keyword, "/")[0]
- opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", opts.OrderBy))
+ orderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", orderBy))
args = append(args, orgName)
}
@@ -623,9 +625,9 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c
}
}
- sess = sess.Where(cond).OrderBy(opts.OrderBy.String(), args...)
+ sess = sess.Where(cond).OrderBy(orderBy.String(), args...)
if opts.PageSize > 0 {
- sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
+ sess = sess.Limit(opts.PageSize, (page-1)*opts.PageSize)
}
return sess, count, nil
}
@@ -689,14 +691,14 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
// SearchRepositoryByName takes keyword and part of repository name to search,
// it returns results in given range and number of total results.
-func SearchRepositoryByName(ctx context.Context, opts *SearchRepoOptions) (RepositoryList, int64, error) {
+func SearchRepositoryByName(ctx context.Context, opts SearchRepoOptions) (RepositoryList, int64, error) {
opts.IncludeDescription = false
return SearchRepository(ctx, opts)
}
// SearchRepositoryIDs takes keyword and part of repository name to search,
// it returns results in given range and number of total results.
-func SearchRepositoryIDs(ctx context.Context, opts *SearchRepoOptions) ([]int64, int64, error) {
+func SearchRepositoryIDs(ctx context.Context, opts SearchRepoOptions) ([]int64, int64, error) {
opts.IncludeDescription = false
cond := SearchRepositoryCondition(opts)
@@ -740,7 +742,7 @@ func FindUserCodeAccessibleOwnerRepoIDs(ctx context.Context, ownerID int64, user
}
// GetUserRepositories returns a list of repositories of given user.
-func GetUserRepositories(ctx context.Context, opts *SearchRepoOptions) (RepositoryList, int64, error) {
+func GetUserRepositories(ctx context.Context, opts SearchRepoOptions) (RepositoryList, int64, error) {
if len(opts.OrderBy) == 0 {
opts.OrderBy = "updated_unix DESC"
}
@@ -767,5 +769,5 @@ func GetUserRepositories(ctx context.Context, opts *SearchRepoOptions) (Reposito
sess = sess.Where(cond).OrderBy(opts.OrderBy.String())
repos := make(RepositoryList, 0, opts.PageSize)
- return repos, count, db.SetSessionPagination(sess, opts).Find(&repos)
+ return repos, count, db.SetSessionPagination(sess, &opts).Find(&repos)
}
diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go
index ca6007f6c7..7eb76416c2 100644
--- a/models/repo/repo_list_test.go
+++ b/models/repo/repo_list_test.go
@@ -17,162 +17,162 @@ import (
func getTestCases() []struct {
name string
- opts *repo_model.SearchRepoOptions
+ opts repo_model.SearchRepoOptions
count int
} {
testCases := []struct {
name string
- opts *repo_model.SearchRepoOptions
+ opts repo_model.SearchRepoOptions
count int
}{
{
name: "PublicRepositoriesByName",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: optional.Some(false)},
count: 7,
},
{
name: "PublicAndPrivateRepositoriesByName",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: optional.Some(false)},
count: 14,
},
{
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14,
},
{
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14,
},
{
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14,
},
{
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14,
},
{
name: "PublicRepositoriesOfUser",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: optional.Some(false)},
count: 2,
},
{
name: "PublicRepositoriesOfUser2",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: optional.Some(false)},
count: 0,
},
{
name: "PublicRepositoriesOfOrg3",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: optional.Some(false)},
count: 2,
},
{
name: "PublicAndPrivateRepositoriesOfUser",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: optional.Some(false)},
count: 4,
},
{
name: "PublicAndPrivateRepositoriesOfUser2",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: optional.Some(false)},
count: 0,
},
{
name: "PublicAndPrivateRepositoriesOfOrg3",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: optional.Some(false)},
count: 4,
},
{
name: "PublicRepositoriesOfUserIncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15},
count: 5,
},
{
name: "PublicRepositoriesOfUser2IncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18},
count: 1,
},
{
name: "PublicRepositoriesOfOrg3IncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20},
count: 3,
},
{
name: "PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true},
count: 9,
},
{
name: "PublicAndPrivateRepositoriesOfUser2IncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true},
count: 4,
},
{
name: "PublicAndPrivateRepositoriesOfOrg3IncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true},
count: 7,
},
{
name: "PublicRepositoriesOfOrganization",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: optional.Some(false)},
count: 1,
},
{
name: "PublicAndPrivateRepositoriesOfOrganization",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: optional.Some(false)},
count: 2,
},
{
name: "AllPublic/PublicRepositoriesByName",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: optional.Some(false)},
count: 7,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesByName",
- opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: optional.Some(false)},
count: 14,
},
{
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)},
count: 34,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)},
count: 39,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
- opts: &repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true},
+ opts: repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true},
count: 15,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUser2IncludingCollaborativeByName",
- opts: &repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true},
+ opts: repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true},
count: 13,
},
{
name: "AllPublic/PublicRepositoriesOfOrganization",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)},
count: 34,
},
{
name: "AllTemplates",
- opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: optional.Some(true)},
+ opts: repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: optional.Some(true)},
count: 2,
},
{
name: "OwnerSlashRepoSearch",
- opts: &repo_model.SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
+ opts: repo_model.SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
count: 2,
},
{
name: "OwnerSlashSearch",
- opts: &repo_model.SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
+ opts: repo_model.SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
count: 4,
},
}
@@ -184,7 +184,7 @@ func TestSearchRepository(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// test search public repository on explore page
- repos, count, err := repo_model.SearchRepositoryByName(db.DefaultContext, &repo_model.SearchRepoOptions{
+ repos, count, err := repo_model.SearchRepositoryByName(db.DefaultContext, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -199,7 +199,7 @@ func TestSearchRepository(t *testing.T) {
}
assert.Equal(t, int64(1), count)
- repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -213,7 +213,7 @@ func TestSearchRepository(t *testing.T) {
assert.Len(t, repos, 2)
// test search private repository on explore page
- repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -229,7 +229,7 @@ func TestSearchRepository(t *testing.T) {
}
assert.Equal(t, int64(1), count)
- repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -244,14 +244,14 @@ func TestSearchRepository(t *testing.T) {
assert.Len(t, repos, 3)
// Test non existing owner
- repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, &repo_model.SearchRepoOptions{OwnerID: unittest.NonexistentID})
+ repos, count, err = repo_model.SearchRepositoryByName(db.DefaultContext, repo_model.SearchRepoOptions{OwnerID: unittest.NonexistentID})
assert.NoError(t, err)
assert.Empty(t, repos)
assert.Equal(t, int64(0), count)
// Test search within description
- repos, count, err = repo_model.SearchRepository(db.DefaultContext, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepository(db.DefaultContext, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -268,7 +268,7 @@ func TestSearchRepository(t *testing.T) {
assert.Equal(t, int64(1), count)
// Test NOT search within description
- repos, count, err = repo_model.SearchRepository(db.DefaultContext, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepository(db.DefaultContext, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -374,22 +374,22 @@ func TestSearchRepositoryByTopicName(t *testing.T) {
testCases := []struct {
name string
- opts *repo_model.SearchRepoOptions
+ opts repo_model.SearchRepoOptions
count int
}{
{
name: "AllPublic/SearchPublicRepositoriesFromTopicAndName",
- opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"},
+ opts: repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"},
count: 2,
},
{
name: "AllPublic/OnlySearchPublicRepositoriesFromTopic",
- opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true},
+ opts: repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true},
count: 1,
},
{
name: "AllPublic/OnlySearchMultipleKeywordPublicRepositoriesFromTopic",
- opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql,golang", TopicOnly: true},
+ opts: repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql,golang", TopicOnly: true},
count: 2,
},
}
diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go
index b2604ab575..66abe864fc 100644
--- a/models/repo/repo_test.go
+++ b/models/repo/repo_test.go
@@ -216,8 +216,23 @@ func TestIsUsableRepoName(t *testing.T) {
assert.Error(t, IsUsableRepoName("-"))
assert.Error(t, IsUsableRepoName("🌞"))
+ assert.Error(t, IsUsableRepoName("the/repo"))
assert.Error(t, IsUsableRepoName("the..repo"))
assert.Error(t, IsUsableRepoName("foo.wiki"))
assert.Error(t, IsUsableRepoName("foo.git"))
assert.Error(t, IsUsableRepoName("foo.RSS"))
}
+
+func TestIsValidSSHAccessRepoName(t *testing.T) {
+ assert.True(t, IsValidSSHAccessRepoName("a"))
+ assert.True(t, IsValidSSHAccessRepoName("-1_."))
+ assert.True(t, IsValidSSHAccessRepoName(".profile"))
+ assert.True(t, IsValidSSHAccessRepoName("foo.wiki"))
+
+ assert.False(t, IsValidSSHAccessRepoName("-"))
+ assert.False(t, IsValidSSHAccessRepoName("🌞"))
+ assert.False(t, IsValidSSHAccessRepoName("the/repo"))
+ assert.False(t, IsValidSSHAccessRepoName("the..repo"))
+ assert.False(t, IsValidSSHAccessRepoName("foo.git"))
+ assert.False(t, IsValidSSHAccessRepoName("foo.RSS"))
+}
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index 8a7dbfe340..a5207bc22a 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -185,10 +185,8 @@ func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool {
}
func (cfg *ActionsConfig) DisableWorkflow(file string) {
- for _, workflow := range cfg.DisabledWorkflows {
- if file == workflow {
- return
- }
+ if slices.Contains(cfg.DisabledWorkflows, file) {
+ return
}
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
diff --git a/models/repo/transfer.go b/models/repo/transfer.go
index b669145d68..3fb8cb69ab 100644
--- a/models/repo/transfer.go
+++ b/models/repo/transfer.go
@@ -61,7 +61,7 @@ func (err ErrRepoTransferInProgress) Unwrap() error {
}
// RepoTransfer is used to manage repository transfers
-type RepoTransfer struct { //nolint
+type RepoTransfer struct { //nolint:revive // export stutter
ID int64 `xorm:"pk autoincr"`
DoerID int64
Doer *user_model.User `xorm:"-"`
@@ -249,7 +249,7 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m
}
repo.Status = RepositoryPendingTransfer
- if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil {
+ if err := UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
return err
}
diff --git a/models/repo/update.go b/models/repo/update.go
index 15c8c48d5b..64065f11c4 100644
--- a/models/repo/update.go
+++ b/models/repo/update.go
@@ -25,7 +25,7 @@ func UpdateRepositoryOwnerNames(ctx context.Context, ownerID int64, ownerName st
}
defer committer.Close()
- if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").Update(&Repository{
+ if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").NoAutoTime().Update(&Repository{
OwnerName: ownerName,
}); err != nil {
return err
@@ -40,15 +40,15 @@ func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime t
return err
}
-// UpdateRepositoryCols updates repository's columns
-func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string) error {
- _, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo)
+// UpdateRepositoryColsWithAutoTime updates repository's columns and the timestamp fields automatically
+func UpdateRepositoryColsWithAutoTime(ctx context.Context, repo *Repository, colName string, moreColNames ...string) error {
+ _, err := db.GetEngine(ctx).ID(repo.ID).Cols(append([]string{colName}, moreColNames...)...).Update(repo)
return err
}
-// UpdateRepositoryColsNoAutoTime updates repository's columns and but applies time change automatically
-func UpdateRepositoryColsNoAutoTime(ctx context.Context, repo *Repository, cols ...string) error {
- _, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).NoAutoTime().Update(repo)
+// UpdateRepositoryColsNoAutoTime updates repository's columns, doesn't change timestamp field automatically
+func UpdateRepositoryColsNoAutoTime(ctx context.Context, repo *Repository, colName string, moreColNames ...string) error {
+ _, err := db.GetEngine(ctx).ID(repo.ID).Cols(append([]string{colName}, moreColNames...)...).NoAutoTime().Update(repo)
return err
}
diff --git a/models/repo/upload.go b/models/repo/upload.go
index fb57fb6c51..20a8fa26fe 100644
--- a/models/repo/upload.go
+++ b/models/repo/upload.go
@@ -124,7 +124,7 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
defer committer.Close()
ids := make([]int64, len(uploads))
- for i := 0; i < len(uploads); i++ {
+ for i := range uploads {
ids[i] = uploads[i].ID
}
if err = db.DeleteByIDs[Upload](ctx, ids...); err != nil {
diff --git a/models/unit/unit.go b/models/unit/unit.go
index 4ca676802f..c0560678ca 100644
--- a/models/unit/unit.go
+++ b/models/unit/unit.go
@@ -6,6 +6,7 @@ package unit
import (
"errors"
"fmt"
+ "slices"
"strings"
"sync/atomic"
@@ -204,22 +205,12 @@ func LoadUnitConfig() error {
// UnitGlobalDisabled checks if unit type is global disabled
func (u Type) UnitGlobalDisabled() bool {
- for _, ud := range DisabledRepoUnitsGet() {
- if u == ud {
- return true
- }
- }
- return false
+ return slices.Contains(DisabledRepoUnitsGet(), u)
}
// CanBeDefault checks if the unit type can be a default repo unit
func (u *Type) CanBeDefault() bool {
- for _, nadU := range NotAllowedDefaultRepoUnits {
- if *u == nadU {
- return false
- }
- }
- return true
+ return !slices.Contains(NotAllowedDefaultRepoUnits, *u)
}
// Unit is a section of one repository
diff --git a/models/user/avatar.go b/models/user/avatar.go
index 3d9fc4452f..542bd93b98 100644
--- a/models/user/avatar.go
+++ b/models/user/avatar.go
@@ -5,7 +5,6 @@ package user
import (
"context"
- "crypto/md5"
"fmt"
"image/png"
"io"
@@ -106,7 +105,7 @@ func (u *User) IsUploadAvatarChanged(data []byte) bool {
if !u.UseCustomAvatar || len(u.Avatar) == 0 {
return true
}
- avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
+ avatarID := avatar.HashAvatar(u.ID, data)
return u.Avatar != avatarID
}
diff --git a/models/user/badge.go b/models/user/badge.go
index 3ff3530a36..e475ceb748 100644
--- a/models/user/badge.go
+++ b/models/user/badge.go
@@ -19,7 +19,7 @@ type Badge struct {
}
// UserBadge represents a user badge
-type UserBadge struct { //nolint:revive
+type UserBadge struct { //nolint:revive // export stutter
ID int64 `xorm:"pk autoincr"`
BadgeID int64
UserID int64 `xorm:"INDEX"`
diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go
index 0e52950cfd..c0666246b0 100644
--- a/models/user/email_address_test.go
+++ b/models/user/email_address_test.go
@@ -4,6 +4,7 @@
package user_test
import (
+ "slices"
"testing"
"code.gitea.io/gitea/models/db"
@@ -100,12 +101,7 @@ func TestListEmails(t *testing.T) {
assert.Greater(t, count, int64(5))
contains := func(match func(s *user_model.SearchEmailResult) bool) bool {
- for _, v := range emails {
- if match(v) {
- return true
- }
- }
- return false
+ return slices.ContainsFunc(emails, match)
}
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 }))
diff --git a/models/user/search.go b/models/user/search.go
index f4436be09a..cfd0d011bc 100644
--- a/models/user/search.go
+++ b/models/user/search.go
@@ -137,7 +137,7 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
// SearchUsers takes options i.e. keyword and part of user name to search,
// it returns results in given range and number of total results.
-func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _ int64, _ error) {
+func SearchUsers(ctx context.Context, opts SearchUserOptions) (users []*User, _ int64, _ error) {
sessCount := opts.toSearchQueryBase(ctx)
defer sessCount.Close()
count, err := sessCount.Count(new(User))
@@ -152,7 +152,7 @@ func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _
sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
defer sessQuery.Close()
if opts.Page > 0 {
- sessQuery = db.SetSessionPagination(sessQuery, opts)
+ sessQuery = db.SetSessionPagination(sessQuery, &opts)
}
// the sql may contain JOIN, so we must only select User related columns
diff --git a/models/user/user.go b/models/user/user.go
index fd420f79c7..7c871bf575 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -831,6 +831,20 @@ type CountUserFilter struct {
IsActive optional.Option[bool]
}
+// HasUsers checks whether there are any users in the database, or only one user exists.
+func HasUsers(ctx context.Context) (ret struct {
+ HasAnyUser, HasOnlyOneUser bool
+}, err error,
+) {
+ res, err := db.GetEngine(ctx).Table(&User{}).Cols("id").Limit(2).Query()
+ if err != nil {
+ return ret, fmt.Errorf("error checking user existence: %w", err)
+ }
+ ret.HasAnyUser = len(res) != 0
+ ret.HasOnlyOneUser = len(res) == 1
+ return ret, nil
+}
+
// CountUsers returns number of users.
func CountUsers(ctx context.Context, opts *CountUserFilter) int64 {
return countUsers(ctx, opts)
@@ -1151,8 +1165,8 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([
}
for _, c := range oldCommits {
- user, ok := emailUserMap[c.Author.Email]
- if !ok {
+ user := emailUserMap.GetByEmail(c.Author.Email) // FIXME: why ValidateCommitsWithEmails uses "Author", but ParseCommitsWithSignature uses "Committer"?
+ if user == nil {
user = &User{
Name: c.Author.Name,
Email: c.Author.Email,
@@ -1166,7 +1180,15 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([
return newCommits, nil
}
-func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, error) {
+type EmailUserMap struct {
+ m map[string]*User
+}
+
+func (eum *EmailUserMap) GetByEmail(email string) *User {
+ return eum.m[strings.ToLower(email)]
+}
+
+func GetUsersByEmails(ctx context.Context, emails []string) (*EmailUserMap, error) {
if len(emails) == 0 {
return nil, nil
}
@@ -1176,7 +1198,7 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
for _, email := range emails {
if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) {
username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress)
- needCheckUserNames.Add(username)
+ needCheckUserNames.Add(strings.ToLower(username))
} else {
needCheckEmails.Add(strings.ToLower(email))
}
@@ -1203,7 +1225,7 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
for _, email := range emailAddresses {
user := users[email.UID]
if user != nil {
- results[user.GetEmail()] = user
+ results[email.LowerEmail] = user
}
}
}
@@ -1213,9 +1235,9 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
return nil, err
}
for _, user := range users {
- results[user.GetPlaceholderEmail()] = user
+ results[strings.ToLower(user.GetPlaceholderEmail())] = user
}
- return results, nil
+ return &EmailUserMap{results}, nil
}
// GetUserByEmail returns the user object by given e-mail if exists.
diff --git a/models/user/user_list.go b/models/user/user_list.go
index 4241905058..1b6a27dd86 100644
--- a/models/user/user_list.go
+++ b/models/user/user_list.go
@@ -17,10 +17,7 @@ func GetUsersMapByIDs(ctx context.Context, userIDs []int64) (map[int64]*User, er
left := len(userIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", userIDs[:limit]).
Find(&userMaps)
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 90e8bf13a8..a2597ba3f5 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestIsUsableUsername(t *testing.T) {
@@ -48,14 +49,43 @@ func TestOAuth2Application_LoadUser(t *testing.T) {
assert.NotNil(t, user)
}
-func TestGetUserEmailsByNames(t *testing.T) {
+func TestUserEmails(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
-
- // ignore none active user email
- assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
- assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
-
- assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
+ t.Run("GetUserEmailsByNames", func(t *testing.T) {
+ // ignore none active user email
+ assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
+ assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
+ assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
+ })
+ t.Run("GetUsersByEmails", func(t *testing.T) {
+ defer test.MockVariableValue(&setting.Service.NoReplyAddress, "NoReply.gitea.internal")()
+ testGetUserByEmail := func(t *testing.T, email string, uid int64) {
+ m, err := user_model.GetUsersByEmails(db.DefaultContext, []string{email})
+ require.NoError(t, err)
+ user := m.GetByEmail(email)
+ if uid == 0 {
+ require.Nil(t, user)
+ return
+ }
+ require.NotNil(t, user)
+ assert.Equal(t, uid, user.ID)
+ }
+ cases := []struct {
+ Email string
+ UID int64
+ }{
+ {"UseR1@example.com", 1},
+ {"user1-2@example.COM", 1},
+ {"USER2@" + setting.Service.NoReplyAddress, 2},
+ {"user4@example.com", 4},
+ {"no-such", 0},
+ }
+ for _, c := range cases {
+ t.Run(c.Email, func(t *testing.T) {
+ testGetUserByEmail(t, c.Email, c.UID)
+ })
+ }
+ })
}
func TestCanCreateOrganization(t *testing.T) {
@@ -78,7 +108,7 @@ func TestCanCreateOrganization(t *testing.T) {
func TestSearchUsers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) {
+ testSuccess := func(opts user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) {
users, _, err := user_model.SearchUsers(db.DefaultContext, opts)
assert.NoError(t, err)
cassText := fmt.Sprintf("ids: %v, opts: %v", expectedUserOrOrgIDs, opts)
@@ -90,61 +120,61 @@ func TestSearchUsers(t *testing.T) {
}
// test orgs
- testOrgSuccess := func(opts *user_model.SearchUserOptions, expectedOrgIDs []int64) {
+ testOrgSuccess := func(opts user_model.SearchUserOptions, expectedOrgIDs []int64) {
opts.Type = user_model.UserTypeOrganization
testSuccess(opts, expectedOrgIDs)
}
- testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}},
+ testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}},
[]int64{3, 6})
- testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}},
+ testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}},
[]int64{7, 17})
- testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}},
+ testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}},
[]int64{19, 25})
- testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
+ testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
[]int64{26, 41})
- testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
+ testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
[]int64{42})
- testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 6, PageSize: 2}},
+ testOrgSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 6, PageSize: 2}},
[]int64{})
// test users
- testUserSuccess := func(opts *user_model.SearchUserOptions, expectedUserIDs []int64) {
+ testUserSuccess := func(opts user_model.SearchUserOptions, expectedUserIDs []int64) {
opts.Type = user_model.UserTypeIndividual
testSuccess(opts, expectedUserIDs)
}
- testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
+ testUserSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
- testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
+ testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
[]int64{9})
- testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
+ testUserSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
- testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
+ testUserSuccess(user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
// order by name asc default
- testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
+ testUserSuccess(user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
- testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: optional.Some(true)},
+ testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: optional.Some(true)},
[]int64{1})
- testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: optional.Some(true)},
+ testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: optional.Some(true)},
[]int64{29})
- testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)},
+ testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)},
[]int64{37})
- testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)},
+ testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)},
[]int64{24})
}
@@ -174,9 +204,9 @@ func TestHashPasswordDeterministic(t *testing.T) {
b := make([]byte, 16)
u := &user_model.User{}
algos := hash.RecommendedHashAlgorithms
- for j := 0; j < len(algos); j++ {
+ for j := range algos {
u.PasswdHashAlgo = algos[j]
- for i := 0; i < 50; i++ {
+ for range 50 {
// generate a random password
rand.Read(b)
pass := string(b)
@@ -503,11 +533,8 @@ func TestIsUserVisibleToViewer(t *testing.T) {
}
func Test_ValidateUser(t *testing.T) {
- oldSetting := setting.Service.AllowedUserVisibilityModesSlice
- defer func() {
- setting.Service.AllowedUserVisibilityModesSlice = oldSetting
- }()
- setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, true}
+ defer test.MockVariableValue(&setting.Service.AllowedUserVisibilityModesSlice, []bool{true, false, true})()
+
kases := map[*user_model.User]bool{
{ID: 1, Visibility: structs.VisibleTypePublic}: true,
{ID: 2, Visibility: structs.VisibleTypeLimited}: false,
@@ -576,12 +603,7 @@ func TestDisabledUserFeatures(t *testing.T) {
testValues := container.SetOf(setting.UserFeatureDeletion,
setting.UserFeatureManageSSHKeys,
setting.UserFeatureManageGPGKeys)
-
- oldSetting := setting.Admin.ExternalUserDisableFeatures
- defer func() {
- setting.Admin.ExternalUserDisableFeatures = oldSetting
- }()
- setting.Admin.ExternalUserDisableFeatures = testValues
+ defer test.MockVariableValue(&setting.Admin.ExternalUserDisableFeatures, testValues)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index 97ad373027..b234d9ffee 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -240,7 +240,7 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
if len(ws) == 0 {
return nil
}
- for i := 0; i < len(ws); i++ {
+ for i := range ws {
ws[i].Type = strings.TrimSpace(ws[i].Type)
}
return db.Insert(ctx, ws)
diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go
index e8a2547c65..edad8fc996 100644
--- a/models/webhook/webhook_test.go
+++ b/models/webhook/webhook_test.go
@@ -73,7 +73,7 @@ func TestWebhook_EventsArray(t *testing.T) {
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
"pull_request_review_comment", "pull_request_sync", "pull_request_review_request", "wiki", "repository", "release",
- "package", "status", "workflow_job",
+ "package", "status", "workflow_run", "workflow_job",
},
(&Webhook{
HookEvent: &webhook_module.HookEvent{SendEverything: true},
diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go
index a538b6e290..27bcafa649 100644
--- a/modules/actions/workflows.go
+++ b/modules/actions/workflows.go
@@ -6,6 +6,7 @@ package actions
import (
"bytes"
"io"
+ "slices"
"strings"
"code.gitea.io/gitea/modules/git"
@@ -43,21 +44,23 @@ func IsWorkflow(path string) bool {
return strings.HasPrefix(path, ".gitea/workflows") || strings.HasPrefix(path, ".github/workflows")
}
-func ListWorkflows(commit *git.Commit) (git.Entries, error) {
- tree, err := commit.SubTree(".gitea/workflows")
+func ListWorkflows(commit *git.Commit) (string, git.Entries, error) {
+ rpath := ".gitea/workflows"
+ tree, err := commit.SubTree(rpath)
if _, ok := err.(git.ErrNotExist); ok {
- tree, err = commit.SubTree(".github/workflows")
+ rpath = ".github/workflows"
+ tree, err = commit.SubTree(rpath)
}
if _, ok := err.(git.ErrNotExist); ok {
- return nil, nil
+ return "", nil, nil
}
if err != nil {
- return nil, err
+ return "", nil, err
}
entries, err := tree.ListEntriesRecursiveFast()
if err != nil {
- return nil, err
+ return "", nil, err
}
ret := make(git.Entries, 0, len(entries))
@@ -66,7 +69,7 @@ func ListWorkflows(commit *git.Commit) (git.Entries, error) {
ret = append(ret, entry)
}
}
- return ret, nil
+ return rpath, ret, nil
}
func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) {
@@ -102,7 +105,7 @@ func DetectWorkflows(
payload api.Payloader,
detectSchedule bool,
) ([]*DetectedWorkflow, []*DetectedWorkflow, error) {
- entries, err := ListWorkflows(commit)
+ _, entries, err := ListWorkflows(commit)
if err != nil {
return nil, nil, err
}
@@ -147,7 +150,7 @@ func DetectWorkflows(
}
func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) {
- entries, err := ListWorkflows(commit)
+ _, entries, err := ListWorkflows(commit)
if err != nil {
return nil, err
}
@@ -243,6 +246,10 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
webhook_module.HookEventPackage:
return matchPackageEvent(payload.(*api.PackagePayload), evt)
+ case // workflow_run
+ webhook_module.HookEventWorkflowRun:
+ return matchWorkflowRunEvent(payload.(*api.WorkflowRunPayload), evt)
+
default:
log.Warn("unsupported event %q", triggedEvent)
return false
@@ -311,6 +318,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
matchTimes++
}
case "paths":
+ if refName.IsTag() {
+ matchTimes++
+ break
+ }
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
if err != nil {
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
@@ -324,6 +335,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
}
}
case "paths-ignore":
+ if refName.IsTag() {
+ matchTimes++
+ break
+ }
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
if err != nil {
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
@@ -554,21 +569,12 @@ func matchPullRequestReviewEvent(prPayload *api.PullRequestPayload, evt *jobpars
actions = append(actions, "submitted", "edited")
}
- matched := false
for _, val := range vals {
- for _, action := range actions {
- if glob.MustCompile(val, '/').Match(action) {
- matched = true
- break
- }
- }
- if matched {
+ if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
+ matchTimes++
break
}
}
- if matched {
- matchTimes++
- }
default:
log.Warn("pull request review event unsupported condition %q", cond)
}
@@ -603,21 +609,12 @@ func matchPullRequestReviewCommentEvent(prPayload *api.PullRequestPayload, evt *
actions = append(actions, "created", "edited")
}
- matched := false
for _, val := range vals {
- for _, action := range actions {
- if glob.MustCompile(val, '/').Match(action) {
- matched = true
- break
- }
- }
- if matched {
+ if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
+ matchTimes++
break
}
}
- if matched {
- matchTimes++
- }
default:
log.Warn("pull request review comment event unsupported condition %q", cond)
}
@@ -698,3 +695,53 @@ func matchPackageEvent(payload *api.PackagePayload, evt *jobparser.Event) bool {
}
return matchTimes == len(evt.Acts())
}
+
+func matchWorkflowRunEvent(payload *api.WorkflowRunPayload, evt *jobparser.Event) bool {
+ // with no special filter parameters
+ if len(evt.Acts()) == 0 {
+ return true
+ }
+
+ matchTimes := 0
+ // all acts conditions should be satisfied
+ for cond, vals := range evt.Acts() {
+ switch cond {
+ case "types":
+ action := payload.Action
+ for _, val := range vals {
+ if glob.MustCompile(val, '/').Match(action) {
+ matchTimes++
+ break
+ }
+ }
+ case "workflows":
+ workflow := payload.Workflow
+ patterns, err := workflowpattern.CompilePatterns(vals...)
+ if err != nil {
+ break
+ }
+ if !workflowpattern.Skip(patterns, []string{workflow.Name}, &workflowpattern.EmptyTraceWriter{}) {
+ matchTimes++
+ }
+ case "branches":
+ patterns, err := workflowpattern.CompilePatterns(vals...)
+ if err != nil {
+ break
+ }
+ if !workflowpattern.Skip(patterns, []string{payload.WorkflowRun.HeadBranch}, &workflowpattern.EmptyTraceWriter{}) {
+ matchTimes++
+ }
+ case "branches-ignore":
+ patterns, err := workflowpattern.CompilePatterns(vals...)
+ if err != nil {
+ break
+ }
+ if !workflowpattern.Filter(patterns, []string{payload.WorkflowRun.HeadBranch}, &workflowpattern.EmptyTraceWriter{}) {
+ matchTimes++
+ }
+ default:
+ log.Warn("workflow run event unsupported condition %q", cond)
+ }
+ }
+ return matchTimes == len(evt.Acts())
+}
diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go
index c8e1e553fe..e23431651d 100644
--- a/modules/actions/workflows_test.go
+++ b/modules/actions/workflows_test.go
@@ -125,6 +125,24 @@ func TestDetectMatched(t *testing.T) {
yamlOn: "on: schedule",
expected: true,
},
+ {
+ desc: "push to tag matches workflow with paths condition (should skip paths check)",
+ triggedEvent: webhook_module.HookEventPush,
+ payload: &api.PushPayload{
+ Ref: "refs/tags/v1.0.0",
+ Before: "0000000",
+ Commits: []*api.PayloadCommit{
+ {
+ ID: "abcdef123456",
+ Added: []string{"src/main.go"},
+ Message: "Release v1.0.0",
+ },
+ },
+ },
+ commit: nil,
+ yamlOn: "on:\n push:\n paths:\n - src/**",
+ expected: true,
+ },
}
for _, tc := range testCases {
diff --git a/modules/assetfs/embed.go b/modules/assetfs/embed.go
new file mode 100644
index 0000000000..95176372d1
--- /dev/null
+++ b/modules/assetfs/embed.go
@@ -0,0 +1,375 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package assetfs
+
+import (
+ "bytes"
+ "compress/gzip"
+ "io"
+ "io/fs"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/util"
+)
+
+type EmbeddedFile interface {
+ io.ReadSeeker
+ fs.ReadDirFile
+ ReadDir(n int) ([]fs.DirEntry, error)
+}
+
+type EmbeddedFileInfo interface {
+ fs.FileInfo
+ fs.DirEntry
+ GetGzipContent() ([]byte, bool)
+}
+
+type decompressor interface {
+ io.Reader
+ Close() error
+ Reset(io.Reader) error
+}
+
+type embeddedFileInfo struct {
+ fs *embeddedFS
+ fullName string
+ data []byte
+
+ BaseName string `json:"n"`
+ OriginSize int64 `json:"s,omitempty"`
+ DataBegin int64 `json:"b,omitempty"`
+ DataLen int64 `json:"l,omitempty"`
+ Children []*embeddedFileInfo `json:"c,omitempty"`
+}
+
+func (fi *embeddedFileInfo) GetGzipContent() ([]byte, bool) {
+ // when generating the bindata, if the compressed data equals or is larger than the original data, we store the original data
+ if fi.DataLen == fi.OriginSize {
+ return nil, false
+ }
+ return fi.data, true
+}
+
+type EmbeddedFileBase struct {
+ info *embeddedFileInfo
+ dataReader io.ReadSeeker
+ seekPos int64
+}
+
+func (f *EmbeddedFileBase) ReadDir(n int) ([]fs.DirEntry, error) {
+ // this method is used to satisfy the "func (f ioFile) ReadDir(...)" in httpfs
+ l, err := f.info.fs.ReadDir(f.info.fullName)
+ if err != nil {
+ return nil, err
+ }
+ if n < 0 || n > len(l) {
+ return l, nil
+ }
+ return l[:n], nil
+}
+
+type EmbeddedOriginFile struct {
+ EmbeddedFileBase
+}
+
+type EmbeddedCompressedFile struct {
+ EmbeddedFileBase
+ decompressor decompressor
+ decompressorPos int64
+}
+
+type embeddedFS struct {
+ meta func() *EmbeddedMeta
+
+ files map[string]*embeddedFileInfo
+ filesMu sync.RWMutex
+
+ data []byte
+}
+
+type EmbeddedMeta struct {
+ Root *embeddedFileInfo
+}
+
+func NewEmbeddedFS(data []byte) fs.ReadDirFS {
+ efs := &embeddedFS{data: data, files: make(map[string]*embeddedFileInfo)}
+ efs.meta = sync.OnceValue(func() *EmbeddedMeta {
+ var meta EmbeddedMeta
+ p := bytes.LastIndexByte(data, '\n')
+ if p < 0 {
+ return &meta
+ }
+ if err := json.Unmarshal(data[p+1:], &meta); err != nil {
+ panic("embedded file is not valid")
+ }
+ return &meta
+ })
+ return efs
+}
+
+var _ fs.ReadDirFS = (*embeddedFS)(nil)
+
+func (e *embeddedFS) ReadDir(name string) (l []fs.DirEntry, err error) {
+ fi, err := e.getFileInfo(name)
+ if err != nil {
+ return nil, err
+ }
+ if !fi.IsDir() {
+ return nil, fs.ErrNotExist
+ }
+ l = make([]fs.DirEntry, len(fi.Children))
+ for i, child := range fi.Children {
+ l[i], err = e.getFileInfo(name + "/" + child.BaseName)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return l, nil
+}
+
+func (e *embeddedFS) getFileInfo(fullName string) (*embeddedFileInfo, error) {
+ // no need to do heavy "path.Clean()" because we don't want to support "foo/../bar" or absolute paths
+ fullName = strings.TrimPrefix(fullName, "./")
+ if fullName == "" {
+ fullName = "."
+ }
+
+ e.filesMu.RLock()
+ fi := e.files[fullName]
+ e.filesMu.RUnlock()
+ if fi != nil {
+ return fi, nil
+ }
+
+ fields := strings.Split(fullName, "/")
+ fi = e.meta().Root
+ if fullName != "." {
+ found := true
+ for _, field := range fields {
+ for _, child := range fi.Children {
+ if found = child.BaseName == field; found {
+ fi = child
+ break
+ }
+ }
+ if !found {
+ return nil, fs.ErrNotExist
+ }
+ }
+ }
+
+ e.filesMu.Lock()
+ defer e.filesMu.Unlock()
+ if fi != nil {
+ fi.fs = e
+ fi.fullName = fullName
+ fi.data = e.data[fi.DataBegin : fi.DataBegin+fi.DataLen]
+ e.files[fullName] = fi // do not cache nil, otherwise keeping accessing random non-existing file will cause OOM
+ return fi, nil
+ }
+ return nil, fs.ErrNotExist
+}
+
+func (e *embeddedFS) Open(name string) (fs.File, error) {
+ info, err := e.getFileInfo(name)
+ if err != nil {
+ return nil, err
+ }
+ base := EmbeddedFileBase{info: info}
+ base.dataReader = bytes.NewReader(base.info.data)
+ if info.DataLen != info.OriginSize {
+ decomp, err := gzip.NewReader(base.dataReader)
+ if err != nil {
+ return nil, err
+ }
+ return &EmbeddedCompressedFile{EmbeddedFileBase: base, decompressor: decomp}, nil
+ }
+ return &EmbeddedOriginFile{base}, nil
+}
+
+var (
+ _ EmbeddedFileInfo = (*embeddedFileInfo)(nil)
+ _ EmbeddedFile = (*EmbeddedOriginFile)(nil)
+ _ EmbeddedFile = (*EmbeddedCompressedFile)(nil)
+)
+
+func (f *EmbeddedOriginFile) Read(p []byte) (n int, err error) {
+ return f.dataReader.Read(p)
+}
+
+func (f *EmbeddedCompressedFile) Read(p []byte) (n int, err error) {
+ if f.decompressorPos > f.seekPos {
+ if err = f.decompressor.Reset(bytes.NewReader(f.info.data)); err != nil {
+ return 0, err
+ }
+ f.decompressorPos = 0
+ }
+ if f.decompressorPos < f.seekPos {
+ if _, err = io.CopyN(io.Discard, f.decompressor, f.seekPos-f.decompressorPos); err != nil {
+ return 0, err
+ }
+ f.decompressorPos = f.seekPos
+ }
+ n, err = f.decompressor.Read(p)
+ f.decompressorPos += int64(n)
+ f.seekPos = f.decompressorPos
+ return n, err
+}
+
+func (f *EmbeddedFileBase) Seek(offset int64, whence int) (int64, error) {
+ switch whence {
+ case io.SeekStart:
+ f.seekPos = offset
+ case io.SeekCurrent:
+ f.seekPos += offset
+ case io.SeekEnd:
+ f.seekPos = f.info.OriginSize + offset
+ }
+ return f.seekPos, nil
+}
+
+func (f *EmbeddedFileBase) Stat() (fs.FileInfo, error) {
+ return f.info, nil
+}
+
+func (f *EmbeddedOriginFile) Close() error {
+ return nil
+}
+
+func (f *EmbeddedCompressedFile) Close() error {
+ return f.decompressor.Close()
+}
+
+func (fi *embeddedFileInfo) Name() string {
+ return fi.BaseName
+}
+
+func (fi *embeddedFileInfo) Size() int64 {
+ return fi.OriginSize
+}
+
+func (fi *embeddedFileInfo) Mode() fs.FileMode {
+ return util.Iif(fi.IsDir(), fs.ModeDir|0o555, 0o444)
+}
+
+func (fi *embeddedFileInfo) ModTime() time.Time {
+ return getExecutableModTime()
+}
+
+func (fi *embeddedFileInfo) IsDir() bool {
+ return fi.Children != nil
+}
+
+func (fi *embeddedFileInfo) Sys() any {
+ return nil
+}
+
+func (fi *embeddedFileInfo) Type() fs.FileMode {
+ return util.Iif(fi.IsDir(), fs.ModeDir, 0)
+}
+
+func (fi *embeddedFileInfo) Info() (fs.FileInfo, error) {
+ return fi, nil
+}
+
+// getExecutableModTime returns the modification time of the executable file.
+// In bindata, we can't use the ModTime of the files because we need to make the build reproducible
+var getExecutableModTime = sync.OnceValue(func() (modTime time.Time) {
+ exePath, err := os.Executable()
+ if err != nil {
+ return modTime
+ }
+ exePath, err = filepath.Abs(exePath)
+ if err != nil {
+ return modTime
+ }
+ exePath, err = filepath.EvalSymlinks(exePath)
+ if err != nil {
+ return modTime
+ }
+ st, err := os.Stat(exePath)
+ if err != nil {
+ return modTime
+ }
+ return st.ModTime()
+})
+
+func GenerateEmbedBindata(fsRootPath, outputFile string) error {
+ output, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
+ if err != nil {
+ return err
+ }
+ defer output.Close()
+
+ meta := &EmbeddedMeta{}
+ meta.Root = &embeddedFileInfo{}
+ var outputOffset int64
+ var embedFiles func(parent *embeddedFileInfo, fsPath, embedPath string) error
+ embedFiles = func(parent *embeddedFileInfo, fsPath, embedPath string) error {
+ dirEntries, err := os.ReadDir(fsPath)
+ if err != nil {
+ return err
+ }
+ for _, dirEntry := range dirEntries {
+ if err != nil {
+ return err
+ }
+ if dirEntry.IsDir() {
+ child := &embeddedFileInfo{
+ BaseName: dirEntry.Name(),
+ Children: []*embeddedFileInfo{}, // non-nil means it's a directory
+ }
+ parent.Children = append(parent.Children, child)
+ if err = embedFiles(child, filepath.Join(fsPath, dirEntry.Name()), path.Join(embedPath, dirEntry.Name())); err != nil {
+ return err
+ }
+ } else {
+ data, err := os.ReadFile(filepath.Join(fsPath, dirEntry.Name()))
+ if err != nil {
+ return err
+ }
+ var compressed bytes.Buffer
+ gz, _ := gzip.NewWriterLevel(&compressed, gzip.BestCompression)
+ if _, err = gz.Write(data); err != nil {
+ return err
+ }
+ if err = gz.Close(); err != nil {
+ return err
+ }
+
+ // only use the compressed data if it is smaller than the original data
+ outputBytes := util.Iif(len(compressed.Bytes()) < len(data), compressed.Bytes(), data)
+ child := &embeddedFileInfo{
+ BaseName: dirEntry.Name(),
+ OriginSize: int64(len(data)),
+ DataBegin: outputOffset,
+ DataLen: int64(len(outputBytes)),
+ }
+ if _, err = output.Write(outputBytes); err != nil {
+ return err
+ }
+ outputOffset += child.DataLen
+ parent.Children = append(parent.Children, child)
+ }
+ }
+ return nil
+ }
+
+ if err = embedFiles(meta.Root, fsRootPath, ""); err != nil {
+ return err
+ }
+ jsonBuf, err := json.Marshal(meta) // can't use json.NewEncoder here because it writes extra EOL
+ if err != nil {
+ return err
+ }
+ _, _ = output.Write([]byte{'\n'})
+ _, err = output.Write(jsonBuf)
+ return err
+}
diff --git a/modules/assetfs/embed_test.go b/modules/assetfs/embed_test.go
new file mode 100644
index 0000000000..06598da4c4
--- /dev/null
+++ b/modules/assetfs/embed_test.go
@@ -0,0 +1,98 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package assetfs
+
+import (
+ "bytes"
+ "io/fs"
+ "net/http"
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestEmbed(t *testing.T) {
+ tmpDir := t.TempDir()
+ tmpDataDir := tmpDir + "/data"
+ _ = os.MkdirAll(tmpDataDir+"/foo/bar", 0o755)
+ _ = os.WriteFile(tmpDataDir+"/a.txt", []byte("a"), 0o644)
+ _ = os.WriteFile(tmpDataDir+"/foo/bar/b.txt", bytes.Repeat([]byte("a"), 1000), 0o644)
+ _ = os.WriteFile(tmpDataDir+"/foo/c.txt", []byte("c"), 0o644)
+ require.NoError(t, GenerateEmbedBindata(tmpDataDir, tmpDir+"/out.dat"))
+
+ data, err := os.ReadFile(tmpDir + "/out.dat")
+ require.NoError(t, err)
+ efs := NewEmbeddedFS(data)
+
+ // test a non-existing file
+ _, err = fs.ReadFile(efs, "not exist")
+ assert.ErrorIs(t, err, fs.ErrNotExist)
+
+ // test a normal file (no compression)
+ content, err := fs.ReadFile(efs, "a.txt")
+ require.NoError(t, err)
+ assert.Equal(t, "a", string(content))
+ fi, err := fs.Stat(efs, "a.txt")
+ require.NoError(t, err)
+ _, ok := fi.(EmbeddedFileInfo).GetGzipContent()
+ assert.False(t, ok)
+
+ // test a compressed file
+ content, err = fs.ReadFile(efs, "foo/bar/b.txt")
+ require.NoError(t, err)
+ assert.Equal(t, bytes.Repeat([]byte("a"), 1000), content)
+ fi, err = fs.Stat(efs, "foo/bar/b.txt")
+ require.NoError(t, err)
+ assert.False(t, fi.Mode().IsDir())
+ assert.True(t, fi.Mode().IsRegular())
+ gzipContent, ok := fi.(EmbeddedFileInfo).GetGzipContent()
+ assert.True(t, ok)
+ assert.Greater(t, len(gzipContent), 1)
+ assert.Less(t, len(gzipContent), 1000)
+
+ // test list root directory
+ entries, err := fs.ReadDir(efs, ".")
+ require.NoError(t, err)
+ assert.Len(t, entries, 2)
+ assert.Equal(t, "a.txt", entries[0].Name())
+ assert.False(t, entries[0].IsDir())
+
+ // test list subdirectory
+ entries, err = fs.ReadDir(efs, "foo")
+ require.NoError(t, err)
+ require.Len(t, entries, 2)
+ assert.Equal(t, "bar", entries[0].Name())
+ assert.True(t, entries[0].IsDir())
+ assert.Equal(t, "c.txt", entries[1].Name())
+ assert.False(t, entries[1].IsDir())
+
+ // test directory mode
+ fi, err = fs.Stat(efs, "foo")
+ require.NoError(t, err)
+ assert.True(t, fi.IsDir())
+ assert.True(t, fi.Mode().IsDir())
+ assert.False(t, fi.Mode().IsRegular())
+
+ // test httpfs
+ hfs := http.FS(efs)
+ hf, err := hfs.Open("foo/bar/b.txt")
+ require.NoError(t, err)
+ hi, err := hf.Stat()
+ require.NoError(t, err)
+ fiEmbedded, ok := hi.(EmbeddedFileInfo)
+ require.True(t, ok)
+ gzipContent, ok = fiEmbedded.GetGzipContent()
+ assert.True(t, ok)
+ assert.Greater(t, len(gzipContent), 1)
+ assert.Less(t, len(gzipContent), 1000)
+
+ // test httpfs directory listing
+ hf, err = hfs.Open("foo")
+ require.NoError(t, err)
+ dirs, err := hf.Readdir(1)
+ require.NoError(t, err)
+ assert.Len(t, dirs, 1)
+}
diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go
index 4f3811ba2b..ce55475bd9 100644
--- a/modules/assetfs/layered.go
+++ b/modules/assetfs/layered.go
@@ -52,8 +52,8 @@ func Local(name, base string, sub ...string) *Layer {
}
// Bindata returns a new Layer with the given name, it serves files from the given bindata asset.
-func Bindata(name string, fs http.FileSystem) *Layer {
- return &Layer{name: name, fs: fs}
+func Bindata(name string, fs fs.FS) *Layer {
+ return &Layer{name: name, fs: http.FS(fs)}
}
// LayeredFS is a layered asset file-system. It works like http.FileSystem, but it can have multiple layers.
diff --git a/modules/auth/httpauth/httpauth.go b/modules/auth/httpauth/httpauth.go
new file mode 100644
index 0000000000..7f1f1ee152
--- /dev/null
+++ b/modules/auth/httpauth/httpauth.go
@@ -0,0 +1,47 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package httpauth
+
+import (
+ "encoding/base64"
+ "strings"
+
+ "code.gitea.io/gitea/modules/util"
+)
+
+type BasicAuth struct {
+ Username, Password string
+}
+
+type BearerToken struct {
+ Token string
+}
+
+type ParsedAuthorizationHeader struct {
+ BasicAuth *BasicAuth
+ BearerToken *BearerToken
+}
+
+func ParseAuthorizationHeader(header string) (ret ParsedAuthorizationHeader, _ bool) {
+ parts := strings.Fields(header)
+ if len(parts) != 2 {
+ return ret, false
+ }
+ if util.AsciiEqualFold(parts[0], "basic") {
+ s, err := base64.StdEncoding.DecodeString(parts[1])
+ if err != nil {
+ return ret, false
+ }
+ u, p, ok := strings.Cut(string(s), ":")
+ if !ok {
+ return ret, false
+ }
+ ret.BasicAuth = &BasicAuth{Username: u, Password: p}
+ return ret, true
+ } else if util.AsciiEqualFold(parts[0], "token") || util.AsciiEqualFold(parts[0], "bearer") {
+ ret.BearerToken = &BearerToken{Token: parts[1]}
+ return ret, true
+ }
+ return ret, false
+}
diff --git a/modules/auth/httpauth/httpauth_test.go b/modules/auth/httpauth/httpauth_test.go
new file mode 100644
index 0000000000..087b86917f
--- /dev/null
+++ b/modules/auth/httpauth/httpauth_test.go
@@ -0,0 +1,43 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package httpauth
+
+import (
+ "encoding/base64"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseAuthorizationHeader(t *testing.T) {
+ type parsed = ParsedAuthorizationHeader
+ type basic = BasicAuth
+ type bearer = BearerToken
+ cases := []struct {
+ headerValue string
+ expected parsed
+ ok bool
+ }{
+ {"", parsed{}, false},
+ {"?", parsed{}, false},
+ {"foo", parsed{}, false},
+ {"any value", parsed{}, false},
+
+ {"Basic ?", parsed{}, false},
+ {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo")), parsed{}, false},
+ {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar")), parsed{BasicAuth: &basic{"foo", "bar"}}, true},
+ {"basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar")), parsed{BasicAuth: &basic{"foo", "bar"}}, true},
+
+ {"token value", parsed{BearerToken: &bearer{"value"}}, true},
+ {"Token value", parsed{BearerToken: &bearer{"value"}}, true},
+ {"bearer value", parsed{BearerToken: &bearer{"value"}}, true},
+ {"Bearer value", parsed{BearerToken: &bearer{"value"}}, true},
+ {"Bearer wrong value", parsed{}, false},
+ }
+ for _, c := range cases {
+ ret, ok := ParseAuthorizationHeader(c.headerValue)
+ assert.Equal(t, c.ok, ok, "header %q", c.headerValue)
+ assert.Equal(t, c.expected, ret, "header %q", c.headerValue)
+ }
+}
diff --git a/modules/auth/openid/discovery_cache_test.go b/modules/auth/openid/discovery_cache_test.go
index 7d4b27c5df..f3d7dd226e 100644
--- a/modules/auth/openid/discovery_cache_test.go
+++ b/modules/auth/openid/discovery_cache_test.go
@@ -26,7 +26,8 @@ func (s *testDiscoveredInfo) OpLocalID() string {
}
func TestTimedDiscoveryCache(t *testing.T) {
- dc := newTimedDiscoveryCache(1 * time.Second)
+ ttl := 50 * time.Millisecond
+ dc := newTimedDiscoveryCache(ttl)
// Put some initial values
dc.Put("foo", &testDiscoveredInfo{}) // openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"})
@@ -41,8 +42,8 @@ func TestTimedDiscoveryCache(t *testing.T) {
// Attempt to get a non-existent value
assert.Nil(t, dc.Get("bar"))
- // Sleep one second and try retrieve again
- time.Sleep(1 * time.Second)
+ // Sleep for a while and try to retrieve again
+ time.Sleep(ttl * 3 / 2)
assert.Nil(t, dc.Get("foo"))
}
diff --git a/modules/auth/password/hash/common.go b/modules/auth/password/hash/common.go
index 487c0738f4..d5e2c34314 100644
--- a/modules/auth/password/hash/common.go
+++ b/modules/auth/password/hash/common.go
@@ -18,7 +18,7 @@ func parseIntParam(value, param, algorithmName, config string, previousErr error
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
}
-func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) { //nolint:unparam
+func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) { //nolint:unparam // algorithmName is always argon2
parsed, err := strconv.ParseUint(value, 10, 64)
if err != nil {
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go
index c66b62937f..a1e101dd62 100644
--- a/modules/auth/password/password.go
+++ b/modules/auth/password/password.go
@@ -101,7 +101,7 @@ func Generate(n int) (string, error) {
buffer := make([]byte, n)
maxInt := big.NewInt(int64(len(validChars)))
for {
- for j := 0; j < n; j++ {
+ for j := range n {
rnd, err := rand.Int(rand.Reader, maxInt)
if err != nil {
return "", err
diff --git a/modules/auth/password/password_test.go b/modules/auth/password/password_test.go
index 6c35dc86bd..0fea593c85 100644
--- a/modules/auth/password/password_test.go
+++ b/modules/auth/password/password_test.go
@@ -50,7 +50,7 @@ func TestComplexity_Generate(t *testing.T) {
test := func(t *testing.T, modes []string) {
testComplextity(modes)
- for i := 0; i < maxCount; i++ {
+ for range maxCount {
pwd, err := Generate(pwdLen)
assert.NoError(t, err)
assert.Len(t, pwd, pwdLen)
diff --git a/modules/auth/password/pwn/pwn.go b/modules/auth/password/pwn/pwn.go
index f77ce9f40b..99a6ca6cea 100644
--- a/modules/auth/password/pwn/pwn.go
+++ b/modules/auth/password/pwn/pwn.go
@@ -101,7 +101,7 @@ func (c *Client) CheckPassword(pw string, padding bool) (int, error) {
}
defer resp.Body.Close()
- for _, pair := range strings.Split(string(body), "\n") {
+ for pair := range strings.SplitSeq(string(body), "\n") {
parts := strings.Split(pair, ":")
if len(parts) != 2 {
continue
diff --git a/modules/avatar/identicon/block.go b/modules/avatar/identicon/block.go
index cb1803a231..fc8ce90212 100644
--- a/modules/avatar/identicon/block.go
+++ b/modules/avatar/identicon/block.go
@@ -24,8 +24,8 @@ func drawBlock(img *image.Paletted, x, y, size, angle int, points []int) {
rotate(points, m, m, angle)
}
- for i := 0; i < size; i++ {
- for j := 0; j < size; j++ {
+ for i := range size {
+ for j := range size {
if pointInPolygon(i, j, points) {
img.SetColorIndex(x+i, y+j, 1)
}
diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go
index 87bd87796e..ee92416a53 100644
--- a/modules/avatar/identicon/identicon.go
+++ b/modules/avatar/identicon/identicon.go
@@ -134,7 +134,7 @@ func drawBlocks(p *image.Paletted, size int, c, b1, b2 blockFunc, b1Angle, b2Ang
// then we make it left-right mirror, so we didn't draw 3/6/9 before
for x := 0; x < size/2; x++ {
- for y := 0; y < size; y++ {
+ for y := range size {
p.SetColorIndex(size-x, y, p.ColorIndexAt(x, y))
}
}
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 02ca85569e..ed94575e74 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -8,13 +8,10 @@ import (
"crypto/sha1"
"crypto/sha256"
"crypto/subtle"
- "encoding/base64"
"encoding/hex"
- "errors"
"fmt"
"hash"
"strconv"
- "strings"
"time"
"code.gitea.io/gitea/modules/setting"
@@ -36,19 +33,6 @@ func ShortSha(sha1 string) string {
return util.TruncateRunes(sha1, 10)
}
-// BasicAuthDecode decode basic auth string
-func BasicAuthDecode(encoded string) (string, string, error) {
- s, err := base64.StdEncoding.DecodeString(encoded)
- if err != nil {
- return "", "", err
- }
-
- if username, password, ok := strings.Cut(string(s), ":"); ok {
- return username, password, nil
- }
- return "", "", errors.New("invalid basic authentication")
-}
-
// VerifyTimeLimitCode verify time limit code
func VerifyTimeLimitCode(now time.Time, data string, minutes int, code string) bool {
if len(code) <= 18 {
diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go
index 7cebedb073..b7365e40c4 100644
--- a/modules/base/tool_test.go
+++ b/modules/base/tool_test.go
@@ -26,25 +26,6 @@ func TestShortSha(t *testing.T) {
assert.Equal(t, "veryverylo", ShortSha("veryverylong"))
}
-func TestBasicAuthDecode(t *testing.T) {
- _, _, err := BasicAuthDecode("?")
- assert.Equal(t, "illegal base64 data at input byte 0", err.Error())
-
- user, pass, err := BasicAuthDecode("Zm9vOmJhcg==")
- assert.NoError(t, err)
- assert.Equal(t, "foo", user)
- assert.Equal(t, "bar", pass)
-
- _, _, err = BasicAuthDecode("aW52YWxpZA==")
- assert.Error(t, err)
-
- _, _, err = BasicAuthDecode("invalid")
- assert.Error(t, err)
-
- _, _, err = BasicAuthDecode("YWxpY2U=") // "alice", no colon
- assert.Error(t, err)
-}
-
func TestVerifyTimeLimitCode(t *testing.T) {
defer test.MockVariableValue(&setting.InstallLock, true)()
initGeneralSecret := func(secret string) {
diff --git a/modules/cache/cache.go b/modules/cache/cache.go
index a434c13b67..039caa9fbc 100644
--- a/modules/cache/cache.go
+++ b/modules/cache/cache.go
@@ -24,7 +24,7 @@ func Init() error {
if err != nil {
return err
}
- for i := 0; i < 10; i++ {
+ for range 10 {
if err = c.Ping(); err == nil {
break
}
diff --git a/modules/cache/cache_redis.go b/modules/cache/cache_redis.go
index c5b52a2086..7473c938af 100644
--- a/modules/cache/cache_redis.go
+++ b/modules/cache/cache_redis.go
@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/nosql"
- "gitea.com/go-chi/cache" //nolint:depguard
+ "gitea.com/go-chi/cache" //nolint:depguard // we wrap this package here
"github.com/redis/go-redis/v9"
)
diff --git a/modules/cache/cache_twoqueue.go b/modules/cache/cache_twoqueue.go
index 1eda2debc4..c8db686e57 100644
--- a/modules/cache/cache_twoqueue.go
+++ b/modules/cache/cache_twoqueue.go
@@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/modules/json"
- mc "gitea.com/go-chi/cache" //nolint:depguard
+ mc "gitea.com/go-chi/cache" //nolint:depguard // we wrap this package here
lru "github.com/hashicorp/golang-lru/v2"
)
diff --git a/modules/cache/string_cache.go b/modules/cache/string_cache.go
index 4f659616f5..3562b7a926 100644
--- a/modules/cache/string_cache.go
+++ b/modules/cache/string_cache.go
@@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
- chi_cache "gitea.com/go-chi/cache" //nolint:depguard
+ chi_cache "gitea.com/go-chi/cache" //nolint:depguard // we wrap this package here
)
type GetJSONError struct {
diff --git a/modules/charset/charset.go b/modules/charset/charset.go
index 1855446a98..597ce5120c 100644
--- a/modules/charset/charset.go
+++ b/modules/charset/charset.go
@@ -164,7 +164,7 @@ func DetectEncoding(content []byte) (string, error) {
}
times := 1024 / len(content)
detectContent = make([]byte, 0, times*len(content))
- for i := 0; i < times; i++ {
+ for range times {
detectContent = append(detectContent, content...)
}
} else {
diff --git a/modules/charset/charset_test.go b/modules/charset/charset_test.go
index 1fb362654d..cd2e3b9aaa 100644
--- a/modules/charset/charset_test.go
+++ b/modules/charset/charset_test.go
@@ -242,7 +242,7 @@ func stringMustEndWith(t *testing.T, expected, value string) {
func TestToUTF8WithFallbackReader(t *testing.T) {
resetDefaultCharsetsOrder()
- for testLen := 0; testLen < 2048; testLen++ {
+ for testLen := range 2048 {
pattern := " test { () }\n"
input := ""
for len(input) < testLen {
diff --git a/modules/structs/commit_status.go b/modules/commitstatus/commit_status.go
index dc880ef5eb..a0ab4e7186 100644
--- a/modules/structs/commit_status.go
+++ b/modules/commitstatus/commit_status.go
@@ -1,11 +1,11 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package structs
+package commitstatus
// CommitStatusState holds the state of a CommitStatus
-// It can be "pending", "success", "error" and "failure"
-type CommitStatusState string
+// swagger:enum CommitStatusState
+type CommitStatusState string //nolint:revive // export stutter
const (
// CommitStatusPending is for when the CommitStatus is Pending
@@ -18,35 +18,14 @@ const (
CommitStatusFailure CommitStatusState = "failure"
// CommitStatusWarning is for when the CommitStatus is Warning
CommitStatusWarning CommitStatusState = "warning"
+ // CommitStatusSkipped is for when CommitStatus is Skipped
+ CommitStatusSkipped CommitStatusState = "skipped"
)
-var commitStatusPriorities = map[CommitStatusState]int{
- CommitStatusError: 0,
- CommitStatusFailure: 1,
- CommitStatusWarning: 2,
- CommitStatusPending: 3,
- CommitStatusSuccess: 4,
-}
-
func (css CommitStatusState) String() string {
return string(css)
}
-// NoBetterThan returns true if this State is no better than the given State
-// This function only handles the states defined in CommitStatusPriorities
-func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool {
- // NoBetterThan only handles the 5 states above
- if _, exist := commitStatusPriorities[css]; !exist {
- return false
- }
-
- if _, exist := commitStatusPriorities[css2]; !exist {
- return false
- }
-
- return commitStatusPriorities[css] <= commitStatusPriorities[css2]
-}
-
// IsPending represents if commit status state is pending
func (css CommitStatusState) IsPending() bool {
return css == CommitStatusPending
@@ -71,3 +50,32 @@ func (css CommitStatusState) IsFailure() bool {
func (css CommitStatusState) IsWarning() bool {
return css == CommitStatusWarning
}
+
+// IsSkipped represents if commit status state is skipped
+func (css CommitStatusState) IsSkipped() bool {
+ return css == CommitStatusSkipped
+}
+
+type CommitStatusStates []CommitStatusState //nolint:revive // export stutter
+
+// According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference
+// > Additionally, a combined state is returned. The state is one of:
+// > failure if any of the contexts report as error or failure
+// > pending if there are no statuses or a context is pending
+// > success if the latest status for all contexts is success
+func (css CommitStatusStates) Combine() CommitStatusState {
+ successCnt := 0
+ for _, state := range css {
+ switch {
+ case state.IsError() || state.IsFailure():
+ return CommitStatusFailure
+ case state.IsPending():
+ case state.IsSuccess() || state.IsWarning() || state.IsSkipped():
+ successCnt++
+ }
+ }
+ if successCnt > 0 && successCnt == len(css) {
+ return CommitStatusSuccess
+ }
+ return CommitStatusPending
+}
diff --git a/modules/commitstatus/commit_status_test.go b/modules/commitstatus/commit_status_test.go
new file mode 100644
index 0000000000..10d8f20aa4
--- /dev/null
+++ b/modules/commitstatus/commit_status_test.go
@@ -0,0 +1,201 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package commitstatus
+
+import "testing"
+
+func TestCombine(t *testing.T) {
+ tests := []struct {
+ name string
+ states CommitStatusStates
+ expected CommitStatusState
+ }{
+ // 0 states
+ {
+ name: "empty",
+ states: CommitStatusStates{},
+ expected: CommitStatusPending,
+ },
+ // 1 state
+ {
+ name: "pending",
+ states: CommitStatusStates{CommitStatusPending},
+ expected: CommitStatusPending,
+ },
+ {
+ name: "success",
+ states: CommitStatusStates{CommitStatusSuccess},
+ expected: CommitStatusSuccess,
+ },
+ {
+ name: "error",
+ states: CommitStatusStates{CommitStatusError},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "failure",
+ states: CommitStatusStates{CommitStatusFailure},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "warning",
+ states: CommitStatusStates{CommitStatusWarning},
+ expected: CommitStatusSuccess,
+ },
+ // 2 states
+ {
+ name: "pending and success",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess},
+ expected: CommitStatusPending,
+ },
+ {
+ name: "pending and error",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusError},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "pending and failure",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusFailure},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "pending and warning",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusWarning},
+ expected: CommitStatusPending,
+ },
+ {
+ name: "success and error",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusError},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "success and failure",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusFailure},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "success and warning",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning},
+ expected: CommitStatusSuccess,
+ },
+ {
+ name: "error and failure",
+ states: CommitStatusStates{CommitStatusError, CommitStatusFailure},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "error and warning",
+ states: CommitStatusStates{CommitStatusError, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "failure and warning",
+ states: CommitStatusStates{CommitStatusFailure, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ // 3 states
+ {
+ name: "pending, success and warning",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusWarning},
+ expected: CommitStatusPending,
+ },
+ {
+ name: "pending, success and error",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "pending, success and failure",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusFailure},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "pending, error and failure",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusError, CommitStatusFailure},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "success, error and warning",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusError, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "success, failure and warning",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusFailure, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "error, failure and warning",
+ states: CommitStatusStates{CommitStatusError, CommitStatusFailure, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "success, warning and skipped",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning, CommitStatusSkipped},
+ expected: CommitStatusSuccess,
+ },
+ // All success
+ {
+ name: "all success",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusSuccess},
+ expected: CommitStatusSuccess,
+ },
+ // All pending
+ {
+ name: "all pending",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusPending, CommitStatusPending},
+ expected: CommitStatusPending,
+ },
+ {
+ name: "all skipped",
+ states: CommitStatusStates{CommitStatusSkipped, CommitStatusSkipped, CommitStatusSkipped},
+ expected: CommitStatusSuccess,
+ },
+ // 4 states
+ {
+ name: "pending, success, error and warning",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "pending, success, failure and warning",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusFailure, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "pending, error, failure and warning",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusError, CommitStatusFailure, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "success, error, failure and warning",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusError, CommitStatusFailure, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "mixed states",
+ states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError, CommitStatusWarning},
+ expected: CommitStatusFailure,
+ },
+ {
+ name: "mixed states with all success",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusPending, CommitStatusWarning},
+ expected: CommitStatusPending,
+ },
+ {
+ name: "all success with warning",
+ states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusSuccess, CommitStatusWarning},
+ expected: CommitStatusSuccess,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := tt.states.Combine()
+ if result != tt.expected {
+ t.Errorf("expected %v, got %v", tt.expected, result)
+ }
+ })
+ }
+}
diff --git a/modules/fileicon/entry.go b/modules/fileicon/entry.go
index e4ded363e5..0326c2bfa8 100644
--- a/modules/fileicon/entry.go
+++ b/modules/fileicon/entry.go
@@ -6,17 +6,17 @@ package fileicon
import "code.gitea.io/gitea/modules/git"
type EntryInfo struct {
- FullName string
+ BaseName string
EntryMode git.EntryMode
SymlinkToMode git.EntryMode
IsOpen bool
}
-func EntryInfoFromGitTreeEntry(gitEntry *git.TreeEntry) *EntryInfo {
- ret := &EntryInfo{FullName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
+func EntryInfoFromGitTreeEntry(commit *git.Commit, fullPath string, gitEntry *git.TreeEntry) *EntryInfo {
+ ret := &EntryInfo{BaseName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
if gitEntry.IsLink() {
- if te, err := gitEntry.FollowLink(); err == nil && te.IsDir() {
- ret.SymlinkToMode = te.Mode()
+ if res, err := git.EntryFollowLink(commit, fullPath, gitEntry); err == nil && res.TargetEntry.IsDir() {
+ ret.SymlinkToMode = res.TargetEntry.Mode()
}
}
return ret
diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go
index 449f527ee8..5361592d8a 100644
--- a/modules/fileicon/material.go
+++ b/modules/fileicon/material.go
@@ -5,7 +5,6 @@ package fileicon
import (
"html/template"
- "path"
"strings"
"sync"
@@ -134,7 +133,7 @@ func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
return "folder-git"
}
- fileNameLower := strings.ToLower(path.Base(entry.FullName))
+ fileNameLower := strings.ToLower(entry.BaseName)
if entry.EntryMode.IsDir() {
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
return s
diff --git a/modules/fileicon/material_test.go b/modules/fileicon/material_test.go
index 68353d2189..d2a769eaac 100644
--- a/modules/fileicon/material_test.go
+++ b/modules/fileicon/material_test.go
@@ -20,8 +20,8 @@ func TestMain(m *testing.M) {
func TestFindIconName(t *testing.T) {
unittest.PrepareTestEnv(t)
p := fileicon.DefaultMaterialIconProvider()
- assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.php", EntryMode: git.EntryModeBlob}))
- assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.PHP", EntryMode: git.EntryModeBlob}))
- assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.js", EntryMode: git.EntryModeBlob}))
- assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.vba", EntryMode: git.EntryModeBlob}))
+ assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.php", EntryMode: git.EntryModeBlob}))
+ assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.PHP", EntryMode: git.EntryModeBlob}))
+ assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.js", EntryMode: git.EntryModeBlob}))
+ assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{BaseName: "foo.vba", EntryMode: git.EntryModeBlob}))
}
diff --git a/modules/git/attribute/checker.go b/modules/git/attribute/checker.go
index c17006a154..167b31416e 100644
--- a/modules/git/attribute/checker.go
+++ b/modules/git/attribute/checker.go
@@ -39,7 +39,12 @@ func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attrib
)
cancel = deleteTemporaryFile
}
- } // else: no treeish, assume it is a not a bare repo, read from working directory
+ } else {
+ // Read from existing index, in cases where the repo is bare and has an index,
+ // or the work tree contains unstaged changes that shouldn't affect the attribute check.
+ // It is caller's responsibility to add changed ".gitattributes" into the index if they want to respect the new changes.
+ cmd.AddArguments("--cached")
+ }
cmd.AddDynamicArguments(attributes...)
if len(filenames) > 0 {
diff --git a/modules/git/attribute/checker_test.go b/modules/git/attribute/checker_test.go
index 97db43460b..67fbda8918 100644
--- a/modules/git/attribute/checker_test.go
+++ b/modules/git/attribute/checker_test.go
@@ -57,8 +57,18 @@ func Test_Checker(t *testing.T) {
assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
})
+ t.Run("Run git check-attr in bare repository using index", func(t *testing.T) {
+ attrs, err := CheckAttributes(t.Context(), gitRepo, "", CheckAttributeOpts{
+ Filenames: []string{"i-am-a-python.p"},
+ Attributes: LinguistAttributes,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, attrs, 1)
+ assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
+ })
+
if !git.DefaultFeatures().SupportCheckAttrOnBare {
- t.Skip("git version 2.40 is required to support run check-attr on bare repo")
+ t.Skip("git version 2.40 is required to support run check-attr on bare repo without using index")
return
}
diff --git a/modules/git/blame.go b/modules/git/blame.go
index 6eb583a6b9..659dec34a1 100644
--- a/modules/git/blame.go
+++ b/modules/git/blame.go
@@ -132,18 +132,22 @@ func (r *BlameReader) Close() error {
}
// CreateBlameReader creates reader for given repository, commit and file
-func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
- reader, stdout, err := os.Pipe()
- if err != nil {
- return nil, err
- }
+func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (rd *BlameReader, err error) {
+ var ignoreRevsFileName string
+ var ignoreRevsFileCleanup func()
+ defer func() {
+ if err != nil && ignoreRevsFileCleanup != nil {
+ ignoreRevsFileCleanup()
+ }
+ }()
cmd := NewCommandNoGlobals("blame", "--porcelain")
- var ignoreRevsFileName string
- var ignoreRevsFileCleanup func() // TODO: maybe it should check the returned err in a defer func to make sure the cleanup could always be executed correctly
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
- ignoreRevsFileName, ignoreRevsFileCleanup = tryCreateBlameIgnoreRevsFile(commit)
+ ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit)
+ if err != nil && !IsErrNotExist(err) {
+ return nil, err
+ }
if ignoreRevsFileName != "" {
// Possible improvement: use --ignore-revs-file /dev/stdin on unix
// There is no equivalent on Windows. May be implemented if Gitea uses an external git backend.
@@ -154,6 +158,10 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file)
done := make(chan error, 1)
+ reader, stdout, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
go func() {
stderr := bytes.Buffer{}
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
@@ -182,33 +190,29 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
}, nil
}
-func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func()) {
+func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func(), error) {
entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs")
if err != nil {
- log.Error("Unable to get .git-blame-ignore-revs file: GetTreeEntryByPath: %v", err)
- return "", nil
+ return "", nil, err
}
r, err := entry.Blob().DataAsync()
if err != nil {
- log.Error("Unable to get .git-blame-ignore-revs file data: DataAsync: %v", err)
- return "", nil
+ return "", nil, err
}
defer r.Close()
f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("git-blame-ignore-revs")
if err != nil {
- log.Error("Unable to get .git-blame-ignore-revs file data: CreateTempFileRandom: %v", err)
- return "", nil
+ return "", nil, err
}
filename := f.Name()
_, err = io.Copy(f, r)
_ = f.Close()
if err != nil {
cleanup()
- log.Error("Unable to get .git-blame-ignore-revs file data: Copy: %v", err)
- return "", nil
+ return "", nil, err
}
- return filename, cleanup
+ return filename, cleanup, nil
}
diff --git a/modules/git/blob.go b/modules/git/blob.go
index b7857dbbc6..40d8f44e79 100644
--- a/modules/git/blob.go
+++ b/modules/git/blob.go
@@ -9,6 +9,7 @@ import (
"encoding/base64"
"errors"
"io"
+ "strings"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
@@ -21,17 +22,22 @@ func (b *Blob) Name() string {
return b.name
}
-// GetBlobContent Gets the limited content of the blob as raw text
-func (b *Blob) GetBlobContent(limit int64) (string, error) {
+// GetBlobBytes Gets the limited content of the blob
+func (b *Blob) GetBlobBytes(limit int64) ([]byte, error) {
if limit <= 0 {
- return "", nil
+ return nil, nil
}
dataRc, err := b.DataAsync()
if err != nil {
- return "", err
+ return nil, err
}
defer dataRc.Close()
- buf, err := util.ReadWithLimit(dataRc, int(limit))
+ return util.ReadWithLimit(dataRc, int(limit))
+}
+
+// GetBlobContent Gets the limited content of the blob as raw text
+func (b *Blob) GetBlobContent(limit int64) (string, error) {
+ buf, err := b.GetBlobBytes(limit)
return string(buf), err
}
@@ -63,42 +69,44 @@ func (b *Blob) GetBlobLineCount(w io.Writer) (int, error) {
}
}
-// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string
-func (b *Blob) GetBlobContentBase64() (string, error) {
+// GetBlobContentBase64 Reads the content of the blob with a base64 encoding and returns the encoded string
+func (b *Blob) GetBlobContentBase64(originContent *strings.Builder) (string, error) {
dataRc, err := b.DataAsync()
if err != nil {
return "", err
}
defer dataRc.Close()
- pr, pw := io.Pipe()
- encoder := base64.NewEncoder(base64.StdEncoding, pw)
-
- go func() {
- _, err := io.Copy(encoder, dataRc)
- _ = encoder.Close()
-
- if err != nil {
- _ = pw.CloseWithError(err)
- } else {
- _ = pw.Close()
+ base64buf := &strings.Builder{}
+ encoder := base64.NewEncoder(base64.StdEncoding, base64buf)
+ buf := make([]byte, 32*1024)
+loop:
+ for {
+ n, err := dataRc.Read(buf)
+ if n > 0 {
+ if originContent != nil {
+ _, _ = originContent.Write(buf[:n])
+ }
+ if _, err := encoder.Write(buf[:n]); err != nil {
+ return "", err
+ }
+ }
+ switch {
+ case errors.Is(err, io.EOF):
+ break loop
+ case err != nil:
+ return "", err
}
- }()
-
- out, err := io.ReadAll(pr)
- if err != nil {
- return "", err
}
- return string(out), nil
+ _ = encoder.Close()
+ return base64buf.String(), nil
}
// GuessContentType guesses the content type of the blob.
func (b *Blob) GuessContentType() (typesniffer.SniffedType, error) {
- r, err := b.DataAsync()
+ buf, err := b.GetBlobBytes(typesniffer.SniffContentSize)
if err != nil {
return typesniffer.SniffedType{}, err
}
- defer r.Close()
-
- return typesniffer.DetectContentTypeFromReader(r)
+ return typesniffer.DetectContentType(buf), nil
}
diff --git a/modules/git/cmdverb.go b/modules/git/cmdverb.go
new file mode 100644
index 0000000000..3d6f4ae0c6
--- /dev/null
+++ b/modules/git/cmdverb.go
@@ -0,0 +1,36 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+const (
+ CmdVerbUploadPack = "git-upload-pack"
+ CmdVerbUploadArchive = "git-upload-archive"
+ CmdVerbReceivePack = "git-receive-pack"
+ CmdVerbLfsAuthenticate = "git-lfs-authenticate"
+ CmdVerbLfsTransfer = "git-lfs-transfer"
+
+ CmdSubVerbLfsUpload = "upload"
+ CmdSubVerbLfsDownload = "download"
+)
+
+func IsAllowedVerbForServe(verb string) bool {
+ switch verb {
+ case CmdVerbUploadPack,
+ CmdVerbUploadArchive,
+ CmdVerbReceivePack,
+ CmdVerbLfsAuthenticate,
+ CmdVerbLfsTransfer:
+ return true
+ }
+ return false
+}
+
+func IsAllowedVerbForServeLfs(verb string) bool {
+ switch verb {
+ case CmdVerbLfsAuthenticate,
+ CmdVerbLfsTransfer:
+ return true
+ }
+ return false
+}
diff --git a/modules/git/command.go b/modules/git/command.go
index eaaa4969d0..22f1d02339 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -47,6 +47,7 @@ type Command struct {
globalArgsLength int
brokenArgs []string
cmd *exec.Cmd // for debug purpose only
+ configArgs []string
}
func logArgSanitize(arg string) string {
@@ -196,6 +197,16 @@ func (c *Command) AddDashesAndList(list ...string) *Command {
return c
}
+func (c *Command) AddConfig(key, value string) *Command {
+ kv := key + "=" + value
+ if !isSafeArgumentValue(kv) {
+ c.brokenArgs = append(c.brokenArgs, key)
+ } else {
+ c.configArgs = append(c.configArgs, "-c", kv)
+ }
+ return c
+}
+
// ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs
// In most cases, it shouldn't be used. Use NewCommand().AddXxx() function instead
func ToTrustedCmdArgs(args []string) TrustedCmdArgs {
@@ -321,7 +332,7 @@ func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error {
startTime := time.Now()
- cmd := exec.CommandContext(ctx, c.prog, c.args...)
+ cmd := exec.CommandContext(ctx, c.prog, append(c.configArgs, c.args...)...)
c.cmd = cmd // for debug purpose only
if opts.Env == nil {
cmd.Env = os.Environ()
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 3e790e89d9..ed4876e7b3 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -20,7 +20,8 @@ import (
// Commit represents a git commit.
type Commit struct {
- Tree
+ Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache"
+
ID ObjectID // The ID of this commit object
Author *Signature
Committer *Signature
@@ -34,7 +35,7 @@ type Commit struct {
// CommitSignature represents a git commit signature part.
type CommitSignature struct {
Signature string
- Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data
+ Payload string
}
// Message returns the commit message. Same as retrieving CommitMessage directly.
@@ -166,6 +167,8 @@ type CommitsCountOptions struct {
Not string
Revision []string
RelPath []string
+ Since string
+ Until string
}
// CommitsCount returns number of total commits of until given revision.
@@ -199,8 +202,8 @@ func (c *Commit) CommitsCount() (int64, error) {
}
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
-func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) {
- return c.repo.commitsByRange(c.ID, page, pageSize, not)
+func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
+ return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
}
// CommitsBefore returns all the commits before current revision
@@ -275,8 +278,8 @@ func NewSearchCommitsOptions(searchString string, forAllRefs bool) SearchCommits
var keywords, authors, committers []string
var after, before string
- fields := strings.Fields(searchString)
- for _, k := range fields {
+ fields := strings.FieldsSeq(searchString)
+ for k := range fields {
switch {
case strings.HasPrefix(k, "author:"):
authors = append(authors, strings.TrimPrefix(k, "author:"))
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
index 7a6af0410b..1b45fc8a6c 100644
--- a/modules/git/commit_info_nogogit.go
+++ b/modules/git/commit_info_nogogit.go
@@ -7,8 +7,7 @@ package git
import (
"context"
- "fmt"
- "io"
+ "maps"
"path"
"sort"
@@ -40,9 +39,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
return nil, nil, err
}
- for pth, found := range commits {
- revs[pth] = found
- }
+ maps.Copy(revs, commits)
}
} else {
sort.Strings(entryPaths)
@@ -124,48 +121,25 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
return nil, err
}
- batchStdinWriter, batchReader, cancel, err := commit.repo.CatFileBatch(ctx)
- if err != nil {
- return nil, err
- }
- defer cancel()
-
commitsMap := map[string]*Commit{}
commitsMap[commit.ID.String()] = commit
commitCommits := map[string]*Commit{}
for path, commitID := range revs {
- c, ok := commitsMap[commitID]
- if ok {
- commitCommits[path] = c
+ if len(commitID) == 0 {
continue
}
- if len(commitID) == 0 {
+ c, ok := commitsMap[commitID]
+ if ok {
+ commitCommits[path] = c
continue
}
- _, err := batchStdinWriter.Write([]byte(commitID + "\n"))
- if err != nil {
- return nil, err
- }
- _, typ, size, err := ReadBatchLine(batchReader)
+ c, err := commit.repo.GetCommit(commitID) // Ensure the commit exists in the repository
if err != nil {
return nil, err
}
- if typ != "commit" {
- if err := DiscardFull(batchReader, size+1); err != nil {
- return nil, err
- }
- return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
- }
- c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size))
- if err != nil {
- return nil, err
- }
- if _, err := batchReader.Discard(1); err != nil {
- return nil, err
- }
commitCommits[path] = c
}
diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go
index 228bbaf314..eb8f4c6322 100644
--- a/modules/git/commit_reader.go
+++ b/modules/git/commit_reader.go
@@ -6,10 +6,44 @@ package git
import (
"bufio"
"bytes"
+ "fmt"
"io"
- "strings"
)
+const (
+ commitHeaderGpgsig = "gpgsig"
+ commitHeaderGpgsigSha256 = "gpgsig-sha256"
+)
+
+func assignCommitFields(gitRepo *Repository, commit *Commit, headerKey string, headerValue []byte) error {
+ if len(headerValue) > 0 && headerValue[len(headerValue)-1] == '\n' {
+ headerValue = headerValue[:len(headerValue)-1] // remove trailing newline
+ }
+ switch headerKey {
+ case "tree":
+ objID, err := NewIDFromString(string(headerValue))
+ if err != nil {
+ return fmt.Errorf("invalid tree ID %q: %w", string(headerValue), err)
+ }
+ commit.Tree = *NewTree(gitRepo, objID)
+ case "parent":
+ objID, err := NewIDFromString(string(headerValue))
+ if err != nil {
+ return fmt.Errorf("invalid parent ID %q: %w", string(headerValue), err)
+ }
+ commit.Parents = append(commit.Parents, objID)
+ case "author":
+ commit.Author.Decode(headerValue)
+ case "committer":
+ commit.Committer.Decode(headerValue)
+ case commitHeaderGpgsig, commitHeaderGpgsigSha256:
+ // if there are duplicate "gpgsig" and "gpgsig-sha256" headers, then the signature must have already been invalid
+ // so we don't need to handle duplicate headers here
+ commit.Signature = &CommitSignature{Signature: string(headerValue)}
+ }
+ return nil
+}
+
// CommitFromReader will generate a Commit from a provided reader
// We need this to interpret commits from cat-file or cat-file --batch
//
@@ -21,90 +55,46 @@ func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader)
Committer: &Signature{},
}
- payloadSB := new(strings.Builder)
- signatureSB := new(strings.Builder)
- messageSB := new(strings.Builder)
- message := false
- pgpsig := false
-
- bufReader, ok := reader.(*bufio.Reader)
- if !ok {
- bufReader = bufio.NewReader(reader)
- }
-
-readLoop:
+ bufReader := bufio.NewReader(reader)
+ inHeader := true
+ var payloadSB, messageSB bytes.Buffer
+ var headerKey string
+ var headerValue []byte
for {
line, err := bufReader.ReadBytes('\n')
- if err != nil {
- if err == io.EOF {
- if message {
- _, _ = messageSB.Write(line)
- }
- _, _ = payloadSB.Write(line)
- break readLoop
- }
- return nil, err
+ if err != nil && err != io.EOF {
+ return nil, fmt.Errorf("unable to read commit %q: %w", objectID.String(), err)
}
- if pgpsig {
- if len(line) > 0 && line[0] == ' ' {
- _, _ = signatureSB.Write(line[1:])
- continue
- }
- pgpsig = false
+ if len(line) == 0 {
+ break
}
- if !message {
- // This is probably not correct but is copied from go-gits interpretation...
- trimmed := bytes.TrimSpace(line)
- if len(trimmed) == 0 {
- message = true
- _, _ = payloadSB.Write(line)
- continue
- }
-
- split := bytes.SplitN(trimmed, []byte{' '}, 2)
- var data []byte
- if len(split) > 1 {
- data = split[1]
+ if inHeader {
+ inHeader = !(len(line) == 1 && line[0] == '\n') // still in header if line is not just a newline
+ k, v, _ := bytes.Cut(line, []byte{' '})
+ if len(k) != 0 || !inHeader {
+ if headerKey != "" {
+ if err = assignCommitFields(gitRepo, commit, headerKey, headerValue); err != nil {
+ return nil, fmt.Errorf("unable to parse commit %q: %w", objectID.String(), err)
+ }
+ }
+ headerKey = string(k) // it also resets the headerValue to empty string if not inHeader
+ headerValue = v
+ } else {
+ headerValue = append(headerValue, v...)
}
-
- switch string(split[0]) {
- case "tree":
- commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data)))
+ if headerKey != commitHeaderGpgsig && headerKey != commitHeaderGpgsigSha256 {
_, _ = payloadSB.Write(line)
- case "parent":
- commit.Parents = append(commit.Parents, MustIDFromString(string(data)))
- _, _ = payloadSB.Write(line)
- case "author":
- commit.Author = &Signature{}
- commit.Author.Decode(data)
- _, _ = payloadSB.Write(line)
- case "committer":
- commit.Committer = &Signature{}
- commit.Committer.Decode(data)
- _, _ = payloadSB.Write(line)
- case "encoding":
- _, _ = payloadSB.Write(line)
- case "gpgsig":
- fallthrough
- case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
- _, _ = signatureSB.Write(data)
- _ = signatureSB.WriteByte('\n')
- pgpsig = true
}
} else {
_, _ = messageSB.Write(line)
_, _ = payloadSB.Write(line)
}
}
+
commit.CommitMessage = messageSB.String()
- commit.Signature = &CommitSignature{
- Signature: signatureSB.String(),
- Payload: payloadSB.String(),
- }
- if len(commit.Signature.Signature) == 0 {
- commit.Signature = nil
+ if commit.Signature != nil {
+ commit.Signature.Payload = payloadSB.String()
}
-
return commit, nil
}
diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go
index 64a0f53908..97ccecdacc 100644
--- a/modules/git/commit_sha256_test.go
+++ b/modules/git/commit_sha256_test.go
@@ -60,8 +60,7 @@ func TestGetFullCommitIDErrorSha256(t *testing.T) {
}
func TestCommitFromReaderSha256(t *testing.T) {
- commitString := `9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 commit 1114
-tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
+ commitString := `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
author Adam Majer <amajer@suse.de> 1698676906 +0100
committer Adam Majer <amajer@suse.de> 1698676906 +0100
@@ -112,8 +111,7 @@ VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
=xybZ
------END PGP SIGNATURE-----
-`, commitFromReader.Signature.Signature)
+-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
author Adam Majer <amajer@suse.de> 1698676906 +0100
diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go
index f43e0081fd..81fb91dfc6 100644
--- a/modules/git/commit_test.go
+++ b/modules/git/commit_test.go
@@ -59,8 +59,7 @@ func TestGetFullCommitIDError(t *testing.T) {
}
func TestCommitFromReader(t *testing.T) {
- commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
-tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
+ commitString := `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind <me@silverwind.io> 1563741793 +0200
committer silverwind <me@silverwind.io> 1563741793 +0200
@@ -108,8 +107,7 @@ sD53z/f0J+We4VZjY+pidvA9BGZPFVdR3wd3xGs8/oH6UWaLJAMGkLG6dDb3qDLm
mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i
1pxsB104XLWjQHTjr1JtiaBQEwFh9r2OKTcpvaLcbNtYpo7CzOs=
=FRsO
------END PGP SIGNATURE-----
-`, commitFromReader.Signature.Signature)
+-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind <me@silverwind.io> 1563741793 +0200
@@ -126,8 +124,7 @@ empty commit`, commitFromReader.Signature.Payload)
}
func TestCommitWithEncodingFromReader(t *testing.T) {
- commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
-tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
+ commitString := `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
@@ -172,8 +169,7 @@ SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
jw4YcO5u
=r3UU
------END PGP SIGNATURE-----
-`, commitFromReader.Signature.Signature)
+-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
diff --git a/modules/git/diff_test.go b/modules/git/diff_test.go
index 9a09347b30..7671fffcc1 100644
--- a/modules/git/diff_test.go
+++ b/modules/git/diff_test.go
@@ -154,7 +154,7 @@ func TestCutDiffAroundLine(t *testing.T) {
}
func BenchmarkCutDiffAroundLine(b *testing.B) {
- for n := 0; n < b.N; n++ {
+ for b.Loop() {
CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
}
}
diff --git a/modules/git/error.go b/modules/git/error.go
index 6c86d1b04d..7d131345d0 100644
--- a/modules/git/error.go
+++ b/modules/git/error.go
@@ -32,22 +32,6 @@ func (err ErrNotExist) Unwrap() error {
return util.ErrNotExist
}
-// ErrSymlinkUnresolved entry.FollowLink error
-type ErrSymlinkUnresolved struct {
- Name string
- Message string
-}
-
-func (err ErrSymlinkUnresolved) Error() string {
- return fmt.Sprintf("%s: %s", err.Name, err.Message)
-}
-
-// IsErrSymlinkUnresolved if some error is ErrSymlinkUnresolved
-func IsErrSymlinkUnresolved(err error) bool {
- _, ok := err.(ErrSymlinkUnresolved)
- return ok
-}
-
// ErrBranchNotExist represents a "BranchNotExist" kind of error.
type ErrBranchNotExist struct {
Name string
diff --git a/modules/git/foreachref/format.go b/modules/git/foreachref/format.go
index 97e8ee4724..d9573a55d6 100644
--- a/modules/git/foreachref/format.go
+++ b/modules/git/foreachref/format.go
@@ -76,7 +76,7 @@ func (f Format) Parser(r io.Reader) *Parser {
// would turn into "%0a%00".
func (f Format) hexEscaped(delim []byte) string {
escaped := ""
- for i := 0; i < len(delim); i++ {
+ for i := range delim {
escaped += "%" + hex.EncodeToString([]byte{delim[i]})
}
return escaped
diff --git a/modules/git/hook.go b/modules/git/hook.go
index a6f6b18855..548a59971d 100644
--- a/modules/git/hook.go
+++ b/modules/git/hook.go
@@ -8,6 +8,7 @@ import (
"errors"
"os"
"path/filepath"
+ "slices"
"strings"
"code.gitea.io/gitea/modules/util"
@@ -25,12 +26,7 @@ var ErrNotValidHook = errors.New("not a valid Git hook")
// IsValidHookName returns true if given name is a valid Git hook.
func IsValidHookName(name string) bool {
- for _, hn := range hookNames {
- if hn == name {
- return true
- }
- }
- return false
+ return slices.Contains(hookNames, name)
}
// Hook represents a Git hook.
diff --git a/modules/git/key.go b/modules/git/key.go
new file mode 100644
index 0000000000..2513c048b7
--- /dev/null
+++ b/modules/git/key.go
@@ -0,0 +1,15 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+// Based on https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat
+const (
+ SigningKeyFormatOpenPGP = "openpgp" // for GPG keys, the expected default of git cli
+ SigningKeyFormatSSH = "ssh"
+)
+
+type SigningKey struct {
+ KeyID string
+ Format string
+}
diff --git a/modules/git/languagestats/language_stats_nogogit.go b/modules/git/languagestats/language_stats_nogogit.go
index 34797263a6..94cf9fff8c 100644
--- a/modules/git/languagestats/language_stats_nogogit.go
+++ b/modules/git/languagestats/language_stats_nogogit.go
@@ -97,17 +97,17 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
}
isVendored := optional.None[bool]()
- isGenerated := optional.None[bool]()
isDocumentation := optional.None[bool]()
isDetectable := optional.None[bool]()
attrs, err := checker.CheckPath(f.Name())
+ attrLinguistGenerated := optional.None[bool]()
if err == nil {
if isVendored = attrs.GetVendored(); isVendored.ValueOrDefault(false) {
continue
}
- if isGenerated = attrs.GetGenerated(); isGenerated.ValueOrDefault(false) {
+ if attrLinguistGenerated = attrs.GetGenerated(); attrLinguistGenerated.ValueOrDefault(false) {
continue
}
@@ -169,7 +169,15 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
return nil, err
}
}
- if !isGenerated.Has() && enry.IsGenerated(f.Name(), content) {
+
+ // if "generated" attribute is set, use it, otherwise use enry.IsGenerated to guess
+ var isGenerated bool
+ if attrLinguistGenerated.Has() {
+ isGenerated = attrLinguistGenerated.Value()
+ } else {
+ isGenerated = enry.IsGenerated(f.Name(), content)
+ }
+ if isGenerated {
continue
}
diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go
index cf9c10d7b4..cff2556083 100644
--- a/modules/git/last_commit_cache.go
+++ b/modules/git/last_commit_cache.go
@@ -13,7 +13,7 @@ import (
)
func getCacheKey(repoPath, commitID, entryPath string) string {
- hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
+ hashBytes := sha256.Sum256(fmt.Appendf(nil, "%s:%s:%s", repoPath, commitID, entryPath))
return fmt.Sprintf("last_commit:%x", hashBytes)
}
diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go
index 3ee462f68e..dfdef38ef9 100644
--- a/modules/git/log_name_status.go
+++ b/modules/git/log_name_status.go
@@ -346,10 +346,7 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st
results := make([]string, len(paths))
remaining := len(paths)
- nextRestart := (len(paths) * 3) / 4
- if nextRestart > 70 {
- nextRestart = 70
- }
+ nextRestart := min((len(paths)*3)/4, 70)
lastEmptyParent := head.ID.String()
commitSinceLastEmptyParent := uint64(0)
commitSinceNextRestart := uint64(0)
diff --git a/modules/git/ref.go b/modules/git/ref.go
index f20a175e42..56b2db858a 100644
--- a/modules/git/ref.go
+++ b/modules/git/ref.go
@@ -109,8 +109,8 @@ func (ref RefName) IsFor() bool {
}
func (ref RefName) nameWithoutPrefix(prefix string) string {
- if strings.HasPrefix(string(ref), prefix) {
- return strings.TrimPrefix(string(ref), prefix)
+ if after, ok := strings.CutPrefix(string(ref), prefix); ok {
+ return after
}
return ""
}
diff --git a/modules/git/repo.go b/modules/git/repo.go
index 45937a8d5f..f1f6902773 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -28,6 +28,7 @@ type GPGSettings struct {
Email string
Name string
PublicKeyContent string
+ Format string
}
const prettyLogFormat = `--pretty=format:%H`
@@ -43,9 +44,9 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro
return commits, nil
}
- parts := bytes.Split(logs, []byte{'\n'})
+ parts := bytes.SplitSeq(logs, []byte{'\n'})
- for _, commitID := range parts {
+ for commitID := range parts {
commit, err := repo.GetCommit(string(commitID))
if err != nil {
return nil, err
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 72f35711f0..4066a1ca7b 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -89,7 +89,8 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
return commits[0], nil
}
-func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) {
+// commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support
+func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) {
cmd := NewCommand("log").
AddOptionFormat("--skip=%d", (page-1)*pageSize).
AddOptionFormat("--max-count=%d", pageSize).
@@ -99,6 +100,12 @@ func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not stri
if not != "" {
cmd.AddOptionValues("--not", not)
}
+ if since != "" {
+ cmd.AddOptionFormat("--since=%s", since)
+ }
+ if until != "" {
+ cmd.AddOptionFormat("--until=%s", until)
+ }
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
@@ -212,6 +219,8 @@ type CommitsByFileAndRangeOptions struct {
File string
Not string
Page int
+ Since string
+ Until string
}
// CommitsByFileAndRange return the commits according revision file and the page
@@ -231,6 +240,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
+ if opts.Since != "" {
+ gitCmd.AddOptionFormat("--since=%s", opts.Since)
+ }
+ if opts.Until != "" {
+ gitCmd.AddOptionFormat("--until=%s", opts.Until)
+ }
gitCmd.AddDashesAndList(opts.File)
err := gitCmd.Run(repo.Ctx, &RunOpts{
@@ -532,11 +547,11 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
return "", runErr
}
- parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
+ parts := bytes.SplitSeq(bytes.TrimSpace(stdout), []byte{'\n'})
// check the commits one by one until we find a commit contained by another branch
// and we think this commit is the divergence point
- for _, commitID := range parts {
+ for commitID := range parts {
branches, err := repo.getBranches(env, string(commitID), 2)
if err != nil {
return "", err
diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go
index 8f91b4dce5..0021a7bda7 100644
--- a/modules/git/repo_gpg.go
+++ b/modules/git/repo_gpg.go
@@ -6,6 +6,7 @@ package git
import (
"fmt"
+ "os"
"strings"
"code.gitea.io/gitea/modules/process"
@@ -13,6 +14,14 @@ import (
// LoadPublicKeyContent will load the key from gpg
func (gpgSettings *GPGSettings) LoadPublicKeyContent() error {
+ if gpgSettings.Format == SigningKeyFormatSSH {
+ content, err := os.ReadFile(gpgSettings.KeyID)
+ if err != nil {
+ return fmt.Errorf("unable to read SSH public key file: %s, %w", gpgSettings.KeyID, err)
+ }
+ gpgSettings.PublicKeyContent = string(content)
+ return nil
+ }
content, stderr, err := process.GetManager().Exec(
"gpg -a --export",
"gpg", "-a", "--export", gpgSettings.KeyID)
@@ -44,6 +53,9 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
signingKey, _, _ := NewCommand("config", "--get", "user.signingkey").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
gpgSettings.KeyID = strings.TrimSpace(signingKey)
+ format, _, _ := NewCommand("config", "--default", SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
+ gpgSettings.Format = strings.TrimSpace(format)
+
defaultEmail, _, _ := NewCommand("config", "--get", "user.email").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
gpgSettings.Email = strings.TrimSpace(defaultEmail)
diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go
index 443a3a20d1..4879121a41 100644
--- a/modules/git/repo_index.go
+++ b/modules/git/repo_index.go
@@ -86,7 +86,7 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
return nil, err
}
filelist := make([]string, 0, len(filenames))
- for _, line := range bytes.Split(res, []byte{'\000'}) {
+ for line := range bytes.SplitSeq(res, []byte{'\000'}) {
filelist = append(filelist, string(line))
}
diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go
index 76fe92bb34..8c6f31c38c 100644
--- a/modules/git/repo_stats.go
+++ b/modules/git/repo_stats.go
@@ -40,7 +40,9 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
since := fromTime.Format(time.RFC3339)
- stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
+ stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").
+ AddOptionFormat("--since=%s", since).
+ RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@@ -60,7 +62,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
_ = stdoutWriter.Close()
}()
- gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since)
+ gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").
+ AddOptionFormat("--since=%s", since)
if len(branch) == 0 {
gitCmd.AddArguments("--branches=*")
} else {
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
index c74618471a..c8d72eee02 100644
--- a/modules/git/repo_tag.go
+++ b/modules/git/repo_tag.go
@@ -39,8 +39,8 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
return "", err
}
- tagRefs := strings.Split(stdout, "\n")
- for _, tagRef := range tagRefs {
+ tagRefs := strings.SplitSeq(stdout, "\n")
+ for tagRef := range tagRefs {
if len(strings.TrimSpace(tagRef)) > 0 {
fields := strings.Fields(tagRef)
if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) {
@@ -62,7 +62,7 @@ func (repo *Repository) GetTagID(name string) (string, error) {
return "", err
}
// Make sure exact match is used: "v1" != "release/v1"
- for _, line := range strings.Split(stdout, "\n") {
+ for line := range strings.SplitSeq(stdout, "\n") {
fields := strings.Fields(line)
if len(fields) == 2 && fields[1] == "refs/tags/"+name {
return fields[0], nil
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
index 70e5aee023..309a73d759 100644
--- a/modules/git/repo_tree.go
+++ b/modules/git/repo_tree.go
@@ -15,7 +15,7 @@ import (
type CommitTreeOpts struct {
Parents []string
Message string
- KeyID string
+ Key *SigningKey
NoGPGSign bool
AlwaysSign bool
}
@@ -43,8 +43,13 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
_, _ = messageBytes.WriteString(opts.Message)
_, _ = messageBytes.WriteString("\n")
- if opts.KeyID != "" || opts.AlwaysSign {
- cmd.AddOptionFormat("-S%s", opts.KeyID)
+ if opts.Key != nil {
+ if opts.Key.Format != "" {
+ cmd.AddConfig("gpg.format", opts.Key.Format)
+ }
+ cmd.AddOptionFormat("-S%s", opts.Key.KeyID)
+ } else if opts.AlwaysSign {
+ cmd.AddOptionFormat("-S")
}
if opts.NoGPGSign {
diff --git a/modules/git/tree.go b/modules/git/tree.go
index f6fdff97d0..38fb45f3b1 100644
--- a/modules/git/tree.go
+++ b/modules/git/tree.go
@@ -56,7 +56,7 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error
return nil, err
}
filelist := make([]string, 0, len(filenames))
- for _, line := range bytes.Split(res, []byte{'\000'}) {
+ for line := range bytes.SplitSeq(res, []byte{'\000'}) {
filelist = append(filelist, string(line))
}
diff --git a/modules/git/tree_blob_nogogit.go b/modules/git/tree_blob_nogogit.go
index b7bcf40edd..b18d0fa05e 100644
--- a/modules/git/tree_blob_nogogit.go
+++ b/modules/git/tree_blob_nogogit.go
@@ -11,7 +11,7 @@ import (
)
// GetTreeEntryByPath get the tree entries according the sub dir
-func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
+func (t *Tree) GetTreeEntryByPath(relpath string) (_ *TreeEntry, err error) {
if len(relpath) == 0 {
return &TreeEntry{
ptree: t,
@@ -21,27 +21,25 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
}, nil
}
- // FIXME: This should probably use git cat-file --batch to be a bit more efficient
relpath = path.Clean(relpath)
parts := strings.Split(relpath, "/")
- var err error
+
tree := t
- for i, name := range parts {
- if i == len(parts)-1 {
- entries, err := tree.ListEntries()
- if err != nil {
- return nil, err
- }
- for _, v := range entries {
- if v.Name() == name {
- return v, nil
- }
- }
- } else {
- tree, err = tree.SubTree(name)
- if err != nil {
- return nil, err
- }
+ for _, name := range parts[:len(parts)-1] {
+ tree, err = tree.SubTree(name)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ name := parts[len(parts)-1]
+ entries, err := tree.ListEntries()
+ if err != nil {
+ return nil, err
+ }
+ for _, v := range entries {
+ if v.Name() == name {
+ return v, nil
}
}
return nil, ErrNotExist{"", relpath}
diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go
index a2e1579290..5099d8ee79 100644
--- a/modules/git/tree_entry.go
+++ b/modules/git/tree_entry.go
@@ -5,7 +5,7 @@
package git
import (
- "io"
+ "path"
"sort"
"strings"
@@ -24,77 +24,57 @@ func (te *TreeEntry) Type() string {
}
}
-// FollowLink returns the entry pointed to by a symlink
-func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
+type EntryFollowResult struct {
+ SymlinkContent string
+ TargetFullPath string
+ TargetEntry *TreeEntry
+}
+
+func EntryFollowLink(commit *Commit, fullPath string, te *TreeEntry) (*EntryFollowResult, error) {
if !te.IsLink() {
- return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"}
+ return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q is not a symlink", fullPath)
}
- // read the link
- r, err := te.Blob().DataAsync()
- if err != nil {
- return nil, err
+ // git's filename max length is 4096, hopefully a link won't be longer than multiple of that
+ const maxSymlinkSize = 20 * 4096
+ if te.Blob().Size() > maxSymlinkSize {
+ return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q content exceeds symlink limit", fullPath)
}
- closed := false
- defer func() {
- if !closed {
- _ = r.Close()
- }
- }()
- buf := make([]byte, te.Size())
- _, err = io.ReadFull(r, buf)
+
+ link, err := te.Blob().GetBlobContent(maxSymlinkSize)
if err != nil {
return nil, err
}
- _ = r.Close()
- closed = true
-
- lnk := string(buf)
- t := te.ptree
-
- // traverse up directories
- for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
- t = t.ptree
+ if strings.HasPrefix(link, "/") {
+ // It's said that absolute path will be stored as is in Git
+ return &EntryFollowResult{SymlinkContent: link}, util.ErrorWrap(util.ErrUnprocessableContent, "%q is an absolute symlink", fullPath)
}
- if t == nil {
- return nil, ErrSymlinkUnresolved{te.Name(), "points outside of repo"}
- }
-
- target, err := t.GetTreeEntryByPath(lnk)
+ targetFullPath := path.Join(path.Dir(fullPath), link)
+ targetEntry, err := commit.GetTreeEntryByPath(targetFullPath)
if err != nil {
- if IsErrNotExist(err) {
- return nil, ErrSymlinkUnresolved{te.Name(), "broken link"}
- }
- return nil, err
+ return &EntryFollowResult{SymlinkContent: link}, err
}
- return target, nil
+ return &EntryFollowResult{SymlinkContent: link, TargetFullPath: targetFullPath, TargetEntry: targetEntry}, nil
}
-// FollowLinks returns the entry ultimately pointed to by a symlink
-func (te *TreeEntry) FollowLinks(optLimit ...int) (*TreeEntry, error) {
- if !te.IsLink() {
- return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"}
- }
+func EntryFollowLinks(commit *Commit, firstFullPath string, firstTreeEntry *TreeEntry, optLimit ...int) (res *EntryFollowResult, err error) {
limit := util.OptionalArg(optLimit, 10)
- entry := te
- for i := 0; i < limit; i++ {
- if !entry.IsLink() {
- break
- }
- next, err := entry.FollowLink()
+ treeEntry, fullPath := firstTreeEntry, firstFullPath
+ for range limit {
+ res, err = EntryFollowLink(commit, fullPath, treeEntry)
if err != nil {
- return nil, err
+ return res, err
}
- if next.ID == entry.ID {
- return nil, ErrSymlinkUnresolved{entry.Name(), "recursive link"}
+ treeEntry, fullPath = res.TargetEntry, res.TargetFullPath
+ if !treeEntry.IsLink() {
+ break
}
- entry = next
}
- if entry.IsLink() {
- return nil, ErrSymlinkUnresolved{te.Name(), "too many levels of symbolic links"}
+ if treeEntry.IsLink() {
+ return res, util.ErrorWrap(util.ErrUnprocessableContent, "%q has too many links", firstFullPath)
}
- return entry, nil
+ return res, nil
}
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
diff --git a/modules/git/tree_entry_common_test.go b/modules/git/tree_entry_common_test.go
new file mode 100644
index 0000000000..8b63bbb993
--- /dev/null
+++ b/modules/git/tree_entry_common_test.go
@@ -0,0 +1,76 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestFollowLink(t *testing.T) {
+ r, err := openRepositoryWithDefaultContext("tests/repos/repo1_bare")
+ require.NoError(t, err)
+ defer r.Close()
+
+ commit, err := r.GetCommit("37991dec2c8e592043f47155ce4808d4580f9123")
+ require.NoError(t, err)
+
+ // get the symlink
+ {
+ lnkFullPath := "foo/bar/link_to_hello"
+ lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello")
+ require.NoError(t, err)
+ assert.True(t, lnk.IsLink())
+
+ // should be able to dereference to target
+ res, err := EntryFollowLink(commit, lnkFullPath, lnk)
+ require.NoError(t, err)
+ assert.Equal(t, "hello", res.TargetEntry.Name())
+ assert.Equal(t, "foo/nar/hello", res.TargetFullPath)
+ assert.False(t, res.TargetEntry.IsLink())
+ assert.Equal(t, "b14df6442ea5a1b382985a6549b85d435376c351", res.TargetEntry.ID.String())
+ }
+
+ {
+ // should error when called on a normal file
+ entry, err := commit.Tree.GetTreeEntryByPath("file1.txt")
+ require.NoError(t, err)
+ res, err := EntryFollowLink(commit, "file1.txt", entry)
+ assert.ErrorIs(t, err, util.ErrUnprocessableContent)
+ assert.Nil(t, res)
+ }
+
+ {
+ // should error for broken links
+ entry, err := commit.Tree.GetTreeEntryByPath("foo/broken_link")
+ require.NoError(t, err)
+ assert.True(t, entry.IsLink())
+ res, err := EntryFollowLink(commit, "foo/broken_link", entry)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ assert.Equal(t, "nar/broken_link", res.SymlinkContent)
+ }
+
+ {
+ // should error for external links
+ entry, err := commit.Tree.GetTreeEntryByPath("foo/outside_repo")
+ require.NoError(t, err)
+ assert.True(t, entry.IsLink())
+ res, err := EntryFollowLink(commit, "foo/outside_repo", entry)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ assert.Equal(t, "../../outside_repo", res.SymlinkContent)
+ }
+
+ {
+ // testing fix for short link bug
+ entry, err := commit.Tree.GetTreeEntryByPath("foo/link_short")
+ require.NoError(t, err)
+ res, err := EntryFollowLink(commit, "foo/link_short", entry)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ assert.Equal(t, "a", res.SymlinkContent)
+ }
+}
diff --git a/modules/git/tree_entry_gogit.go b/modules/git/tree_entry_gogit.go
index eb9b012681..e6845f1c77 100644
--- a/modules/git/tree_entry_gogit.go
+++ b/modules/git/tree_entry_gogit.go
@@ -19,16 +19,12 @@ type TreeEntry struct {
gogitTreeEntry *object.TreeEntry
ptree *Tree
- size int64
- sized bool
- fullName string
+ size int64
+ sized bool
}
// Name returns the name of the entry
func (te *TreeEntry) Name() string {
- if te.fullName != "" {
- return te.fullName
- }
return te.gogitTreeEntry.Name
}
@@ -55,7 +51,7 @@ func (te *TreeEntry) Size() int64 {
return te.size
}
-// IsSubModule if the entry is a sub module
+// IsSubModule if the entry is a submodule
func (te *TreeEntry) IsSubModule() bool {
return te.gogitTreeEntry.Mode == filemode.Submodule
}
diff --git a/modules/git/tree_entry_mode.go b/modules/git/tree_entry_mode.go
index d815a8bc2e..f36c07bc2a 100644
--- a/modules/git/tree_entry_mode.go
+++ b/modules/git/tree_entry_mode.go
@@ -15,7 +15,7 @@ type EntryMode int
// one of these.
const (
// EntryModeNoEntry is possible if the file was added or removed in a commit. In the case of
- // added the base commit will not have the file in its tree so a mode of 0o000000 is used.
+ // when adding the base commit doesn't have the file in its tree, a mode of 0o000000 is used.
EntryModeNoEntry EntryMode = 0o000000
EntryModeBlob EntryMode = 0o100644
@@ -30,7 +30,7 @@ func (e EntryMode) String() string {
return strconv.FormatInt(int64(e), 8)
}
-// IsSubModule if the entry is a sub module
+// IsSubModule if the entry is a submodule
func (e EntryMode) IsSubModule() bool {
return e == EntryModeCommit
}
diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go
index 0c0e1835f1..8fad96cdf8 100644
--- a/modules/git/tree_entry_nogogit.go
+++ b/modules/git/tree_entry_nogogit.go
@@ -18,7 +18,7 @@ type TreeEntry struct {
sized bool
}
-// Name returns the name of the entry
+// Name returns the name of the entry (base name)
func (te *TreeEntry) Name() string {
return te.name
}
@@ -57,7 +57,7 @@ func (te *TreeEntry) Size() int64 {
return te.size
}
-// IsSubModule if the entry is a sub module
+// IsSubModule if the entry is a submodule
func (te *TreeEntry) IsSubModule() bool {
return te.entryMode.IsSubModule()
}
diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go
index 30eee13669..9ca82675e0 100644
--- a/modules/git/tree_entry_test.go
+++ b/modules/git/tree_entry_test.go
@@ -53,50 +53,3 @@ func TestEntriesCustomSort(t *testing.T) {
assert.Equal(t, "bcd", entries[6].Name())
assert.Equal(t, "abc", entries[7].Name())
}
-
-func TestFollowLink(t *testing.T) {
- r, err := openRepositoryWithDefaultContext("tests/repos/repo1_bare")
- assert.NoError(t, err)
- defer r.Close()
-
- commit, err := r.GetCommit("37991dec2c8e592043f47155ce4808d4580f9123")
- assert.NoError(t, err)
-
- // get the symlink
- lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello")
- assert.NoError(t, err)
- assert.True(t, lnk.IsLink())
-
- // should be able to dereference to target
- target, err := lnk.FollowLink()
- assert.NoError(t, err)
- assert.Equal(t, "hello", target.Name())
- assert.False(t, target.IsLink())
- assert.Equal(t, "b14df6442ea5a1b382985a6549b85d435376c351", target.ID.String())
-
- // should error when called on normal file
- target, err = commit.Tree.GetTreeEntryByPath("file1.txt")
- assert.NoError(t, err)
- _, err = target.FollowLink()
- assert.EqualError(t, err, "file1.txt: not a symlink")
-
- // should error for broken links
- target, err = commit.Tree.GetTreeEntryByPath("foo/broken_link")
- assert.NoError(t, err)
- assert.True(t, target.IsLink())
- _, err = target.FollowLink()
- assert.EqualError(t, err, "broken_link: broken link")
-
- // should error for external links
- target, err = commit.Tree.GetTreeEntryByPath("foo/outside_repo")
- assert.NoError(t, err)
- assert.True(t, target.IsLink())
- _, err = target.FollowLink()
- assert.EqualError(t, err, "outside_repo: points outside of repo")
-
- // testing fix for short link bug
- target, err = commit.Tree.GetTreeEntryByPath("foo/link_short")
- assert.NoError(t, err)
- _, err = target.FollowLink()
- assert.EqualError(t, err, "link_short: broken link")
-}
diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go
index 421b0ecb0f..272b018ffd 100644
--- a/modules/git/tree_gogit.go
+++ b/modules/git/tree_gogit.go
@@ -69,7 +69,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
seen := map[plumbing.Hash]bool{}
walker := object.NewTreeWalker(t.gogitTree, true, seen)
for {
- fullName, entry, err := walker.Next()
+ _, entry, err := walker.Next()
if err == io.EOF {
break
}
@@ -84,7 +84,6 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
ID: ParseGogitHash(entry.Hash),
gogitTreeEntry: &entry,
ptree: t,
- fullName: fullName,
}
entries = append(entries, convertedEntry)
}
diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go
index 61e5482538..cae11c4b1b 100644
--- a/modules/git/tree_test.go
+++ b/modules/git/tree_test.go
@@ -19,7 +19,7 @@ func TestSubTree_Issue29101(t *testing.T) {
assert.NoError(t, err)
// old code could produce a different error if called multiple times
- for i := 0; i < 10; i++ {
+ for range 10 {
_, err = commit.SubTree("file1.txt")
assert.Error(t, err)
assert.True(t, IsErrNotExist(err))
diff --git a/modules/globallock/globallock_test.go b/modules/globallock/globallock_test.go
index 0143fc6833..8d55d9f699 100644
--- a/modules/globallock/globallock_test.go
+++ b/modules/globallock/globallock_test.go
@@ -70,7 +70,7 @@ func testLockAndDo(t *testing.T) {
count := 0
wg := sync.WaitGroup{}
wg.Add(concurrency)
- for i := 0; i < concurrency; i++ {
+ for range concurrency {
go func() {
defer wg.Done()
err := LockAndDo(ctx, "test", func(ctx context.Context) error {
diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go
index d776e0e9f9..457768d6ca 100644
--- a/modules/graceful/manager_windows.go
+++ b/modules/graceful/manager_windows.go
@@ -41,8 +41,7 @@ func (g *Manager) start() {
// Make SVC process
run := svc.Run
- //lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile
- isAnInteractiveSession, err := svc.IsAnInteractiveSession() //nolint:staticcheck
+ isAnInteractiveSession, err := svc.IsAnInteractiveSession() //nolint:staticcheck // must use IsAnInteractiveSession because IsWindowsService has a different permissions profile
if err != nil {
log.Error("Unable to ascertain if running as an Windows Service: %v", err)
return
diff --git a/modules/hostmatcher/hostmatcher.go b/modules/hostmatcher/hostmatcher.go
index 1069310316..15c6371422 100644
--- a/modules/hostmatcher/hostmatcher.go
+++ b/modules/hostmatcher/hostmatcher.go
@@ -6,6 +6,7 @@ package hostmatcher
import (
"net"
"path/filepath"
+ "slices"
"strings"
)
@@ -38,7 +39,7 @@ func isBuiltin(s string) bool {
// ParseHostMatchList parses the host list HostMatchList
func ParseHostMatchList(settingKeyHint, hostList string) *HostMatchList {
hl := &HostMatchList{SettingKeyHint: settingKeyHint, SettingValue: hostList}
- for _, s := range strings.Split(hostList, ",") {
+ for s := range strings.SplitSeq(hostList, ",") {
s = strings.ToLower(strings.TrimSpace(s))
if s == "" {
continue
@@ -61,7 +62,7 @@ func ParseSimpleMatchList(settingKeyHint, matchList string) *HostMatchList {
SettingKeyHint: settingKeyHint,
SettingValue: matchList,
}
- for _, s := range strings.Split(matchList, ",") {
+ for s := range strings.SplitSeq(matchList, ",") {
s = strings.ToLower(strings.TrimSpace(s))
if s == "" {
continue
@@ -98,10 +99,8 @@ func (hl *HostMatchList) checkPattern(host string) bool {
}
func (hl *HostMatchList) checkIP(ip net.IP) bool {
- for _, pattern := range hl.patterns {
- if pattern == "*" {
- return true
- }
+ if slices.Contains(hl.patterns, "*") {
+ return true
}
for _, builtin := range hl.builtins {
switch builtin {
diff --git a/modules/htmlutil/html.go b/modules/htmlutil/html.go
index 0ab0e71689..efbc174b2e 100644
--- a/modules/htmlutil/html.go
+++ b/modules/htmlutil/html.go
@@ -7,6 +7,7 @@ import (
"fmt"
"html/template"
"slices"
+ "strings"
)
// ParseSizeAndClass get size and class from string with default values
@@ -31,6 +32,9 @@ func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int
}
func HTMLFormat(s template.HTML, rawArgs ...any) template.HTML {
+ if !strings.Contains(string(s), "%") || len(rawArgs) == 0 {
+ panic("HTMLFormat requires one or more arguments")
+ }
args := slices.Clone(rawArgs)
for i, v := range args {
switch v := v.(type) {
@@ -38,6 +42,8 @@ func HTMLFormat(s template.HTML, rawArgs ...any) template.HTML {
// for most basic types (including template.HTML which is safe), just do nothing and use it
case string:
args[i] = template.HTMLEscapeString(v)
+ case template.URL:
+ args[i] = template.HTMLEscapeString(string(v))
case fmt.Stringer:
args[i] = template.HTMLEscapeString(v.String())
default:
diff --git a/modules/htmlutil/html_test.go b/modules/htmlutil/html_test.go
index 5ff05d75b3..22258ce59d 100644
--- a/modules/htmlutil/html_test.go
+++ b/modules/htmlutil/html_test.go
@@ -10,6 +10,15 @@ import (
"github.com/stretchr/testify/assert"
)
+type testStringer struct{}
+
+func (t testStringer) String() string {
+ return "&StringMethod"
+}
+
func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("<a>&lt; < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1))
+ assert.Equal(t, template.HTML("%!s(<nil>)"), HTMLFormat("%s", nil))
+ assert.Equal(t, template.HTML("&lt;&gt;"), HTMLFormat("%s", template.URL("<>")))
+ assert.Equal(t, template.HTML("&amp;StringMethod &amp;StringMethod"), HTMLFormat("%s %s", testStringer{}, &testStringer{}))
}
diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go
index 045b00d944..dd3efab7a5 100644
--- a/modules/httpcache/httpcache.go
+++ b/modules/httpcache/httpcache.go
@@ -79,7 +79,7 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin
func checkIfNoneMatchIsValid(req *http.Request, etag string) bool {
ifNoneMatch := req.Header.Get("If-None-Match")
if len(ifNoneMatch) > 0 {
- for _, item := range strings.Split(ifNoneMatch, ",") {
+ for item := range strings.SplitSeq(ifNoneMatch, ",") {
item = strings.TrimPrefix(strings.TrimSpace(item), "W/") // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#directives
if item == etag {
return true
diff --git a/modules/indexer/code/bleve/token/path/path.go b/modules/indexer/code/bleve/token/path/path.go
index ae24e84974..6dfc12f146 100644
--- a/modules/indexer/code/bleve/token/path/path.go
+++ b/modules/indexer/code/bleve/token/path/path.go
@@ -51,7 +51,7 @@ func generatePathTokens(input analysis.TokenStream, reversed bool) analysis.Toke
slices.Reverse(input)
}
- for i := 0; i < len(input); i++ {
+ for i := range input {
var sb strings.Builder
sb.Write(input[0].Term)
diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go
index 0089dd259f..41bc74e6ec 100644
--- a/modules/indexer/code/git.go
+++ b/modules/indexer/code/git.go
@@ -129,8 +129,8 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
changes.Updates = append(changes.Updates, updates...)
return nil
}
- lines := strings.Split(stdout, "\n")
- for _, line := range lines {
+ lines := strings.SplitSeq(stdout, "\n")
+ for line := range lines {
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go
index e37aff8e59..a7a5d7d2e3 100644
--- a/modules/indexer/code/search.go
+++ b/modules/indexer/code/search.go
@@ -77,7 +77,7 @@ func HighlightSearchResultCode(filename, language string, lineNums []int, code s
// The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n`
lines := make([]*ResultLine, min(len(highlightedLines), len(lineNums)))
- for i := 0; i < len(lines); i++ {
+ for i := range lines {
lines[i] = &ResultLine{
Num: lineNums[i],
FormattedContent: template.HTML(highlightedLines[i]),
diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go
index 9e63ad1ad8..8f25c84b76 100644
--- a/modules/indexer/issues/indexer.go
+++ b/modules/indexer/issues/indexer.go
@@ -217,7 +217,7 @@ func PopulateIssueIndexer(ctx context.Context) error {
return fmt.Errorf("shutdown before completion: %w", ctx.Err())
default:
}
- repos, _, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
+ repos, _, err := repo_model.SearchRepositoryByName(ctx, repo_model.SearchRepoOptions{
ListOptions: db_model.ListOptions{Page: page, PageSize: repo_model.RepositoryListDefaultPageSize},
OrderBy: db_model.SearchOrderByID,
Private: true,
diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go
index a42ec9a2bc..7aebbbcd58 100644
--- a/modules/indexer/issues/internal/tests/tests.go
+++ b/modules/indexer/issues/internal/tests/tests.go
@@ -8,7 +8,6 @@
package tests
import (
- "context"
"fmt"
"slices"
"testing"
@@ -40,7 +39,7 @@ func TestIndexer(t *testing.T, indexer internal.Indexer) {
data[v.ID] = v
}
require.NoError(t, indexer.Index(t.Context(), d...))
- require.NoError(t, waitData(indexer, int64(len(data))))
+ waitData(t, indexer, int64(len(data)))
}
defer func() {
@@ -54,13 +53,13 @@ func TestIndexer(t *testing.T, indexer internal.Indexer) {
for _, v := range c.ExtraData {
data[v.ID] = v
}
- require.NoError(t, waitData(indexer, int64(len(data))))
+ waitData(t, indexer, int64(len(data)))
defer func() {
for _, v := range c.ExtraData {
require.NoError(t, indexer.Delete(t.Context(), v.ID))
delete(data, v.ID)
}
- require.NoError(t, waitData(indexer, int64(len(data))))
+ waitData(t, indexer, int64(len(data)))
}()
}
@@ -751,22 +750,10 @@ func countIndexerData(data map[int64]*internal.IndexerData, f func(v *internal.I
// waitData waits for the indexer to index all data.
// Some engines like Elasticsearch index data asynchronously, so we need to wait for a while.
-func waitData(indexer internal.Indexer, total int64) error {
- var actual int64
- for i := 0; i < 100; i++ {
- result, err := indexer.Search(context.Background(), &internal.SearchOptions{
- Paginator: &db.ListOptions{
- PageSize: 0,
- },
- })
- if err != nil {
- return err
- }
- actual = result.Total
- if actual == total {
- return nil
- }
- time.Sleep(100 * time.Millisecond)
- }
- return fmt.Errorf("waitData: expected %d, actual %d", total, actual)
+func waitData(t *testing.T, indexer internal.Indexer, total int64) {
+ assert.Eventually(t, func() bool {
+ result, err := indexer.Search(t.Context(), &internal.SearchOptions{Paginator: &db.ListOptions{}})
+ require.NoError(t, err)
+ return result.Total == total
+ }, 10*time.Second, 100*time.Millisecond, "expected total=%d", total)
}
diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go
index 84ae90e4ed..192aaf8e01 100644
--- a/modules/issue/template/template.go
+++ b/modules/issue/template/template.go
@@ -8,6 +8,7 @@ import (
"fmt"
"net/url"
"regexp"
+ "slices"
"strconv"
"strings"
@@ -447,12 +448,7 @@ func (o *valuedOption) IsChecked() bool {
case api.IssueFormFieldTypeDropdown:
checks := strings.Split(o.field.Get("form-field-"+o.field.ID), ",")
idx := strconv.Itoa(o.index)
- for _, v := range checks {
- if v == idx {
- return true
- }
- }
- return false
+ return slices.Contains(checks, idx)
case api.IssueFormFieldTypeCheckboxes:
return o.field.Get(fmt.Sprintf("form-field-%s-%d", o.field.ID, o.index)) == "on"
}
diff --git a/modules/json/json.go b/modules/json/json.go
index acd4118573..444dc8526a 100644
--- a/modules/json/json.go
+++ b/modules/json/json.go
@@ -3,11 +3,10 @@
package json
-// Allow "encoding/json" import.
import (
"bytes"
"encoding/binary"
- "encoding/json" //nolint:depguard
+ "encoding/json" //nolint:depguard // this package wraps it
"io"
jsoniter "github.com/json-iterator/go"
diff --git a/modules/label/label.go b/modules/label/label.go
index ce028aa9f3..3e68c4d26e 100644
--- a/modules/label/label.go
+++ b/modules/label/label.go
@@ -7,10 +7,10 @@ import (
"fmt"
"regexp"
"strings"
-)
+ "sync"
-// colorPattern is a regexp which can validate label color
-var colorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
+ "code.gitea.io/gitea/modules/util"
+)
// Label represents label information loaded from template
type Label struct {
@@ -21,6 +21,10 @@ type Label struct {
ExclusiveOrder int `yaml:"exclusive_order,omitempty"`
}
+var colorPattern = sync.OnceValue(func() *regexp.Regexp {
+ return regexp.MustCompile(`^#([\da-fA-F]{3}|[\da-fA-F]{6})$`)
+})
+
// NormalizeColor normalizes a color string to a 6-character hex code
func NormalizeColor(color string) (string, error) {
// normalize case
@@ -31,8 +35,8 @@ func NormalizeColor(color string) (string, error) {
color = "#" + color
}
- if !colorPattern.MatchString(color) {
- return "", fmt.Errorf("bad color code: %s", color)
+ if !colorPattern().MatchString(color) {
+ return "", util.NewInvalidArgumentErrorf("invalid color: %s", color)
}
// convert 3-character shorthand into 6-character version
diff --git a/modules/label/parser.go b/modules/label/parser.go
index 511bac823f..2a10152062 100644
--- a/modules/label/parser.go
+++ b/modules/label/parser.go
@@ -72,7 +72,7 @@ func parseYamlFormat(fileName string, data []byte) ([]*Label, error) {
func parseLegacyFormat(fileName string, data []byte) ([]*Label, error) {
lines := strings.Split(string(data), "\n")
list := make([]*Label, 0, len(lines))
- for i := 0; i < len(lines); i++ {
+ for i := range lines {
line := strings.TrimSpace(lines[i])
if len(line) == 0 {
continue
@@ -108,7 +108,7 @@ func LoadTemplateDescription(fileName string) (string, error) {
return "", err
}
- for i := 0; i < len(list); i++ {
+ for i := range list {
if i > 0 {
buf.WriteString(", ")
}
diff --git a/modules/lfs/pointer.go b/modules/lfs/pointer.go
index ebde20f826..9c95613057 100644
--- a/modules/lfs/pointer.go
+++ b/modules/lfs/pointer.go
@@ -15,15 +15,13 @@ import (
"strings"
)
+// spec: https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
const (
- blobSizeCutoff = 1024
+ MetaFileMaxSize = 1024 // spec says the maximum size of a pointer file must be smaller than 1024
- // MetaFileIdentifier is the string appearing at the first line of LFS pointer files.
- // https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
- MetaFileIdentifier = "version https://git-lfs.github.com/spec/v1"
+ MetaFileIdentifier = "version https://git-lfs.github.com/spec/v1" // the first line of a pointer file
- // MetaFileOidPrefix appears in LFS pointer files on a line before the sha256 hash.
- MetaFileOidPrefix = "oid sha256:"
+ MetaFileOidPrefix = "oid sha256:" // spec says the only supported hash is sha256 at the moment
)
var (
@@ -39,7 +37,7 @@ var (
// ReadPointer tries to read LFS pointer data from the reader
func ReadPointer(reader io.Reader) (Pointer, error) {
- buf := make([]byte, blobSizeCutoff)
+ buf := make([]byte, MetaFileMaxSize)
n, err := io.ReadFull(reader, buf)
if err != nil && err != io.ErrUnexpectedEOF {
return Pointer{}, err
@@ -65,6 +63,7 @@ func ReadPointerFromBuffer(buf []byte) (Pointer, error) {
return p, ErrInvalidStructure
}
+ // spec says "key/value pairs MUST be sorted alphabetically in ascending order (version is exception and must be the first)"
oid := strings.TrimPrefix(splitLines[1], MetaFileOidPrefix)
if len(oid) != 64 || !oidPattern.MatchString(oid) {
return p, ErrInvalidOIDFormat
diff --git a/modules/lfs/pointer_scanner_gogit.go b/modules/lfs/pointer_scanner_gogit.go
index f4302c23bc..e153b8e24e 100644
--- a/modules/lfs/pointer_scanner_gogit.go
+++ b/modules/lfs/pointer_scanner_gogit.go
@@ -31,7 +31,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c
default:
}
- if blob.Size > blobSizeCutoff {
+ if blob.Size > MetaFileMaxSize {
return nil
}
diff --git a/modules/lfstransfer/backend/util.go b/modules/lfstransfer/backend/util.go
index 98ce0b1e62..afe02f799c 100644
--- a/modules/lfstransfer/backend/util.go
+++ b/modules/lfstransfer/backend/util.go
@@ -132,6 +132,7 @@ func newInternalRequestLFS(ctx context.Context, internalURL, method string, head
return nil
}
req := private.NewInternalRequest(ctx, internalURL, method)
+ req.SetReadWriteTimeout(0)
for k, v := range headers {
req.Header(k, v)
}
diff --git a/modules/log/event_format.go b/modules/log/event_format.go
index c23b3b411b..4cf471d223 100644
--- a/modules/log/event_format.go
+++ b/modules/log/event_format.go
@@ -212,7 +212,7 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms
}
}
if hasColorValue {
- msg = []byte(fmt.Sprintf(msgFormat, msgArgs...))
+ msg = fmt.Appendf(nil, msgFormat, msgArgs...)
}
}
// try to re-use the pre-formatted simple text message
@@ -243,8 +243,8 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms
buf = append(buf, msg...)
if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level {
- lines := bytes.Split([]byte(event.Stacktrace), []byte("\n"))
- for _, line := range lines {
+ lines := bytes.SplitSeq([]byte(event.Stacktrace), []byte("\n"))
+ for line := range lines {
buf = append(buf, "\n\t"...)
buf = append(buf, line...)
}
diff --git a/modules/log/flags.go b/modules/log/flags.go
index 8064c91745..f409261150 100644
--- a/modules/log/flags.go
+++ b/modules/log/flags.go
@@ -123,7 +123,7 @@ func FlagsFromString(from string, def ...uint32) Flags {
return Flags{defined: true, flags: def[0]}
}
flags := uint32(0)
- for _, flag := range strings.Split(strings.ToLower(from), ",") {
+ for flag := range strings.SplitSeq(strings.ToLower(from), ",") {
flags |= flagFromString[strings.TrimSpace(flag)]
}
return Flags{defined: true, flags: flags}
diff --git a/modules/log/level_test.go b/modules/log/level_test.go
index cd18a807d8..0e59af6cb7 100644
--- a/modules/log/level_test.go
+++ b/modules/log/level_test.go
@@ -32,11 +32,11 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, INFO, testLevel.Level)
- err = json.Unmarshal([]byte(fmt.Sprintf(`{"level":%d}`, 2)), &testLevel)
+ err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 2), &testLevel)
assert.NoError(t, err)
assert.Equal(t, INFO, testLevel.Level)
- err = json.Unmarshal([]byte(fmt.Sprintf(`{"level":%d}`, 10012)), &testLevel)
+ err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 10012), &testLevel)
assert.NoError(t, err)
assert.Equal(t, INFO, testLevel.Level)
@@ -51,5 +51,5 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) {
}
func makeTestLevelBytes(level string) []byte {
- return []byte(fmt.Sprintf(`{"level":"%s"}`, level))
+ return fmt.Appendf(nil, `{"level":"%s"}`, level)
}
diff --git a/modules/log/logger.go b/modules/log/logger.go
index 3fc524d55e..8b89e0eb5a 100644
--- a/modules/log/logger.go
+++ b/modules/log/logger.go
@@ -45,6 +45,6 @@ type Logger interface {
LevelLogger
}
-type LogStringer interface { //nolint:revive
+type LogStringer interface { //nolint:revive // export stutter
LogString() string
}
diff --git a/modules/markup/common/footnote.go b/modules/markup/common/footnote.go
index 9a4f18ed7f..1ece436c66 100644
--- a/modules/markup/common/footnote.go
+++ b/modules/markup/common/footnote.go
@@ -197,7 +197,7 @@ func (b *footnoteBlockParser) Open(parent ast.Node, reader text.Reader, pc parse
return nil, parser.NoChildren
}
open := pos + 1
- closure := util.FindClosure(line[pos+1:], '[', ']', false, false) //nolint
+ closure := util.FindClosure(line[pos+1:], '[', ']', false, false) //nolint:staticcheck // deprecated function
closes := pos + 1 + closure
next := closes + 1
if closure > -1 {
@@ -287,7 +287,7 @@ func (s *footnoteParser) Parse(parent ast.Node, block text.Reader, pc parser.Con
return nil
}
open := pos
- closure := util.FindClosure(line[pos:], '[', ']', false, false) //nolint
+ closure := util.FindClosure(line[pos:], '[', ']', false, false) //nolint:staticcheck // deprecated function
if closure < 0 {
return nil
}
@@ -409,9 +409,9 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
_, _ = w.Write(n.Name)
_, _ = w.WriteString(`"><a href="#fn:`)
_, _ = w.Write(n.Name)
- _, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
+ _, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`) // FIXME: here and below, need to keep the classes
_, _ = w.WriteString(is)
- _, _ = w.WriteString(`</a></sup>`)
+ _, _ = w.WriteString(` </a></sup>`) // the style doesn't work at the moment, so add a space to separate the names
}
return ast.WalkContinue, nil
}
diff --git a/modules/markup/console/console.go b/modules/markup/console/console.go
index 06f3acfa68..492579b0a5 100644
--- a/modules/markup/console/console.go
+++ b/modules/markup/console/console.go
@@ -6,13 +6,14 @@ package console
import (
"bytes"
"io"
- "path"
+ "unicode/utf8"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/typesniffer"
+ "code.gitea.io/gitea/modules/util"
trend "github.com/buildkite/terminal-to-html/v3"
- "github.com/go-enry/go-enry/v2"
)
func init() {
@@ -22,6 +23,8 @@ func init() {
// Renderer implements markup.Renderer
type Renderer struct{}
+var _ markup.RendererContentDetector = (*Renderer)(nil)
+
// Name implements markup.Renderer
func (Renderer) Name() string {
return "console"
@@ -40,15 +43,36 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
}
// CanRender implements markup.RendererContentDetector
-func (Renderer) CanRender(filename string, input io.Reader) bool {
- buf, err := io.ReadAll(input)
- if err != nil {
+func (Renderer) CanRender(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) bool {
+ if !sniffedType.IsTextPlain() {
return false
}
- if enry.GetLanguage(path.Base(filename), buf) != enry.OtherLanguage {
+
+ s := util.UnsafeBytesToString(prefetchBuf)
+ rs := []rune(s)
+ cnt := 0
+ firstErrPos := -1
+ isCtrlSep := func(p int) bool {
+ return p < len(rs) && (rs[p] == ';' || rs[p] == 'm')
+ }
+ for i, c := range rs {
+ if c == 0 {
+ return false
+ }
+ if c == '\x1b' {
+ match := i+1 < len(rs) && rs[i+1] == '['
+ if match && (isCtrlSep(i+2) || isCtrlSep(i+3) || isCtrlSep(i+4) || isCtrlSep(i+5)) {
+ cnt++
+ }
+ }
+ if c == utf8.RuneError && firstErrPos == -1 {
+ firstErrPos = i
+ }
+ }
+ if firstErrPos != -1 && firstErrPos != len(rs)-1 {
return false
}
- return bytes.ContainsRune(buf, '\x1b')
+ return cnt >= 2 // only render it as console output if there are at least two escape sequences
}
// Render renders terminal colors to HTML with all specific handling stuff.
diff --git a/modules/markup/console/console_test.go b/modules/markup/console/console_test.go
index 539f965ea1..d1192bebc2 100644
--- a/modules/markup/console/console_test.go
+++ b/modules/markup/console/console_test.go
@@ -8,23 +8,39 @@ import (
"testing"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/typesniffer"
"github.com/stretchr/testify/assert"
)
func TestRenderConsole(t *testing.T) {
- var render Renderer
- kases := map[string]string{
- "\x1b[37m\x1b[40mnpm\x1b[0m \x1b[0m\x1b[32minfo\x1b[0m \x1b[0m\x1b[35mit worked if it ends with\x1b[0m ok": "<span class=\"term-fg37 term-bg40\">npm</span> <span class=\"term-fg32\">info</span> <span class=\"term-fg35\">it worked if it ends with</span> ok",
+ cases := []struct {
+ input string
+ expected string
+ }{
+ {"\x1b[37m\x1b[40mnpm\x1b[0m \x1b[0m\x1b[32minfo\x1b[0m \x1b[0m\x1b[35mit worked if it ends with\x1b[0m ok", `<span class="term-fg37 term-bg40">npm</span> <span class="term-fg32">info</span> <span class="term-fg35">it worked if it ends with</span> ok`},
+ {"\x1b[1;2m \x1b[123m 啊", `<span class="term-fg2"> 啊</span>`},
+ {"\x1b[1;2m \x1b[123m \xef", `<span class="term-fg2"> �</span>`},
+ {"\x1b[1;2m \x1b[123m \xef \xef", ``},
+ {"\x1b[12", ``},
+ {"\x1b[1", ``},
+ {"\x1b[FOO\x1b[", ``},
+ {"\x1b[mFOO\x1b[m", `FOO`},
}
- for k, v := range kases {
+ var render Renderer
+ for i, c := range cases {
var buf strings.Builder
- canRender := render.CanRender("test", strings.NewReader(k))
- assert.True(t, canRender)
+ st := typesniffer.DetectContentType([]byte(c.input))
+ canRender := render.CanRender("test", st, []byte(c.input))
+ if c.expected == "" {
+ assert.False(t, canRender, "case %d: expected not to render", i)
+ continue
+ }
- err := render.Render(markup.NewRenderContext(t.Context()), strings.NewReader(k), &buf)
+ assert.True(t, canRender)
+ err := render.Render(markup.NewRenderContext(t.Context()), strings.NewReader(c.input), &buf)
assert.NoError(t, err)
- assert.Equal(t, v, buf.String())
+ assert.Equal(t, c.expected, buf.String())
}
}
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 7c3bd93699..51afd4be00 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"regexp"
+ "slices"
"strings"
"sync"
@@ -86,8 +87,8 @@ var globalVars = sync.OnceValue(func() *globalVarsType {
// codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20"
v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`)
- // cleans: "<foo/bar", "<any words/", ("<html", "<head", "<script", "<style")
- v.tagCleaner = regexp.MustCompile(`(?i)<(/?\w+/\w+|/[\w ]+/|/?(html|head|script|style\b))`)
+ // cleans: "<foo/bar", "<any words/", ("<html", "<head", "<script", "<style", "<?", "<%")
+ v.tagCleaner = regexp.MustCompile(`(?i)<(/?\w+/\w+|/[\w ]+/|/?(html|head|script|style|%|\?)\b)`)
v.nulCleaner = strings.NewReplacer("\000", "")
return v
})
@@ -109,13 +110,7 @@ func CustomLinkURLSchemes(schemes []string) {
if !validScheme.MatchString(s) {
continue
}
- without := false
- for _, sna := range xurls.SchemesNoAuthority {
- if s == sna {
- without = true
- break
- }
- }
+ without := slices.Contains(xurls.SchemesNoAuthority, s)
if without {
s += ":"
} else {
@@ -253,7 +248,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
node, err := html.Parse(io.MultiReader(
// prepend "<html><body>"
strings.NewReader("<html><body>"),
- // Strip out nuls - they're always invalid
+ // strip out NULLs (they're always invalid), and escape known tags
bytes.NewReader(globalVars().tagCleaner.ReplaceAll([]byte(globalVars().nulCleaner.Replace(string(rawHTML))), []byte("&lt;$1"))),
// close the tags
strings.NewReader("</body></html>"),
@@ -320,6 +315,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
}
processNodeAttrID(node)
+ processFootnoteNode(ctx, node) // FIXME: the footnote processing should be done in the "footnote.go" renderer directly
if isEmojiNode(node) {
// TextNode emoji will be converted to `<span class="emoji">`, then the next iteration will visit the "span"
diff --git a/modules/markup/html_commit.go b/modules/markup/html_commit.go
index 967c327f36..fe7a034967 100644
--- a/modules/markup/html_commit.go
+++ b/modules/markup/html_commit.go
@@ -62,7 +62,7 @@ func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
// if url ends in '.', it's very likely that it is not part of the actual url but used to finish a sentence.
ret.PosEnd--
ret.FullURL = ret.FullURL[:len(ret.FullURL)-1]
- for i := 0; i < len(m); i++ {
+ for i := range m {
m[i] = min(m[i], ret.PosEnd)
}
}
diff --git a/modules/markup/html_issue_test.go b/modules/markup/html_issue_test.go
index c68429641f..39cd9dcf6a 100644
--- a/modules/markup/html_issue_test.go
+++ b/modules/markup/html_issue_test.go
@@ -30,6 +30,7 @@ func TestRender_IssueList(t *testing.T) {
rctx := markup.NewTestRenderContext(markup.TestAppURL, map[string]string{
"user": "test-user", "repo": "test-repo",
"markupAllowShortIssuePattern": "true",
+ "footnoteContextId": "12345",
})
out, err := markdown.RenderString(rctx, input)
require.NoError(t, err)
@@ -69,4 +70,22 @@ func TestRender_IssueList(t *testing.T) {
</ul>`,
)
})
+
+ t.Run("IssueFootnote", func(t *testing.T) {
+ test(
+ "foo[^1][^2]\n\n[^1]: bar\n[^2]: baz",
+ `<p>foo<sup id="fnref:user-content-1-12345"><a href="#fn:user-content-1-12345" rel="nofollow">1 </a></sup><sup id="fnref:user-content-2-12345"><a href="#fn:user-content-2-12345" rel="nofollow">2 </a></sup></p>
+<div>
+<hr/>
+<ol>
+<li id="fn:user-content-1-12345">
+<p>bar <a href="#fnref:user-content-1-12345" rel="nofollow">↩︎</a></p>
+</li>
+<li id="fn:user-content-2-12345">
+<p>baz <a href="#fnref:user-content-2-12345" rel="nofollow">↩︎</a></p>
+</li>
+</ol>
+</div>`,
+ )
+ })
}
diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go
index 1ea0b14028..43faef1681 100644
--- a/modules/markup/html_link.go
+++ b/modules/markup/html_link.go
@@ -31,8 +31,8 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
// It makes page handling terrible, but we prefer GitHub syntax
// And fall back to MediaWiki only when it is obvious from the look
// Of text and link contents
- sl := strings.Split(content, "|")
- for _, v := range sl {
+ sl := strings.SplitSeq(content, "|")
+ for v := range sl {
if equalPos := strings.IndexByte(v, '='); equalPos == -1 {
// There is no equal in this argument; this is a mandatory arg
if props["name"] == "" {
diff --git a/modules/markup/html_node.go b/modules/markup/html_node.go
index 68858b024a..4eb78fdd2b 100644
--- a/modules/markup/html_node.go
+++ b/modules/markup/html_node.go
@@ -15,6 +15,14 @@ func isAnchorIDUserContent(s string) bool {
return strings.HasPrefix(s, "user-content-") || strings.Contains(s, ":user-content-")
}
+func isAnchorIDFootnote(s string) bool {
+ return strings.HasPrefix(s, "fnref:user-content-") || strings.HasPrefix(s, "fn:user-content-")
+}
+
+func isAnchorHrefFootnote(s string) bool {
+ return strings.HasPrefix(s, "#fnref:user-content-") || strings.HasPrefix(s, "#fn:user-content-")
+}
+
func processNodeAttrID(node *html.Node) {
// Add user-content- to IDs and "#" links if they don't already have them,
// and convert the link href to a relative link to the host root
@@ -27,6 +35,18 @@ func processNodeAttrID(node *html.Node) {
}
}
+func processFootnoteNode(ctx *RenderContext, node *html.Node) {
+ for idx, attr := range node.Attr {
+ if (attr.Key == "id" && isAnchorIDFootnote(attr.Val)) ||
+ (attr.Key == "href" && isAnchorHrefFootnote(attr.Val)) {
+ if footnoteContextID := ctx.RenderOptions.Metas["footnoteContextId"]; footnoteContextID != "" {
+ node.Attr[idx].Val = attr.Val + "-" + footnoteContextID
+ }
+ continue
+ }
+ }
+}
+
func processNodeA(ctx *RenderContext, node *html.Node) {
for idx, attr := range node.Attr {
if attr.Key == "href" {
@@ -43,8 +63,11 @@ func processNodeA(ctx *RenderContext, node *html.Node) {
func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
next = img.NextSibling
+ attrSrc, hasLazy := "", false
for i, imgAttr := range img.Attr {
+ hasLazy = hasLazy || imgAttr.Key == "loading" && imgAttr.Val == "lazy"
if imgAttr.Key != "src" {
+ attrSrc = imgAttr.Val
continue
}
@@ -52,8 +75,8 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
isLinkable := imgSrcOrigin != "" && !strings.HasPrefix(imgSrcOrigin, "data:")
// By default, the "<img>" tag should also be clickable,
- // because frontend use `<img>` to paste the re-scaled image into the markdown,
- // so it must match the default markdown image behavior.
+ // because frontend uses `<img>` to paste the re-scaled image into the Markdown,
+ // so it must match the default Markdown image behavior.
cnt := 0
for p := img.Parent; isLinkable && p != nil && cnt < 2; p = p.Parent {
if hasParentAnchor := p.Type == html.ElementNode && p.Data == "a"; hasParentAnchor {
@@ -78,6 +101,9 @@ func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
imgAttr.Val = camoHandleLink(imgAttr.Val)
img.Attr[i] = imgAttr
}
+ if !RenderBehaviorForTesting.DisableAdditionalAttributes && !hasLazy && !strings.HasPrefix(attrSrc, "data:") {
+ img.Attr = append(img.Attr, html.Attribute{Key: "loading", Val: "lazy"})
+ }
return next
}
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 58f71bdd7b..5fdbf43f7c 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -525,6 +525,10 @@ func TestPostProcess(t *testing.T) {
test("<script>a</script>", `&lt;script&gt;a&lt;/script&gt;`)
test("<STYLE>a", `&lt;STYLE&gt;a`)
test("<style>a</STYLE>", `&lt;style&gt;a&lt;/STYLE&gt;`)
+
+ // other special tags, our special behavior
+ test("<?php\nfoo", "&lt;?php\nfoo")
+ test("<%asp\nfoo", "&lt;%asp\nfoo")
}
func TestIssue16020(t *testing.T) {
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 79df547c2c..3b788432ba 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -182,10 +182,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
rc := &RenderConfig{Meta: markup.RenderMetaAsDetails}
buf, _ = ExtractMetadataBytes(buf, rc)
- metaLength := bufWithMetadataLength - len(buf)
- if metaLength < 0 {
- metaLength = 0
- }
+ metaLength := max(bufWithMetadataLength-len(buf), 0)
rc.metaLength = metaLength
pc.Set(renderConfigKey, rc)
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 2310895fc3..4eb01bcc2d 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -47,7 +47,7 @@ func TestRender_StandardLinks(t *testing.T) {
func TestRender_Images(t *testing.T) {
setting.AppURL = AppURL
- test := func(input, expected string) {
+ render := func(input, expected string) {
buffer, err := markdown.RenderString(markup.NewTestRenderContext(FullURL), input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
@@ -59,27 +59,32 @@ func TestRender_Images(t *testing.T) {
result := util.URLJoin(FullURL, url)
// hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
- test(
+ render(
"!["+title+"]("+url+")",
`<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[["+title+"|"+url+"]]",
`<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[!["+title+"]("+url+")]("+href+")",
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"!["+title+"]("+url+")",
`<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[["+title+"|"+url+"]]",
`<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[!["+title+"]("+url+")]("+href+")",
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
+
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, false)()
+ render(
+ "<a><img src='a.jpg'></a>", // by the way, empty "a" tag will be removed
+ `<p dir="auto"><img src="http://localhost:3000/user13/repo11/a.jpg" loading="lazy"/></p>`)
}
func TestTotal_RenderString(t *testing.T) {
@@ -223,7 +228,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
<dd>This is another definition of the second term.</dd>
</dl>
<h3 id="user-content-footnotes">Footnotes</h3>
-<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2</a></sup></p>
+<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1 </a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2 </a></sup></p>
<div>
<hr/>
<ol>
@@ -252,7 +257,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
return username == "r-lyeh"
},
})
- for i := 0; i < len(sameCases); i++ {
+ for i := range sameCases {
line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), sameCases[i])
assert.NoError(t, err)
assert.Equal(t, testAnswers[i], string(line))
diff --git a/modules/markup/markdown/math/block_renderer.go b/modules/markup/markdown/math/block_renderer.go
index 412e4d0dee..95a336a02c 100644
--- a/modules/markup/markdown/math/block_renderer.go
+++ b/modules/markup/markdown/math/block_renderer.go
@@ -42,7 +42,7 @@ func (r *BlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node) {
l := n.Lines().Len()
- for i := 0; i < l; i++ {
+ for i := range l {
line := n.Lines().At(i)
_, _ = w.Write(util.EscapeHTML(line.Value(source)))
}
@@ -51,8 +51,8 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node)
func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
n := node.(*Block)
if entering {
- code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
- _ = r.renderInternal.FormatWithSafeAttrs(w, template.HTML(code))
+ codeHTML := giteaUtil.Iif[template.HTML](n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
+ _, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(codeHTML)))
r.writeLines(w, source, n)
} else {
_, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")
diff --git a/modules/markup/markdown/math/inline_renderer.go b/modules/markup/markdown/math/inline_renderer.go
index d000a7b317..eeeb60cc7e 100644
--- a/modules/markup/markdown/math/inline_renderer.go
+++ b/modules/markup/markdown/math/inline_renderer.go
@@ -28,7 +28,7 @@ func NewInlineRenderer(renderInternal *internal.RenderInternal) renderer.NodeRen
func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
- _ = r.renderInternal.FormatWithSafeAttrs(w, `<code class="language-math">`)
+ _, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(`<code class="language-math">`)))
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
segment := c.(*ast.Text).Segment
value := util.EscapeHTML(segment.Value(source))
diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go
index 3f74adeaef..283d289d48 100644
--- a/modules/markup/markdown/meta_test.go
+++ b/modules/markup/markdown/meta_test.go
@@ -60,7 +60,7 @@ func TestExtractMetadata(t *testing.T) {
func TestExtractMetadataBytes(t *testing.T) {
t.Run("ValidFrontAndBody", func(t *testing.T) {
var meta IssueTemplate
- body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
+ body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
assert.NoError(t, err)
assert.Equal(t, bodyTest, string(body))
assert.Equal(t, metaTest, meta)
@@ -69,19 +69,19 @@ func TestExtractMetadataBytes(t *testing.T) {
t.Run("NoFirstSeparator", func(t *testing.T) {
var meta IssueTemplate
- _, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta)
+ _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
assert.Error(t, err)
})
t.Run("NoLastSeparator", func(t *testing.T) {
var meta IssueTemplate
- _, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta)
+ _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
assert.Error(t, err)
})
t.Run("NoBody", func(t *testing.T) {
var meta IssueTemplate
- body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
+ body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
assert.NoError(t, err)
assert.Empty(t, string(body))
assert.Equal(t, metaTest, meta)
diff --git a/modules/markup/markdown/transform_blockquote.go b/modules/markup/markdown/transform_blockquote.go
index 3a8c6fa018..bf17f01681 100644
--- a/modules/markup/markdown/transform_blockquote.go
+++ b/modules/markup/markdown/transform_blockquote.go
@@ -46,7 +46,7 @@ func (g *ASTTransformer) extractBlockquoteAttentionEmphasis(firstParagraph ast.N
if !ok {
return "", nil
}
- val1 := string(node1.Text(reader.Source())) //nolint:staticcheck
+ val1 := string(node1.Text(reader.Source())) //nolint:staticcheck // Text is deprecated
attentionType := strings.ToLower(val1)
if g.attentionTypes.Contains(attentionType) {
return attentionType, []ast.Node{node1}
diff --git a/modules/markup/markdown/transform_codespan.go b/modules/markup/markdown/transform_codespan.go
index bccc43aad2..c2e4295bc2 100644
--- a/modules/markup/markdown/transform_codespan.go
+++ b/modules/markup/markdown/transform_codespan.go
@@ -68,7 +68,7 @@ func cssColorHandler(value string) bool {
}
func (g *ASTTransformer) transformCodeSpan(_ *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) {
- colorContent := v.Text(reader.Source()) //nolint:staticcheck
+ colorContent := v.Text(reader.Source()) //nolint:staticcheck // Text is deprecated
if cssColorHandler(string(colorContent)) {
v.AppendChild(v, NewColorPreview(colorContent))
}
diff --git a/modules/markup/markdown/transform_heading.go b/modules/markup/markdown/transform_heading.go
index 5f8a12794d..a229a7b1a4 100644
--- a/modules/markup/markdown/transform_heading.go
+++ b/modules/markup/markdown/transform_heading.go
@@ -16,10 +16,10 @@ import (
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]Header) {
for _, attr := range v.Attributes() {
if _, ok := attr.Value.([]byte); !ok {
- v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value)))
+ v.SetAttribute(attr.Name, fmt.Appendf(nil, "%v", attr.Value))
}
}
- txt := v.Text(reader.Source()) //nolint:staticcheck
+ txt := v.Text(reader.Source()) //nolint:staticcheck // Text is deprecated
header := Header{
Text: util.UnsafeBytesToString(txt),
Level: v.Level,
diff --git a/modules/markup/mdstripper/mdstripper.go b/modules/markup/mdstripper/mdstripper.go
index c589926b5e..6e392444b4 100644
--- a/modules/markup/mdstripper/mdstripper.go
+++ b/modules/markup/mdstripper/mdstripper.go
@@ -46,7 +46,7 @@ func (r *stripRenderer) Render(w io.Writer, source []byte, doc ast.Node) error {
coalesce := prevSibIsText
r.processString(
w,
- v.Text(source), //nolint:staticcheck
+ v.Text(source), //nolint:staticcheck // Text is deprecated
coalesce)
if v.SoftLineBreak() {
r.doubleSpace(w)
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index 35f90eb46c..b6e9c348b7 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -4,12 +4,12 @@
package markup
import (
- "bytes"
"io"
"path"
"strings"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/typesniffer"
)
// Renderer defines an interface for rendering markup file to HTML
@@ -37,7 +37,7 @@ type ExternalRenderer interface {
// RendererContentDetector detects if the content can be rendered
// by specified renderer
type RendererContentDetector interface {
- CanRender(filename string, input io.Reader) bool
+ CanRender(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) bool
}
var (
@@ -60,13 +60,9 @@ func GetRendererByFileName(filename string) Renderer {
}
// DetectRendererType detects the markup type of the content
-func DetectRendererType(filename string, input io.Reader) string {
- buf, err := io.ReadAll(input)
- if err != nil {
- return ""
- }
+func DetectRendererType(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) string {
for _, renderer := range renderers {
- if detector, ok := renderer.(RendererContentDetector); ok && detector.CanRender(filename, bytes.NewReader(buf)) {
+ if detector, ok := renderer.(RendererContentDetector); ok && detector.CanRender(filename, sniffedType, prefetchBuf) {
return renderer.Name()
}
}
diff --git a/modules/markup/sanitizer_default.go b/modules/markup/sanitizer_default.go
index 14161eb533..0fbf0f0b24 100644
--- a/modules/markup/sanitizer_default.go
+++ b/modules/markup/sanitizer_default.go
@@ -4,6 +4,7 @@
package markup
import (
+ "html/template"
"io"
"net/url"
"regexp"
@@ -52,6 +53,8 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
+ policy.AllowAttrs("loading").OnElements("img")
+
// Allow generally safe attributes (reference: https://github.com/jch/html-pipeline)
generalSafeAttrs := []string{
"abbr", "accept", "accept-charset",
@@ -90,9 +93,9 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
return policy
}
-// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
-func Sanitize(s string) string {
- return GetDefaultSanitizer().defaultPolicy.Sanitize(s)
+// Sanitize use default sanitizer policy to sanitize a string
+func Sanitize(s string) template.HTML {
+ return template.HTML(GetDefaultSanitizer().defaultPolicy.Sanitize(s))
}
// SanitizeReader sanitizes a Reader
diff --git a/modules/markup/sanitizer_default_test.go b/modules/markup/sanitizer_default_test.go
index 5282916944..e5ba018e1b 100644
--- a/modules/markup/sanitizer_default_test.go
+++ b/modules/markup/sanitizer_default_test.go
@@ -69,6 +69,6 @@ func TestSanitizer(t *testing.T) {
}
for i := 0; i < len(testCases); i += 2 {
- assert.Equal(t, testCases[i+1], Sanitize(testCases[i]))
+ assert.Equal(t, testCases[i+1], string(Sanitize(testCases[i])))
}
}
diff --git a/modules/migration/schemas_bindata.go b/modules/migration/schemas_bindata.go
index c5db3b3461..695c2c1135 100644
--- a/modules/migration/schemas_bindata.go
+++ b/modules/migration/schemas_bindata.go
@@ -3,6 +3,28 @@
//go:build bindata
+//go:generate go run ../../build/generate-bindata.go ../../modules/migration/schemas bindata.dat
+
package migration
-//go:generate go run ../../build/generate-bindata.go ../../modules/migration/schemas migration bindata.go
+import (
+ "io"
+ "io/fs"
+ "path"
+ "sync"
+
+ _ "embed"
+
+ "code.gitea.io/gitea/modules/assetfs"
+)
+
+//go:embed bindata.dat
+var bindata []byte
+
+var BuiltinAssets = sync.OnceValue(func() fs.FS {
+ return assetfs.NewEmbeddedFS(bindata)
+})
+
+func openSchema(filename string) (io.ReadCloser, error) {
+ return BuiltinAssets().Open(path.Base(filename))
+}
diff --git a/modules/migration/schemas_static.go b/modules/migration/schemas_static.go
deleted file mode 100644
index 8a0c340a65..0000000000
--- a/modules/migration/schemas_static.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-//go:build bindata
-
-package migration
-
-import (
- "io"
- "path"
-)
-
-func openSchema(filename string) (io.ReadCloser, error) {
- return Assets.Open(path.Base(filename))
-}
diff --git a/modules/optional/option.go b/modules/optional/option.go
index ccbad259c2..6075c6347e 100644
--- a/modules/optional/option.go
+++ b/modules/optional/option.go
@@ -5,6 +5,12 @@ package optional
import "strconv"
+// Option is a generic type that can hold a value of type T or be empty (None).
+//
+// It must use the slice type to work with "chi" form values binding:
+// * non-existing value are represented as an empty slice (None)
+// * existing value is represented as a slice with one element (Some)
+// * multiple values are represented as a slice with multiple elements (Some), the Value is the first element (not well-defined in this case)
type Option[T any] []T
func None[T any]() Option[T] {
diff --git a/modules/optional/serialization_test.go b/modules/optional/serialization_test.go
index 21d3ad8470..cf81a94cfc 100644
--- a/modules/optional/serialization_test.go
+++ b/modules/optional/serialization_test.go
@@ -4,7 +4,7 @@
package optional_test
import (
- std_json "encoding/json" //nolint:depguard
+ std_json "encoding/json" //nolint:depguard // for testing purpose
"testing"
"code.gitea.io/gitea/modules/json"
diff --git a/modules/options/options_bindata.go b/modules/options/options_bindata.go
index 29151cb3cb..b2321d7eb5 100644
--- a/modules/options/options_bindata.go
+++ b/modules/options/options_bindata.go
@@ -3,6 +3,21 @@
//go:build bindata
+//go:generate go run ../../build/generate-bindata.go ../../options bindata.dat
+
package options
-//go:generate go run ../../build/generate-bindata.go ../../options options bindata.go
+import (
+ "sync"
+
+ _ "embed"
+
+ "code.gitea.io/gitea/modules/assetfs"
+)
+
+//go:embed bindata.dat
+var bindata []byte
+
+var BuiltinAssets = sync.OnceValue(func() *assetfs.Layer {
+ return assetfs.Bindata("builtin(bindata)", assetfs.NewEmbeddedFS(bindata))
+})
diff --git a/modules/options/dynamic.go b/modules/options/options_dynamic.go
index 085492d11c..085492d11c 100644
--- a/modules/options/dynamic.go
+++ b/modules/options/options_dynamic.go
diff --git a/modules/options/static.go b/modules/options/static.go
deleted file mode 100644
index 72b28e990e..0000000000
--- a/modules/options/static.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-//go:build bindata
-
-package options
-
-import (
- "code.gitea.io/gitea/modules/assetfs"
-)
-
-func BuiltinAssets() *assetfs.Layer {
- return assetfs.Bindata("builtin(bindata)", Assets)
-}
diff --git a/models/packages/container/const.go b/modules/packages/container/const.go
index 0dfbda051d..6c7c9b46d1 100644
--- a/models/packages/container/const.go
+++ b/modules/packages/container/const.go
@@ -4,6 +4,8 @@
package container
const (
+ ContentTypeDockerDistributionManifestV2 = "application/vnd.docker.distribution.manifest.v2+json"
+
ManifestFilename = "manifest.json"
UploadVersion = "_upload"
)
diff --git a/modules/packages/container/metadata.go b/modules/packages/container/metadata.go
index 2a41fb9105..3ef0684d13 100644
--- a/modules/packages/container/metadata.go
+++ b/modules/packages/container/metadata.go
@@ -71,14 +71,34 @@ type Manifest struct {
Size int64 `json:"size"`
}
+func IsMediaTypeValid(mt string) bool {
+ return strings.HasPrefix(mt, "application/vnd.docker.") || strings.HasPrefix(mt, "application/vnd.oci.")
+}
+
+func IsMediaTypeImageManifest(mt string) bool {
+ return strings.EqualFold(mt, oci.MediaTypeImageManifest) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.v2+json")
+}
+
+func IsMediaTypeImageIndex(mt string) bool {
+ return strings.EqualFold(mt, oci.MediaTypeImageIndex) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.list.v2+json")
+}
+
// ParseImageConfig parses the metadata of an image config
-func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) {
- if strings.EqualFold(mt, helm.ConfigMediaType) {
+func ParseImageConfig(mediaType string, r io.Reader) (*Metadata, error) {
+ if strings.EqualFold(mediaType, helm.ConfigMediaType) {
return parseHelmConfig(r)
}
// fallback to OCI Image Config
- return parseOCIImageConfig(r)
+ // FIXME: this fallback is not right, we should strictly check the media type in the future
+ metadata, err := parseOCIImageConfig(r)
+ if err != nil {
+ if !IsMediaTypeImageManifest(mediaType) {
+ return &Metadata{Platform: "unknown/unknown"}, nil
+ }
+ return nil, err
+ }
+ return metadata, nil
}
func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
diff --git a/modules/packages/container/metadata_test.go b/modules/packages/container/metadata_test.go
index 665499b2e6..0f2d702925 100644
--- a/modules/packages/container/metadata_test.go
+++ b/modules/packages/container/metadata_test.go
@@ -11,6 +11,7 @@ import (
oci "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestParseImageConfig(t *testing.T) {
@@ -58,4 +59,8 @@ func TestParseImageConfig(t *testing.T) {
assert.ElementsMatch(t, []string{author}, metadata.Authors)
assert.Equal(t, projectURL, metadata.ProjectURL)
assert.Equal(t, repositoryURL, metadata.RepositoryURL)
+
+ metadata, err = ParseImageConfig("anything-unknown", strings.NewReader(""))
+ require.NoError(t, err)
+ assert.Equal(t, &Metadata{Platform: "unknown/unknown"}, metadata)
}
diff --git a/modules/packages/content_store.go b/modules/packages/content_store.go
index 37612556d7..dadb7eaefc 100644
--- a/modules/packages/content_store.go
+++ b/modules/packages/content_store.go
@@ -28,8 +28,7 @@ func NewContentStore() *ContentStore {
return contentStore
}
-// Get gets a package blob
-func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
+func (s *ContentStore) OpenBlob(key BlobHash256Key) (storage.Object, error) {
return s.store.Open(KeyToRelativePath(key))
}
diff --git a/modules/packages/npm/creator.go b/modules/packages/npm/creator.go
index 8ba4dbfba7..11b5123c27 100644
--- a/modules/packages/npm/creator.go
+++ b/modules/packages/npm/creator.go
@@ -58,7 +58,7 @@ type PackageMetadata struct {
Time map[string]time.Time `json:"time,omitempty"`
Homepage string `json:"homepage,omitempty"`
Keywords []string `json:"keywords,omitempty"`
- Repository Repository `json:"repository,omitempty"`
+ Repository Repository `json:"repository"`
Author User `json:"author"`
ReadmeFilename string `json:"readmeFilename,omitempty"`
Users map[string]bool `json:"users,omitempty"`
@@ -75,7 +75,7 @@ type PackageMetadataVersion struct {
Author User `json:"author"`
Homepage string `json:"homepage,omitempty"`
License string `json:"license,omitempty"`
- Repository Repository `json:"repository,omitempty"`
+ Repository Repository `json:"repository"`
Keywords []string `json:"keywords,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"`
BundleDependencies []string `json:"bundleDependencies,omitempty"`
diff --git a/modules/packages/npm/metadata.go b/modules/packages/npm/metadata.go
index d1d0263387..362d0470d5 100644
--- a/modules/packages/npm/metadata.go
+++ b/modules/packages/npm/metadata.go
@@ -23,5 +23,5 @@ type Metadata struct {
OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"`
Bin map[string]string `json:"bin,omitempty"`
Readme string `json:"readme,omitempty"`
- Repository Repository `json:"repository,omitempty"`
+ Repository Repository `json:"repository"`
}
diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go
index 1e98ddffde..a122590bf1 100644
--- a/modules/packages/nuget/metadata.go
+++ b/modules/packages/nuget/metadata.go
@@ -57,14 +57,24 @@ type Package struct {
// Metadata represents the metadata of a Nuget package
type Metadata struct {
- Description string `json:"description,omitempty"`
- ReleaseNotes string `json:"release_notes,omitempty"`
- Readme string `json:"readme,omitempty"`
- Authors string `json:"authors,omitempty"`
- ProjectURL string `json:"project_url,omitempty"`
- RepositoryURL string `json:"repository_url,omitempty"`
- RequireLicenseAcceptance bool `json:"require_license_acceptance"`
- Dependencies map[string][]Dependency `json:"dependencies,omitempty"`
+ Authors string `json:"authors,omitempty"`
+ Copyright string `json:"copyright,omitempty"`
+ Description string `json:"description,omitempty"`
+ DevelopmentDependency bool `json:"development_dependency,omitempty"`
+ IconURL string `json:"icon_url,omitempty"`
+ Language string `json:"language,omitempty"`
+ LicenseURL string `json:"license_url,omitempty"`
+ MinClientVersion string `json:"min_client_version,omitempty"`
+ Owners string `json:"owners,omitempty"`
+ ProjectURL string `json:"project_url,omitempty"`
+ Readme string `json:"readme,omitempty"`
+ ReleaseNotes string `json:"release_notes,omitempty"`
+ RepositoryURL string `json:"repository_url,omitempty"`
+ RequireLicenseAcceptance bool `json:"require_license_acceptance"`
+ Tags string `json:"tags,omitempty"`
+ Title string `json:"title,omitempty"`
+
+ Dependencies map[string][]Dependency `json:"dependencies,omitempty"`
}
// Dependency represents a dependency of a Nuget package
@@ -74,24 +84,30 @@ type Dependency struct {
}
// https://learn.microsoft.com/en-us/nuget/reference/nuspec
+// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd
type nuspecPackage struct {
Metadata struct {
- ID string `xml:"id"`
- Version string `xml:"version"`
- Authors string `xml:"authors"`
- RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
+ // required fields
+ Authors string `xml:"authors"`
+ Description string `xml:"description"`
+ ID string `xml:"id"`
+ Version string `xml:"version"`
+
+ // optional fields
+ Copyright string `xml:"copyright"`
+ DevelopmentDependency bool `xml:"developmentDependency"`
+ IconURL string `xml:"iconUrl"`
+ Language string `xml:"language"`
+ LicenseURL string `xml:"licenseUrl"`
+ MinClientVersion string `xml:"minClientVersion,attr"`
+ Owners string `xml:"owners"`
ProjectURL string `xml:"projectUrl"`
- Description string `xml:"description"`
- ReleaseNotes string `xml:"releaseNotes"`
Readme string `xml:"readme"`
- PackageTypes struct {
- PackageType []struct {
- Name string `xml:"name,attr"`
- } `xml:"packageType"`
- } `xml:"packageTypes"`
- Repository struct {
- URL string `xml:"url,attr"`
- } `xml:"repository"`
+ ReleaseNotes string `xml:"releaseNotes"`
+ RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
+ Tags string `xml:"tags"`
+ Title string `xml:"title"`
+
Dependencies struct {
Dependency []struct {
ID string `xml:"id,attr"`
@@ -107,6 +123,14 @@ type nuspecPackage struct {
} `xml:"dependency"`
} `xml:"group"`
} `xml:"dependencies"`
+ PackageTypes struct {
+ PackageType []struct {
+ Name string `xml:"name,attr"`
+ } `xml:"packageType"`
+ } `xml:"packageTypes"`
+ Repository struct {
+ URL string `xml:"url,attr"`
+ } `xml:"repository"`
} `xml:"metadata"`
}
@@ -167,13 +191,23 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
}
m := &Metadata{
- Description: p.Metadata.Description,
- ReleaseNotes: p.Metadata.ReleaseNotes,
Authors: p.Metadata.Authors,
+ Copyright: p.Metadata.Copyright,
+ Description: p.Metadata.Description,
+ DevelopmentDependency: p.Metadata.DevelopmentDependency,
+ IconURL: p.Metadata.IconURL,
+ Language: p.Metadata.Language,
+ LicenseURL: p.Metadata.LicenseURL,
+ MinClientVersion: p.Metadata.MinClientVersion,
+ Owners: p.Metadata.Owners,
ProjectURL: p.Metadata.ProjectURL,
+ ReleaseNotes: p.Metadata.ReleaseNotes,
RepositoryURL: p.Metadata.Repository.URL,
RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
- Dependencies: make(map[string][]Dependency),
+ Tags: p.Metadata.Tags,
+ Title: p.Metadata.Title,
+
+ Dependencies: make(map[string][]Dependency),
}
if p.Metadata.Readme != "" {
@@ -227,13 +261,13 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
func toNormalizedVersion(v *version.Version) string {
var buf bytes.Buffer
segments := v.Segments64()
- fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
+ _, _ = fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
if len(segments) > 3 && segments[3] > 0 {
- fmt.Fprintf(&buf, ".%d", segments[3])
+ _, _ = fmt.Fprintf(&buf, ".%d", segments[3])
}
pre := v.Prerelease()
if pre != "" {
- fmt.Fprint(&buf, "-", pre)
+ _, _ = fmt.Fprint(&buf, "-", pre)
}
return buf.String()
}
diff --git a/modules/packages/nuget/metadata_test.go b/modules/packages/nuget/metadata_test.go
index f466492f8a..90c3e8dfeb 100644
--- a/modules/packages/nuget/metadata_test.go
+++ b/modules/packages/nuget/metadata_test.go
@@ -12,44 +12,62 @@ import (
)
const (
- id = "System.Gitea"
- semver = "1.0.1"
- authors = "Gitea Authors"
- projectURL = "https://gitea.io"
- description = "Package Description"
- releaseNotes = "Package Release Notes"
- readme = "Readme"
- repositoryURL = "https://gitea.io/gitea/gitea"
- targetFramework = ".NETStandard2.1"
- dependencyID = "System.Text.Json"
- dependencyVersion = "5.0.0"
+ authors = "Gitea Authors"
+ copyright = "Package Copyright"
+ dependencyID = "System.Text.Json"
+ dependencyVersion = "5.0.0"
+ developmentDependency = true
+ description = "Package Description"
+ iconURL = "https://gitea.io/favicon.png"
+ id = "System.Gitea"
+ language = "Package Language"
+ licenseURL = "https://gitea.io/license"
+ minClientVersion = "1.0.0.0"
+ owners = "Package Owners"
+ projectURL = "https://gitea.io"
+ readme = "Readme"
+ releaseNotes = "Package Release Notes"
+ repositoryURL = "https://gitea.io/gitea/gitea"
+ requireLicenseAcceptance = true
+ tags = "tag_1 tag_2 tag_3"
+ targetFramework = ".NETStandard2.1"
+ title = "Package Title"
+ versionStr = "1.0.1"
)
const nuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
- <metadata>
- <id>` + id + `</id>
- <version>` + semver + `</version>
- <authors>` + authors + `</authors>
- <requireLicenseAcceptance>true</requireLicenseAcceptance>
- <projectUrl>` + projectURL + `</projectUrl>
- <description>` + description + `</description>
- <releaseNotes>` + releaseNotes + `</releaseNotes>
- <repository url="` + repositoryURL + `" />
- <readme>README.md</readme>
- <dependencies>
- <group targetFramework="` + targetFramework + `">
- <dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
- </group>
- </dependencies>
- </metadata>
+ <metadata minClientVersion="` + minClientVersion + `">
+ <authors>` + authors + `</authors>
+ <copyright>` + copyright + `</copyright>
+ <description>` + description + `</description>
+ <developmentDependency>true</developmentDependency>
+ <iconUrl>` + iconURL + `</iconUrl>
+ <id>` + id + `</id>
+ <language>` + language + `</language>
+ <licenseUrl>` + licenseURL + `</licenseUrl>
+ <owners>` + owners + `</owners>
+ <projectUrl>` + projectURL + `</projectUrl>
+ <readme>README.md</readme>
+ <releaseNotes>` + releaseNotes + `</releaseNotes>
+ <repository url="` + repositoryURL + `" />
+ <requireLicenseAcceptance>true</requireLicenseAcceptance>
+ <tags>` + tags + `</tags>
+ <title>` + title + `</title>
+ <version>` + versionStr + `</version>
+ <dependencies>
+ <group targetFramework="` + targetFramework + `">
+ <dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
+ </group>
+ </dependencies>
+ </metadata>
</package>`
const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>` + id + `</id>
- <version>` + semver + `</version>
+ <version>` + versionStr + `</version>
<description>` + description + `</description>
<packageTypes>
<packageType name="SymbolsPackage" />
@@ -140,14 +158,26 @@ func TestParsePackageMetaData(t *testing.T) {
assert.NotNil(t, np)
assert.Equal(t, DependencyPackage, np.PackageType)
- assert.Equal(t, id, np.ID)
- assert.Equal(t, semver, np.Version)
assert.Equal(t, authors, np.Metadata.Authors)
- assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, description, np.Metadata.Description)
- assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
+ assert.Equal(t, id, np.ID)
+ assert.Equal(t, versionStr, np.Version)
+
+ assert.Equal(t, copyright, np.Metadata.Copyright)
+ assert.Equal(t, developmentDependency, np.Metadata.DevelopmentDependency)
+ assert.Equal(t, iconURL, np.Metadata.IconURL)
+ assert.Equal(t, language, np.Metadata.Language)
+ assert.Equal(t, licenseURL, np.Metadata.LicenseURL)
+ assert.Equal(t, minClientVersion, np.Metadata.MinClientVersion)
+ assert.Equal(t, owners, np.Metadata.Owners)
+ assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, readme, np.Metadata.Readme)
+ assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL)
+ assert.Equal(t, requireLicenseAcceptance, np.Metadata.RequireLicenseAcceptance)
+ assert.Equal(t, tags, np.Metadata.Tags)
+ assert.Equal(t, title, np.Metadata.Title)
+
assert.Len(t, np.Metadata.Dependencies, 1)
assert.Contains(t, np.Metadata.Dependencies, targetFramework)
deps := np.Metadata.Dependencies[targetFramework]
@@ -180,7 +210,7 @@ func TestParsePackageMetaData(t *testing.T) {
assert.Equal(t, SymbolsPackage, np.PackageType)
assert.Equal(t, id, np.ID)
- assert.Equal(t, semver, np.Version)
+ assert.Equal(t, versionStr, np.Version)
assert.Equal(t, description, np.Metadata.Description)
assert.Empty(t, np.Metadata.Dependencies)
})
diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go
index 81bf0371a0..9c952e1f10 100644
--- a/modules/packages/nuget/symbol_extractor.go
+++ b/modules/packages/nuget/symbol_extractor.go
@@ -34,7 +34,7 @@ type PortablePdbList []*PortablePdb
func (l PortablePdbList) Close() {
for _, pdb := range l {
- pdb.Content.Close()
+ _ = pdb.Content.Close()
}
}
@@ -65,7 +65,7 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
buf, err := packages.CreateHashedBufferFromReader(f)
- f.Close()
+ _ = f.Close()
if err != nil {
return err
@@ -73,12 +73,12 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
id, err := ParseDebugHeaderID(buf)
if err != nil {
- buf.Close()
+ _ = buf.Close()
return fmt.Errorf("Invalid PDB file: %w", err)
}
if _, err := buf.Seek(0, io.SeekStart); err != nil {
- buf.Close()
+ _ = buf.Close()
return err
}
diff --git a/modules/packages/nuget/symbol_extractor_test.go b/modules/packages/nuget/symbol_extractor_test.go
index 711ad6d096..e841e377d9 100644
--- a/modules/packages/nuget/symbol_extractor_test.go
+++ b/modules/packages/nuget/symbol_extractor_test.go
@@ -24,14 +24,14 @@ func TestExtractPortablePdb(t *testing.T) {
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
w, _ := archive.Create(name)
- w.Write(content)
- archive.Close()
+ _, _ = w.Write(content)
+ _ = archive.Close()
return buf.Bytes()
}
t.Run("MissingPdbFiles", func(t *testing.T) {
var buf bytes.Buffer
- zip.NewWriter(&buf).Close()
+ _ = zip.NewWriter(&buf).Close()
pdbs, err := ExtractPortablePdb(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
assert.ErrorIs(t, err, ErrMissingPdbFiles)
diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go
index 4e6a5fc5f8..1505221acc 100644
--- a/modules/packages/rubygems/marshal.go
+++ b/modules/packages/rubygems/marshal.go
@@ -250,7 +250,7 @@ func (e *MarshalEncoder) marshalArray(arr reflect.Value) error {
return err
}
- for i := 0; i < length; i++ {
+ for i := range length {
if err := e.marshal(arr.Index(i).Interface()); err != nil {
return err
}
diff --git a/modules/packages/swift/metadata.go b/modules/packages/swift/metadata.go
index 24c4262ab7..85beb57607 100644
--- a/modules/packages/swift/metadata.go
+++ b/modules/packages/swift/metadata.go
@@ -47,7 +47,7 @@ type Metadata struct {
Keywords []string `json:"keywords,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
License string `json:"license,omitempty"`
- Author Person `json:"author,omitempty"`
+ Author Person `json:"author"`
Manifests map[string]*Manifest `json:"manifests,omitempty"`
}
diff --git a/modules/private/serv.go b/modules/private/serv.go
index 10e9f7995c..b1dafbd81b 100644
--- a/modules/private/serv.go
+++ b/modules/private/serv.go
@@ -46,18 +46,16 @@ type ServCommandResults struct {
}
// ServCommand preps for a serv call
-func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verbs ...string) (*ServCommandResults, ResponseExtra) {
+func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verb, lfsVerb string) (*ServCommandResults, ResponseExtra) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
keyID,
url.PathEscape(ownerName),
url.PathEscape(repoName),
mode,
)
- for _, verb := range verbs {
- if verb != "" {
- reqURL += "&verb=" + url.QueryEscape(verb)
- }
- }
+ reqURL += "&verb=" + url.QueryEscape(verb)
+ // reqURL += "&lfs_verb=" + url.QueryEscape(lfsVerb) // TODO: actually there is no use of this parameter. In the future, the URL construction should be more flexible
+ _ = lfsVerb
req := newInternalRequestAPI(ctx, reqURL, "GET")
return requestJSONResp(req, &ServCommandResults{})
}
diff --git a/modules/public/public.go b/modules/public/public.go
index 7f8ce29056..a7eace1538 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -44,7 +44,7 @@ func FileHandlerFunc() http.HandlerFunc {
func parseAcceptEncoding(val string) container.Set[string] {
parts := strings.Split(val, ";")
types := make(container.Set[string])
- for _, v := range strings.Split(parts[0], ",") {
+ for v := range strings.SplitSeq(parts[0], ",") {
types.Add(strings.TrimSpace(v))
}
return types
@@ -89,19 +89,16 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem,
servePublicAsset(w, req, fi, fi.ModTime(), f)
}
-type GzipBytesProvider interface {
- GzipBytes() []byte
-}
-
// servePublicAsset serve http content
func servePublicAsset(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) {
setWellKnownContentType(w, fi.Name())
httpcache.SetCacheControlInHeader(w.Header(), httpcache.CacheControlForPublicStatic())
encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
- if encodings.Contains("gzip") {
- // try to provide gzip content directly from bindata (provided by vfsgenÛ°CompressedFileInfo)
- if compressed, ok := fi.(GzipBytesProvider); ok {
- rdGzip := bytes.NewReader(compressed.GzipBytes())
+ fiEmbedded, _ := fi.(assetfs.EmbeddedFileInfo)
+ if encodings.Contains("gzip") && fiEmbedded != nil {
+ // try to provide gzip content directly from bindata
+ if gzipBytes, ok := fiEmbedded.GetGzipContent(); ok {
+ rdGzip := bytes.NewReader(gzipBytes)
// all gzipped static files (from bindata) are managed by Gitea, so we can make sure every file has the correct ext name
// then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
if w.Header().Get("Content-Type") == "" {
@@ -113,5 +110,4 @@ func servePublicAsset(w http.ResponseWriter, req *http.Request, fi os.FileInfo,
}
}
http.ServeContent(w, req, fi.Name(), modtime, content)
- return
}
diff --git a/modules/public/public_bindata.go b/modules/public/public_bindata.go
index 4878f88ad1..2dcf3e72e4 100644
--- a/modules/public/public_bindata.go
+++ b/modules/public/public_bindata.go
@@ -5,4 +5,19 @@
package public
-//go:generate go run ../../build/generate-bindata.go ../../public public bindata.go true
+//go:generate go run ../../build/generate-bindata.go ../../public bindata.dat
+
+import (
+ "sync"
+
+ _ "embed"
+
+ "code.gitea.io/gitea/modules/assetfs"
+)
+
+//go:embed bindata.dat
+var bindata []byte
+
+var BuiltinAssets = sync.OnceValue(func() *assetfs.Layer {
+ return assetfs.Bindata("builtin(bindata)", assetfs.NewEmbeddedFS(bindata))
+})
diff --git a/modules/public/serve_dynamic.go b/modules/public/public_dynamic.go
index a668b17c34..a668b17c34 100644
--- a/modules/public/serve_dynamic.go
+++ b/modules/public/public_dynamic.go
diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go
deleted file mode 100644
index e79085021e..0000000000
--- a/modules/public/serve_static.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2016 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-//go:build bindata
-
-package public
-
-import (
- "time"
-
- "code.gitea.io/gitea/modules/assetfs"
- "code.gitea.io/gitea/modules/timeutil"
-)
-
-var _ GzipBytesProvider = (*vfsgenÛ°CompressedFileInfo)(nil)
-
-// GlobalModTime provide a global mod time for embedded asset files
-func GlobalModTime(filename string) time.Time {
- return timeutil.GetExecutableModTime()
-}
-
-func BuiltinAssets() *assetfs.Layer {
- return assetfs.Bindata("builtin(bindata)", Assets)
-}
diff --git a/modules/queue/base_levelqueue_common.go b/modules/queue/base_levelqueue_common.go
index 78d3b85a8a..d37093b84d 100644
--- a/modules/queue/base_levelqueue_common.go
+++ b/modules/queue/base_levelqueue_common.go
@@ -83,7 +83,7 @@ func prepareLevelDB(cfg *BaseConfig) (conn string, db *leveldb.DB, err error) {
}
conn = cfg.ConnStr
}
- for i := 0; i < 10; i++ {
+ for range 10 {
if db, err = nosql.GetManager().GetLevelDB(conn); err == nil {
break
}
diff --git a/modules/queue/base_redis.go b/modules/queue/base_redis.go
index a1e234943d..bea0fd7a98 100644
--- a/modules/queue/base_redis.go
+++ b/modules/queue/base_redis.go
@@ -29,7 +29,7 @@ func newBaseRedisGeneric(cfg *BaseConfig, unique bool) (baseQueue, error) {
client := nosql.GetManager().GetRedisClient(cfg.ConnStr)
var err error
- for i := 0; i < 10; i++ {
+ for range 10 {
err = client.Ping(graceful.GetManager().ShutdownContext()).Err()
if err == nil {
break
diff --git a/modules/queue/base_test.go b/modules/queue/base_test.go
index 1a96ac1e1d..8e7c18d740 100644
--- a/modules/queue/base_test.go
+++ b/modules/queue/base_test.go
@@ -87,7 +87,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error)
// test blocking push if queue is full
for i := 0; i < cfg.Length; i++ {
- err = q.PushItem(ctx, []byte(fmt.Sprintf("item-%d", i)))
+ err = q.PushItem(ctx, fmt.Appendf(nil, "item-%d", i))
assert.NoError(t, err)
}
ctxTimed, cancel = context.WithTimeout(ctx, 10*time.Millisecond)
diff --git a/modules/queue/manager.go b/modules/queue/manager.go
index 079e2bee7a..ae6c51872d 100644
--- a/modules/queue/manager.go
+++ b/modules/queue/manager.go
@@ -6,6 +6,7 @@ package queue
import (
"context"
"errors"
+ "maps"
"sync"
"time"
@@ -70,9 +71,7 @@ func (m *Manager) ManagedQueues() map[int64]ManagedWorkerPoolQueue {
defer m.mu.Unlock()
queues := make(map[int64]ManagedWorkerPoolQueue, len(m.Queues))
- for k, v := range m.Queues {
- queues[k] = v
- }
+ maps.Copy(queues, m.Queues)
return queues
}
diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go
index 487c2f1a92..a6c369d5f9 100644
--- a/modules/queue/workerqueue_test.go
+++ b/modules/queue/workerqueue_test.go
@@ -77,17 +77,17 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) {
runCount := 2 // we can run these tests even hundreds times to see its stability
t.Run("1/1", func(t *testing.T) {
- for i := 0; i < runCount; i++ {
+ for range runCount {
test(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1})
}
})
t.Run("3/1", func(t *testing.T) {
- for i := 0; i < runCount; i++ {
+ for range runCount {
test(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1})
}
})
t.Run("4/5", func(t *testing.T) {
- for i := 0; i < runCount; i++ {
+ for range runCount {
test(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5})
}
})
@@ -96,17 +96,17 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) {
func TestWorkerPoolQueuePersistence(t *testing.T) {
runCount := 2 // we can run these tests even hundreds times to see its stability
t.Run("1/1", func(t *testing.T) {
- for i := 0; i < runCount; i++ {
+ for range runCount {
testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1, Length: 100})
}
})
t.Run("3/1", func(t *testing.T) {
- for i := 0; i < runCount; i++ {
+ for range runCount {
testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1, Length: 100})
}
})
t.Run("4/5", func(t *testing.T) {
- for i := 0; i < runCount; i++ {
+ for range runCount {
testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5, Length: 100})
}
})
@@ -141,7 +141,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett
q, _ := newWorkerPoolQueueForTest("pr_patch_checker_test", queueSetting, testHandler, true)
stop := runWorkerPoolQueue(q)
- for i := 0; i < testCount; i++ {
+ for i := range testCount {
_ = q.Push("task-" + strconv.Itoa(i))
}
close(startWhenAllReady)
@@ -186,7 +186,7 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) {
q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 1, Length: 100}, handler, false)
stop := runWorkerPoolQueue(q)
- for i := 0; i < 5; i++ {
+ for i := range 5 {
assert.NoError(t, q.Push(i))
}
@@ -202,7 +202,7 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) {
q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 3, Length: 100}, handler, false)
stop = runWorkerPoolQueue(q)
- for i := 0; i < 15; i++ {
+ for i := range 15 {
assert.NoError(t, q.Push(i))
}
@@ -274,7 +274,7 @@ func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) {
}
q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false)
stop := runWorkerPoolQueue(q)
- for i := 0; i < 100; i++ {
+ for i := range 100 {
assert.NoError(t, q.Push(i))
}
time.Sleep(500 * time.Millisecond)
diff --git a/modules/repository/branch.go b/modules/repository/branch.go
index 2bf9930f19..30aa0a6e85 100644
--- a/modules/repository/branch.go
+++ b/modules/repository/branch.go
@@ -41,11 +41,12 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
if err != nil {
return 0, fmt.Errorf("GetObjectFormat: %w", err)
}
- _, err = db.GetEngine(ctx).ID(repo.ID).Update(&repo_model.Repository{ObjectFormatName: objFmt.Name()})
- if err != nil {
- return 0, fmt.Errorf("UpdateRepository: %w", err)
+ if objFmt.Name() != repo.ObjectFormatName {
+ repo.ObjectFormatName = objFmt.Name()
+ if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "object_format_name"); err != nil {
+ return 0, fmt.Errorf("UpdateRepositoryColsWithAutoTime: %w", err)
+ }
}
- repo.ObjectFormatName = objFmt.Name() // keep consistent with db
allBranches := container.Set[string]{}
{
diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go
index 6e407015c2..030cd7714d 100644
--- a/modules/repository/commits_test.go
+++ b/modules/repository/commits_test.go
@@ -200,5 +200,3 @@ func TestListToPushCommits(t *testing.T) {
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
}
}
-
-// TODO TestPushUpdate
diff --git a/modules/repository/init.go b/modules/repository/init.go
index 91d4889782..12e9606c74 100644
--- a/modules/repository/init.go
+++ b/modules/repository/init.go
@@ -125,7 +125,7 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg
}
labels := make([]*issues_model.Label, len(list))
- for i := 0; i < len(list); i++ {
+ for i := range list {
labels[i] = &issues_model.Label{
Name: list[i].Name,
Exclusive: list[i].Exclusive,
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index bc147a4dd5..ad4a53b858 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -9,13 +9,10 @@ import (
"fmt"
"io"
"strings"
- "time"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/lfs"
@@ -59,118 +56,6 @@ func SyncRepoTags(ctx context.Context, repoID int64) error {
return SyncReleasesWithTags(ctx, repo, gitRepo)
}
-// SyncReleasesWithTags synchronizes release table with repository tags
-func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
- log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
-
- // optimized procedure for pull-mirrors which saves a lot of time (in
- // particular for repos with many tags).
- if repo.IsMirror {
- return pullMirrorReleaseSync(ctx, repo, gitRepo)
- }
-
- existingRelTags := make(container.Set[string])
- opts := repo_model.FindReleasesOptions{
- IncludeDrafts: true,
- IncludeTags: true,
- ListOptions: db.ListOptions{PageSize: 50},
- RepoID: repo.ID,
- }
- for page := 1; ; page++ {
- opts.Page = page
- rels, err := db.Find[repo_model.Release](gitRepo.Ctx, opts)
- if err != nil {
- return fmt.Errorf("unable to GetReleasesByRepoID in Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
- }
- if len(rels) == 0 {
- break
- }
- for _, rel := range rels {
- if rel.IsDraft {
- continue
- }
- commitID, err := gitRepo.GetTagCommitID(rel.TagName)
- if err != nil && !git.IsErrNotExist(err) {
- return fmt.Errorf("unable to GetTagCommitID for %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
- }
- if git.IsErrNotExist(err) || commitID != rel.Sha1 {
- if err := repo_model.PushUpdateDeleteTag(ctx, repo, rel.TagName); err != nil {
- return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
- }
- } else {
- existingRelTags.Add(strings.ToLower(rel.TagName))
- }
- }
- }
-
- _, err := gitRepo.WalkReferences(git.ObjectTag, 0, 0, func(sha1, refname string) error {
- tagName := strings.TrimPrefix(refname, git.TagPrefix)
- if existingRelTags.Contains(strings.ToLower(tagName)) {
- return nil
- }
-
- if err := PushUpdateAddTag(ctx, repo, gitRepo, tagName, sha1, refname); err != nil {
- // sometimes, some tags will be sync failed. i.e. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tag/?h=v2.6.11
- // this is a tree object, not a tag object which created before git
- log.Error("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %v", tagName, repo.ID, repo.OwnerName, repo.Name, err)
- }
-
- return nil
- })
- return err
-}
-
-// PushUpdateAddTag must be called for any push actions to add tag
-func PushUpdateAddTag(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, tagName, sha1, refname string) error {
- tag, err := gitRepo.GetTagWithID(sha1, tagName)
- if err != nil {
- return fmt.Errorf("unable to GetTag: %w", err)
- }
- commit, err := gitRepo.GetTagCommit(tag.Name)
- if err != nil {
- return fmt.Errorf("unable to get tag Commit: %w", err)
- }
-
- sig := tag.Tagger
- if sig == nil {
- sig = commit.Author
- }
- if sig == nil {
- sig = commit.Committer
- }
-
- var author *user_model.User
- createdAt := time.Unix(1, 0)
-
- if sig != nil {
- author, err = user_model.GetUserByEmail(ctx, sig.Email)
- if err != nil && !user_model.IsErrUserNotExist(err) {
- return fmt.Errorf("unable to GetUserByEmail for %q: %w", sig.Email, err)
- }
- createdAt = sig.When
- }
-
- commitsCount, err := commit.CommitsCount()
- if err != nil {
- return fmt.Errorf("unable to get CommitsCount: %w", err)
- }
-
- rel := repo_model.Release{
- RepoID: repo.ID,
- TagName: tagName,
- LowerTagName: strings.ToLower(tagName),
- Sha1: commit.ID.String(),
- NumCommits: commitsCount,
- CreatedUnix: timeutil.TimeStamp(createdAt.Unix()),
- IsTag: true,
- }
- if author != nil {
- rel.PublisherID = author.ID
- }
-
- return repo_model.SaveOrUpdateTag(ctx, repo, &rel)
-}
-
// StoreMissingLfsObjectsInRepository downloads missing LFS objects
func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, lfsClient lfs.Client) error {
contentStore := lfs.NewContentStore()
@@ -286,18 +171,19 @@ func (shortRelease) TableName() string {
return "release"
}
-// pullMirrorReleaseSync is a pull-mirror specific tag<->release table
+// SyncReleasesWithTags is a tag<->release table
// synchronization which overwrites all Releases from the repository tags. This
// can be relied on since a pull-mirror is always identical to its
-// upstream. Hence, after each sync we want the pull-mirror release set to be
+// upstream. Hence, after each sync we want the release set to be
// identical to the upstream tag set. This is much more efficient for
// repositories like https://github.com/vim/vim (with over 13000 tags).
-func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
- log.Trace("pullMirrorReleaseSync: rebuilding releases for pull-mirror Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
- tags, numTags, err := gitRepo.GetTagInfos(0, 0)
+func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
+ log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
+ tags, _, err := gitRepo.GetTagInfos(0, 0)
if err != nil {
return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
}
+ var added, deleted, updated int
err = db.WithTx(ctx, func(ctx context.Context) error {
dbReleases, err := db.Find[shortRelease](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID,
@@ -318,9 +204,7 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
TagName: tag.Name,
LowerTagName: strings.ToLower(tag.Name),
Sha1: tag.Object.String(),
- // NOTE: ignored, since NumCommits are unused
- // for pull-mirrors (only relevant when
- // displaying releases, IsTag: false)
+ // NOTE: ignored, The NumCommits value is calculated and cached on demand when the UI requires it.
NumCommits: -1,
CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
IsTag: true,
@@ -349,13 +233,14 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
}
}
+ added, deleted, updated = len(deletes), len(updates), len(inserts)
return nil
})
if err != nil {
return fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
}
- log.Trace("pullMirrorReleaseSync: done rebuilding %d releases", numTags)
+ log.Trace("SyncReleasesWithTags: %d tags added, %d tags deleted, %d tags updated", added, deleted, updated)
return nil
}
diff --git a/modules/reqctx/datastore.go b/modules/reqctx/datastore.go
index d025dad7f3..1d4bee613f 100644
--- a/modules/reqctx/datastore.go
+++ b/modules/reqctx/datastore.go
@@ -6,6 +6,7 @@ package reqctx
import (
"context"
"io"
+ "maps"
"sync"
"code.gitea.io/gitea/modules/process"
@@ -22,9 +23,7 @@ func (ds ContextData) GetData() ContextData {
}
func (ds ContextData) MergeFrom(other ContextData) ContextData {
- for k, v := range other {
- ds[k] = v
- }
+ maps.Copy(ds, other)
return ds
}
diff --git a/modules/setting/config_env.go b/modules/setting/config_env.go
index 5d94a9641f..409588dc44 100644
--- a/modules/setting/config_env.go
+++ b/modules/setting/config_env.go
@@ -97,7 +97,7 @@ func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
// decodeEnvironmentKey decode the environment key to section and key
// The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE
-func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) { //nolint:unparam
+func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) {
if !strings.HasPrefix(envKey, prefixGitea) {
return false, "", "", false
}
diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go
index 217ea53860..7d270ac21a 100644
--- a/modules/setting/config_env_test.go
+++ b/modules/setting/config_env_test.go
@@ -73,6 +73,9 @@ func TestDecodeEnvironmentKey(t *testing.T) {
assert.Equal(t, "sec", section)
assert.Equal(t, "KEY", key)
assert.True(t, file)
+
+ ok, _, _, _ = decodeEnvironmentKey("PREFIX__", "", "PREFIX__SEC__KEY")
+ assert.True(t, ok)
}
func TestEnvironmentToConfig(t *testing.T) {
diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go
index a0c53a1032..09eaaefdaf 100644
--- a/modules/setting/config_provider.go
+++ b/modules/setting/config_provider.go
@@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
- "gopkg.in/ini.v1" //nolint:depguard
+ "gopkg.in/ini.v1" //nolint:depguard // wrapper for this package
)
type ConfigKey interface {
diff --git a/modules/setting/git_test.go b/modules/setting/git_test.go
index 818bcf9df6..0d7f634abf 100644
--- a/modules/setting/git_test.go
+++ b/modules/setting/git_test.go
@@ -6,6 +6,8 @@ package setting
import (
"testing"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/stretchr/testify/assert"
)
@@ -36,12 +38,8 @@ diff.algorithm = other
}
func TestGitReflog(t *testing.T) {
- oldGit := Git
- oldGitConfig := GitConfig
- defer func() {
- Git = oldGit
- GitConfig = oldGitConfig
- }()
+ defer test.MockVariableValue(&Git)
+ defer test.MockVariableValue(&GitConfig)
// default reflog config without legacy options
cfg, err := NewConfigProviderFromData(``)
diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go
index e34baae012..ace7eec70e 100644
--- a/modules/setting/indexer.go
+++ b/modules/setting/indexer.go
@@ -96,7 +96,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
func IndexerGlobFromString(globstr string) []*GlobMatcher {
extarr := make([]*GlobMatcher, 0, 10)
- for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
+ for expr := range strings.SplitSeq(strings.ToLower(globstr), ",") {
expr = strings.TrimSpace(expr)
if expr != "" {
if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil {
diff --git a/modules/setting/log.go b/modules/setting/log.go
index 614d9ee75a..59866c7605 100644
--- a/modules/setting/log.go
+++ b/modules/setting/log.go
@@ -227,8 +227,8 @@ func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, logger
}
var eventWriters []log.EventWriter
- modes := strings.Split(modeVal, ",")
- for _, modeName := range modes {
+ modes := strings.SplitSeq(modeVal, ",")
+ for modeName := range modes {
modeName = strings.TrimSpace(modeName)
if modeName == "" {
continue
diff --git a/modules/setting/markup.go b/modules/setting/markup.go
index 365af05fcf..057b0650c3 100644
--- a/modules/setting/markup.go
+++ b/modules/setting/markup.go
@@ -149,8 +149,8 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
func newMarkupSanitizer(name string, sec ConfigSection) {
rule, ok := createMarkupSanitizerRule(name, sec)
if ok {
- if strings.HasPrefix(name, "sanitizer.") {
- names := strings.SplitN(strings.TrimPrefix(name, "sanitizer."), ".", 2)
+ if after, found := strings.CutPrefix(name, "sanitizer."); found {
+ names := strings.SplitN(after, ".", 2)
name = names[0]
}
for _, renderer := range ExternalMarkupRenderers {
diff --git a/modules/setting/mirror.go b/modules/setting/mirror.go
index 3aa530a1f4..300711789d 100644
--- a/modules/setting/mirror.go
+++ b/modules/setting/mirror.go
@@ -48,11 +48,7 @@ func loadMirrorFrom(rootCfg ConfigProvider) {
Mirror.MinInterval = 1 * time.Minute
}
if Mirror.DefaultInterval < Mirror.MinInterval {
- if time.Hour*8 < Mirror.MinInterval {
- Mirror.DefaultInterval = Mirror.MinInterval
- } else {
- Mirror.DefaultInterval = time.Hour * 8
- }
+ Mirror.DefaultInterval = max(time.Hour*8, Mirror.MinInterval)
log.Warn("Mirror.DefaultInterval is less than Mirror.MinInterval, set to %s", Mirror.DefaultInterval.String())
}
}
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index c6bdc65b32..318cf41108 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -100,11 +100,13 @@ var (
SigningKey string
SigningName string
SigningEmail string
+ SigningFormat string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
Wiki []string
DefaultTrustModel string
+ TrustedSSHKeys []string `ini:"TRUSTED_SSH_KEYS"`
} `ini:"repository.signing"`
}{
DetectedCharsetsOrder: []string{
@@ -242,20 +244,24 @@ var (
SigningKey string
SigningName string
SigningEmail string
+ SigningFormat string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
Wiki []string
DefaultTrustModel string
+ TrustedSSHKeys []string `ini:"TRUSTED_SSH_KEYS"`
}{
SigningKey: "default",
SigningName: "",
SigningEmail: "",
+ SigningFormat: "openpgp", // git.SigningKeyFormatOpenPGP
InitialCommit: []string{"always"},
CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
Wiki: []string{"never"},
DefaultTrustModel: "collaborator",
+ TrustedSSHKeys: []string{},
},
}
RepoRootPath string
diff --git a/modules/setting/security.go b/modules/setting/security.go
index 3ae4c005c7..153b6bc944 100644
--- a/modules/setting/security.go
+++ b/modules/setting/security.go
@@ -111,7 +111,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
if SecretKey == "" {
// FIXME: https://github.com/go-gitea/gitea/issues/16832
// Until it supports rotating an existing secret key, we shouldn't move users off of the widely used default value
- SecretKey = "!#@FDEWREWR&*(" //nolint:gosec
+ SecretKey = "!#@FDEWREWR&*("
}
CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible")
diff --git a/modules/setting/ssh.go b/modules/setting/ssh.go
index da8cdf58d2..900fc6ade2 100644
--- a/modules/setting/ssh.go
+++ b/modules/setting/ssh.go
@@ -51,9 +51,6 @@ var SSH = struct {
StartBuiltinServer: false,
Domain: "",
Port: 22,
- ServerCiphers: []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"},
- ServerKeyExchanges: []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"},
- ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"},
MinimumKeySizeCheck: true,
MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 3071},
ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"},
@@ -107,21 +104,20 @@ func loadSSHFrom(rootCfg ConfigProvider) {
homeDir = strings.ReplaceAll(homeDir, "\\", "/")
SSH.RootPath = filepath.Join(homeDir, ".ssh")
- serverCiphers := sec.Key("SSH_SERVER_CIPHERS").Strings(",")
- if len(serverCiphers) > 0 {
- SSH.ServerCiphers = serverCiphers
- }
- serverKeyExchanges := sec.Key("SSH_SERVER_KEY_EXCHANGES").Strings(",")
- if len(serverKeyExchanges) > 0 {
- SSH.ServerKeyExchanges = serverKeyExchanges
- }
- serverMACs := sec.Key("SSH_SERVER_MACS").Strings(",")
- if len(serverMACs) > 0 {
- SSH.ServerMACs = serverMACs
- }
+
if err = sec.MapTo(&SSH); err != nil {
log.Fatal("Failed to map SSH settings: %v", err)
}
+
+ serverCiphers := sec.Key("SSH_SERVER_CIPHERS").Strings(",")
+ SSH.ServerCiphers = util.Iif(len(serverCiphers) > 0, serverCiphers, nil)
+
+ serverKeyExchanges := sec.Key("SSH_SERVER_KEY_EXCHANGES").Strings(",")
+ SSH.ServerKeyExchanges = util.Iif(len(serverKeyExchanges) > 0, serverKeyExchanges, nil)
+
+ serverMACs := sec.Key("SSH_SERVER_MACS").Strings(",")
+ SSH.ServerMACs = util.Iif(len(serverMACs) > 0, serverMACs, nil)
+
for i, key := range SSH.ServerHostKeys {
if !filepath.IsAbs(key) {
SSH.ServerHostKeys[i] = filepath.Join(AppDataPath, key)
diff --git a/modules/setting/storage.go b/modules/setting/storage.go
index e1d9b1fa7a..ee246158d9 100644
--- a/modules/setting/storage.go
+++ b/modules/setting/storage.go
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"path/filepath"
+ "slices"
"strings"
)
@@ -30,12 +31,7 @@ var storageTypes = []StorageType{
// IsValidStorageType returns true if the given storage type is valid
func IsValidStorageType(storageType StorageType) bool {
- for _, t := range storageTypes {
- if t == storageType {
- return true
- }
- }
- return false
+ return slices.Contains(storageTypes, storageType)
}
// MinioStorageConfig represents the configuration for a minio storage
@@ -162,7 +158,7 @@ const (
targetSecIsSec // target section is from the name seciont [name]
)
-func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) { //nolint:unparam
+func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) { //nolint:unparam // FIXME: targetSecType is always 0, wrong design?
targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
if err != nil {
if !IsValidStorageType(StorageType(typ)) {
@@ -287,7 +283,7 @@ func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType,
return &storage, nil
}
-func getStorageForMinio(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) { //nolint:dupl
+func getStorageForMinio(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) { //nolint:dupl // duplicates azure setup
var storage Storage
storage.Type = StorageType(targetSec.Key("STORAGE_TYPE").String())
if err := targetSec.MapTo(&storage.MinioConfig); err != nil {
@@ -316,7 +312,7 @@ func getStorageForMinio(targetSec, overrideSec ConfigSection, tp targetSecType,
return &storage, nil
}
-func getStorageForAzureBlob(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) { //nolint:dupl
+func getStorageForAzureBlob(targetSec, overrideSec ConfigSection, tp targetSecType, name string) (*Storage, error) { //nolint:dupl // duplicates minio setup
var storage Storage
storage.Type = StorageType(targetSec.Key("STORAGE_TYPE").String())
if err := targetSec.MapTo(&storage.AzureBlobConfig); err != nil {
diff --git a/modules/ssh/init.go b/modules/ssh/init.go
index fdc11632e2..cfb0d5693a 100644
--- a/modules/ssh/init.go
+++ b/modules/ssh/init.go
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
)
func Init() error {
@@ -23,9 +24,11 @@ func Init() error {
if setting.SSH.StartBuiltinServer {
Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
- log.Info("SSH server started on %s. Cipher list (%v), key exchange algorithms (%v), MACs (%v)",
+ log.Info("SSH server started on %q. Ciphers: %v, key exchange algorithms: %v, MACs: %v",
net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)),
- setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs,
+ util.Iif[any](setting.SSH.ServerCiphers == nil, "default", setting.SSH.ServerCiphers),
+ util.Iif[any](setting.SSH.ServerKeyExchanges == nil, "default", setting.SSH.ServerKeyExchanges),
+ util.Iif[any](setting.SSH.ServerMACs == nil, "default", setting.SSH.ServerMACs),
)
return nil
}
diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
index ff0ad34a0d..3fea4851c7 100644
--- a/modules/ssh/ssh.go
+++ b/modules/ssh/ssh.go
@@ -333,7 +333,7 @@ func sshConnectionFailed(conn net.Conn, err error) {
log.Warn("Failed authentication attempt from %s", conn.RemoteAddr())
}
-// Listen starts a SSH server listens on given port.
+// Listen starts an SSH server listening on given port.
func Listen(host string, port int, ciphers, keyExchanges, macs []string) {
srv := ssh.Server{
Addr: net.JoinHostPort(host, strconv.Itoa(port)),
diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go
index f7c6d10ba0..c68b59a897 100644
--- a/modules/structs/admin_user.go
+++ b/modules/structs/admin_user.go
@@ -8,8 +8,11 @@ import "time"
// CreateUserOption create user options
type CreateUserOption struct {
- SourceID int64 `json:"source_id"`
+ SourceID int64 `json:"source_id"`
+ // identifier of the user, provided by the external authenticator (if configured)
+ // default: empty
LoginName string `json:"login_name"`
+ // username of the user
// required: true
Username string `json:"username" binding:"Required;Username;MaxSize(40)"`
FullName string `json:"full_name" binding:"MaxSize(100)"`
@@ -32,6 +35,8 @@ type CreateUserOption struct {
type EditUserOption struct {
// required: true
SourceID int64 `json:"source_id"`
+ // identifier of the user, provided by the external authenticator (if configured)
+ // default: empty
// required: true
LoginName string `json:"login_name" binding:"Required"`
// swagger:strfmt email
diff --git a/modules/structs/commit_status_test.go b/modules/structs/commit_status_test.go
deleted file mode 100644
index 88e09aadc1..0000000000
--- a/modules/structs/commit_status_test.go
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package structs
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestNoBetterThan(t *testing.T) {
- type args struct {
- css CommitStatusState
- css2 CommitStatusState
- }
- var unExpectedState CommitStatusState
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "success is no better than success",
- args: args{
- css: CommitStatusSuccess,
- css2: CommitStatusSuccess,
- },
- want: true,
- },
- {
- name: "success is no better than pending",
- args: args{
- css: CommitStatusSuccess,
- css2: CommitStatusPending,
- },
- want: false,
- },
- {
- name: "success is no better than failure",
- args: args{
- css: CommitStatusSuccess,
- css2: CommitStatusFailure,
- },
- want: false,
- },
- {
- name: "success is no better than error",
- args: args{
- css: CommitStatusSuccess,
- css2: CommitStatusError,
- },
- want: false,
- },
- {
- name: "pending is no better than success",
- args: args{
- css: CommitStatusPending,
- css2: CommitStatusSuccess,
- },
- want: true,
- },
- {
- name: "pending is no better than pending",
- args: args{
- css: CommitStatusPending,
- css2: CommitStatusPending,
- },
- want: true,
- },
- {
- name: "pending is no better than failure",
- args: args{
- css: CommitStatusPending,
- css2: CommitStatusFailure,
- },
- want: false,
- },
- {
- name: "pending is no better than error",
- args: args{
- css: CommitStatusPending,
- css2: CommitStatusError,
- },
- want: false,
- },
- {
- name: "failure is no better than success",
- args: args{
- css: CommitStatusFailure,
- css2: CommitStatusSuccess,
- },
- want: true,
- },
- {
- name: "failure is no better than pending",
- args: args{
- css: CommitStatusFailure,
- css2: CommitStatusPending,
- },
- want: true,
- },
- {
- name: "failure is no better than failure",
- args: args{
- css: CommitStatusFailure,
- css2: CommitStatusFailure,
- },
- want: true,
- },
- {
- name: "failure is no better than error",
- args: args{
- css: CommitStatusFailure,
- css2: CommitStatusError,
- },
- want: false,
- },
- {
- name: "error is no better than success",
- args: args{
- css: CommitStatusError,
- css2: CommitStatusSuccess,
- },
- want: true,
- },
- {
- name: "error is no better than pending",
- args: args{
- css: CommitStatusError,
- css2: CommitStatusPending,
- },
- want: true,
- },
- {
- name: "error is no better than failure",
- args: args{
- css: CommitStatusError,
- css2: CommitStatusFailure,
- },
- want: true,
- },
- {
- name: "error is no better than error",
- args: args{
- css: CommitStatusError,
- css2: CommitStatusError,
- },
- want: true,
- },
- {
- name: "unExpectedState is no better than success",
- args: args{
- css: unExpectedState,
- css2: CommitStatusSuccess,
- },
- want: false,
- },
- {
- name: "unExpectedState is no better than unExpectedState",
- args: args{
- css: unExpectedState,
- css2: unExpectedState,
- },
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := tt.args.css.NoBetterThan(tt.args.css2)
- assert.Equal(t, tt.want, result)
- })
- }
-}
diff --git a/modules/structs/git_blob.go b/modules/structs/git_blob.go
index 96770cc62e..643b69ed37 100644
--- a/modules/structs/git_blob.go
+++ b/modules/structs/git_blob.go
@@ -10,4 +10,7 @@ type GitBlobResponse struct {
URL string `json:"url"`
SHA string `json:"sha"`
Size int64 `json:"size"`
+
+ LfsOid *string `json:"lfs_oid,omitempty"`
+ LfsSize *int64 `json:"lfs_size,omitempty"`
}
diff --git a/modules/structs/hook.go b/modules/structs/hook.go
index aaa9fbc9d3..ac779a5740 100644
--- a/modules/structs/hook.go
+++ b/modules/structs/hook.go
@@ -71,7 +71,8 @@ type PayloadUser struct {
// Full name of the commit author
Name string `json:"name"`
// swagger:strfmt email
- Email string `json:"email"`
+ Email string `json:"email"`
+ // username of the user
UserName string `json:"username"`
}
@@ -286,6 +287,8 @@ const (
HookIssueReOpened HookIssueAction = "reopened"
// HookIssueEdited edited
HookIssueEdited HookIssueAction = "edited"
+ // HookIssueDeleted is an issue action for deleting an issue
+ HookIssueDeleted HookIssueAction = "deleted"
// HookIssueAssigned assigned
HookIssueAssigned HookIssueAction = "assigned"
// HookIssueUnassigned unassigned
@@ -470,6 +473,22 @@ func (p *CommitStatusPayload) JSONPayload() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}
+// WorkflowRunPayload represents a payload information of workflow run event.
+type WorkflowRunPayload struct {
+ Action string `json:"action"`
+ Workflow *ActionWorkflow `json:"workflow"`
+ WorkflowRun *ActionWorkflowRun `json:"workflow_run"`
+ PullRequest *PullRequest `json:"pull_request,omitempty"`
+ Organization *Organization `json:"organization,omitempty"`
+ Repo *Repository `json:"repository"`
+ Sender *User `json:"sender"`
+}
+
+// JSONPayload implements Payload
+func (p *WorkflowRunPayload) JSONPayload() ([]byte, error) {
+ return json.MarshalIndent(p, "", " ")
+}
+
// WorkflowJobPayload represents a payload information of workflow job event.
type WorkflowJobPayload struct {
Action string `json:"action"`
diff --git a/modules/structs/issue.go b/modules/structs/issue.go
index 6a6b74c34e..df0be8f9ec 100644
--- a/modules/structs/issue.go
+++ b/modules/structs/issue.go
@@ -203,7 +203,7 @@ func (l *IssueTemplateStringSlice) UnmarshalYAML(value *yaml.Node) error {
if err != nil {
return err
}
- for _, v := range strings.Split(str, ",") {
+ for v := range strings.SplitSeq(str, ",") {
if v = strings.TrimSpace(v); v == "" {
continue
}
diff --git a/modules/structs/issue_tracked_time.go b/modules/structs/issue_tracked_time.go
index a3904af80e..befcfb323d 100644
--- a/modules/structs/issue_tracked_time.go
+++ b/modules/structs/issue_tracked_time.go
@@ -14,7 +14,7 @@ type AddTimeOption struct {
Time int64 `json:"time" binding:"Required"`
// swagger:strfmt date-time
Created time.Time `json:"created"`
- // User who spent the time (optional)
+ // username of the user who spent the time working on the issue (optional)
User string `json:"user_name"`
}
@@ -26,7 +26,8 @@ type TrackedTime struct {
// Time in seconds
Time int64 `json:"time"`
// deprecated (only for backwards compatibility)
- UserID int64 `json:"user_id"`
+ UserID int64 `json:"user_id"`
+ // username of the user
UserName string `json:"user_name"`
// deprecated (only for backwards compatibility)
IssueID int64 `json:"issue_id"`
diff --git a/modules/structs/org.go b/modules/structs/org.go
index f93b3b6493..33b45c6344 100644
--- a/modules/structs/org.go
+++ b/modules/structs/org.go
@@ -15,6 +15,7 @@ type Organization struct {
Location string `json:"location"`
Visibility string `json:"visibility"`
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
+ // username of the organization
// deprecated
UserName string `json:"username"`
}
@@ -30,6 +31,7 @@ type OrganizationPermissions struct {
// CreateOrgOption options for creating an organization
type CreateOrgOption struct {
+ // username of the organization
// required: true
UserName string `json:"username" binding:"Required;Username;MaxSize(40)"`
FullName string `json:"full_name" binding:"MaxSize(100)"`
diff --git a/modules/structs/release.go b/modules/structs/release.go
index c7378645c2..fac86ca7a2 100644
--- a/modules/structs/release.go
+++ b/modules/structs/release.go
@@ -33,6 +33,7 @@ type Release struct {
type CreateReleaseOption struct {
// required: true
TagName string `json:"tag_name" binding:"Required"`
+ TagMessage string `json:"tag_message"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index fb784bd8b3..abc8076387 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -101,6 +101,8 @@ type Repository struct {
AllowSquash bool `json:"allow_squash_merge"`
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"`
AllowRebaseUpdate bool `json:"allow_rebase_update"`
+ AllowManualMerge bool `json:"allow_manual_merge"`
+ AutodetectManualMerge bool `json:"autodetect_manual_merge"`
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
DefaultMergeStyle string `json:"default_merge_style"`
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
@@ -111,7 +113,7 @@ type Repository struct {
// enum: sha1,sha256
ObjectFormatName string `json:"object_format_name"`
// swagger:strfmt date-time
- MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
+ MirrorUpdated time.Time `json:"mirror_updated"`
RepoTransfer *RepoTransfer `json:"repo_transfer"`
Topics []string `json:"topics"`
Licenses []string `json:"licenses"`
@@ -357,7 +359,7 @@ type MigrateRepoOptions struct {
// required: true
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
- // enum: git,github,gitea,gitlab,gogs,onedev,gitbucket,codebase
+ // enum: git,github,gitea,gitlab,gogs,onedev,gitbucket,codebase,codecommit
Service string `json:"service"`
AuthUsername string `json:"auth_username"`
AuthPassword string `json:"auth_password"`
diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go
index 75f8e188dd..ac1c288270 100644
--- a/modules/structs/repo_actions.go
+++ b/modules/structs/repo_actions.go
@@ -57,7 +57,7 @@ type ActionWorkflow struct {
HTMLURL string `json:"html_url"`
BadgeURL string `json:"badge_url"`
// swagger:strfmt date-time
- DeletedAt time.Time `json:"deleted_at,omitempty"`
+ DeletedAt time.Time `json:"deleted_at"`
}
// ActionWorkflowResponse returns a ActionWorkflow
@@ -86,9 +86,39 @@ type ActionArtifact struct {
// ActionWorkflowRun represents a WorkflowRun
type ActionWorkflowRun struct {
- ID int64 `json:"id"`
- RepositoryID int64 `json:"repository_id"`
- HeadSha string `json:"head_sha"`
+ ID int64 `json:"id"`
+ URL string `json:"url"`
+ HTMLURL string `json:"html_url"`
+ DisplayTitle string `json:"display_title"`
+ Path string `json:"path"`
+ Event string `json:"event"`
+ RunAttempt int64 `json:"run_attempt"`
+ RunNumber int64 `json:"run_number"`
+ RepositoryID int64 `json:"repository_id,omitempty"`
+ HeadSha string `json:"head_sha"`
+ HeadBranch string `json:"head_branch,omitempty"`
+ Status string `json:"status"`
+ Actor *User `json:"actor,omitempty"`
+ TriggerActor *User `json:"trigger_actor,omitempty"`
+ Repository *Repository `json:"repository,omitempty"`
+ HeadRepository *Repository `json:"head_repository,omitempty"`
+ Conclusion string `json:"conclusion,omitempty"`
+ // swagger:strfmt date-time
+ StartedAt time.Time `json:"started_at"`
+ // swagger:strfmt date-time
+ CompletedAt time.Time `json:"completed_at"`
+}
+
+// ActionWorkflowRunsResponse returns ActionWorkflowRuns
+type ActionWorkflowRunsResponse struct {
+ Entries []*ActionWorkflowRun `json:"workflow_runs"`
+ TotalCount int64 `json:"total_count"`
+}
+
+// ActionWorkflowJobsResponse returns ActionWorkflowJobs
+type ActionWorkflowJobsResponse struct {
+ Entries []*ActionWorkflowJob `json:"jobs"`
+ TotalCount int64 `json:"total_count"`
}
// ActionArtifactsResponse returns ActionArtifacts
@@ -104,9 +134,9 @@ type ActionWorkflowStep struct {
Status string `json:"status"`
Conclusion string `json:"conclusion,omitempty"`
// swagger:strfmt date-time
- StartedAt time.Time `json:"started_at,omitempty"`
+ StartedAt time.Time `json:"started_at"`
// swagger:strfmt date-time
- CompletedAt time.Time `json:"completed_at,omitempty"`
+ CompletedAt time.Time `json:"completed_at"`
}
// ActionWorkflowJob represents a WorkflowJob
@@ -129,9 +159,9 @@ type ActionWorkflowJob struct {
// swagger:strfmt date-time
CreatedAt time.Time `json:"created_at"`
// swagger:strfmt date-time
- StartedAt time.Time `json:"started_at,omitempty"`
+ StartedAt time.Time `json:"started_at"`
// swagger:strfmt date-time
- CompletedAt time.Time `json:"completed_at,omitempty"`
+ CompletedAt time.Time `json:"completed_at"`
}
// ActionRunnerLabel represents a Runner Label
diff --git a/modules/structs/repo_branch.go b/modules/structs/repo_branch.go
index 55c98d60b9..5416f43b0d 100644
--- a/modules/structs/repo_branch.go
+++ b/modules/structs/repo_branch.go
@@ -136,6 +136,7 @@ type UpdateBranchProtectionPriories struct {
type MergeUpstreamRequest struct {
Branch string `json:"branch"`
+ FfOnly bool `json:"ff_only"`
}
type MergeUpstreamResponse struct {
diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go
index b0e0bd979e..5a86db868b 100644
--- a/modules/structs/repo_file.go
+++ b/modules/structs/repo_file.go
@@ -22,6 +22,23 @@ type FileOptions struct {
Signoff bool `json:"signoff"`
}
+type FileOptionsWithSHA struct {
+ FileOptions
+ // the blob ID (SHA) for the file that already exists, it is required for changing existing files
+ // required: true
+ SHA string `json:"sha" binding:"Required"`
+}
+
+func (f *FileOptions) GetFileOptions() *FileOptions {
+ return f
+}
+
+type FileOptionsInterface interface {
+ GetFileOptions() *FileOptions
+}
+
+var _ FileOptionsInterface = (*FileOptions)(nil)
+
// CreateFileOptions options for creating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type CreateFileOptions struct {
@@ -31,29 +48,16 @@ type CreateFileOptions struct {
ContentBase64 string `json:"content"`
}
-// Branch returns branch name
-func (o *CreateFileOptions) Branch() string {
- return o.FileOptions.BranchName
-}
-
// DeleteFileOptions options for deleting files (used for other File structs below)
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type DeleteFileOptions struct {
- FileOptions
- // sha is the SHA for the file that already exists
- // required: true
- SHA string `json:"sha" binding:"Required"`
-}
-
-// Branch returns branch name
-func (o *DeleteFileOptions) Branch() string {
- return o.FileOptions.BranchName
+ FileOptionsWithSHA
}
// UpdateFileOptions options for updating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type UpdateFileOptions struct {
- DeleteFileOptions
+ FileOptionsWithSHA
// content must be base64 encoded
// required: true
ContentBase64 string `json:"content"`
@@ -61,23 +65,21 @@ type UpdateFileOptions struct {
FromPath string `json:"from_path" binding:"MaxSize(500)"`
}
-// Branch returns branch name
-func (o *UpdateFileOptions) Branch() string {
- return o.FileOptions.BranchName
-}
+// FIXME: there is no LastCommitID in FileOptions, actually it should be an alternative to the SHA in ChangeFileOperation
// ChangeFileOperation for creating, updating or deleting a file
type ChangeFileOperation struct {
- // indicates what to do with the file
+ // indicates what to do with the file: "create" for creating a new file, "update" for updating an existing file,
+ // "upload" for creating or updating a file, "rename" for renaming a file, and "delete" for deleting an existing file.
// required: true
- // enum: create,update,delete
+ // enum: create,update,upload,rename,delete
Operation string `json:"operation" binding:"Required"`
// path to the existing or new file
// required: true
Path string `json:"path" binding:"Required;MaxSize(500)"`
- // new or updated file content, must be base64 encoded
+ // new or updated file content, it must be base64 encoded
ContentBase64 string `json:"content"`
- // sha is the SHA for the file that already exists, required for update or delete
+ // the blob ID (SHA) for the file that already exists, required for changing existing files
SHA string `json:"sha"`
// old path of the file to move
FromPath string `json:"from_path"`
@@ -92,20 +94,10 @@ type ChangeFilesOptions struct {
Files []*ChangeFileOperation `json:"files" binding:"Required"`
}
-// Branch returns branch name
-func (o *ChangeFilesOptions) Branch() string {
- return o.FileOptions.BranchName
-}
-
-// FileOptionInterface provides a unified interface for the different file options
-type FileOptionInterface interface {
- Branch() string
-}
-
// ApplyDiffPatchFileOptions options for applying a diff patch
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type ApplyDiffPatchFileOptions struct {
- DeleteFileOptions
+ FileOptions
// required: true
Content string `json:"content"`
}
@@ -117,16 +109,24 @@ type FileLinksResponse struct {
HTMLURL *string `json:"html"`
}
+type ContentsExtResponse struct {
+ FileContents *ContentsResponse `json:"file_contents,omitempty"`
+ DirContents []*ContentsResponse `json:"dir_contents,omitempty"`
+}
+
// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content
type ContentsResponse struct {
- Name string `json:"name"`
- Path string `json:"path"`
- SHA string `json:"sha"`
- LastCommitSHA string `json:"last_commit_sha"`
+ Name string `json:"name"`
+ Path string `json:"path"`
+ SHA string `json:"sha"`
+
+ LastCommitSHA *string `json:"last_commit_sha,omitempty"`
// swagger:strfmt date-time
- LastCommitterDate time.Time `json:"last_committer_date"`
+ LastCommitterDate *time.Time `json:"last_committer_date,omitempty"`
// swagger:strfmt date-time
- LastAuthorDate time.Time `json:"last_author_date"`
+ LastAuthorDate *time.Time `json:"last_author_date,omitempty"`
+ LastCommitMessage *string `json:"last_commit_message,omitempty"`
+
// `type` will be `file`, `dir`, `symlink`, or `submodule`
Type string `json:"type"`
Size int64 `json:"size"`
@@ -143,6 +143,9 @@ type ContentsResponse struct {
// `submodule_git_url` is populated when `type` is `submodule`, otherwise null
SubmoduleGitURL *string `json:"submodule_git_url"`
Links *FileLinksResponse `json:"_links"`
+
+ LfsOid *string `json:"lfs_oid,omitempty"`
+ LfsSize *int64 `json:"lfs_size,omitempty"`
}
// FileCommitResponse contains information generated from a Git commit for a repo's file.
diff --git a/modules/structs/status.go b/modules/structs/status.go
index c1d8b902ec..a9779541ff 100644
--- a/modules/structs/status.go
+++ b/modules/structs/status.go
@@ -5,17 +5,19 @@ package structs
import (
"time"
+
+ "code.gitea.io/gitea/modules/commitstatus"
)
// CommitStatus holds a single status of a single Commit
type CommitStatus struct {
- ID int64 `json:"id"`
- State CommitStatusState `json:"status"`
- TargetURL string `json:"target_url"`
- Description string `json:"description"`
- URL string `json:"url"`
- Context string `json:"context"`
- Creator *User `json:"creator"`
+ ID int64 `json:"id"`
+ State commitstatus.CommitStatusState `json:"status"`
+ TargetURL string `json:"target_url"`
+ Description string `json:"description"`
+ URL string `json:"url"`
+ Context string `json:"context"`
+ Creator *User `json:"creator"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
@@ -24,19 +26,19 @@ type CommitStatus struct {
// CombinedStatus holds the combined state of several statuses for a single commit
type CombinedStatus struct {
- State CommitStatusState `json:"state"`
- SHA string `json:"sha"`
- TotalCount int `json:"total_count"`
- Statuses []*CommitStatus `json:"statuses"`
- Repository *Repository `json:"repository"`
- CommitURL string `json:"commit_url"`
- URL string `json:"url"`
+ State commitstatus.CommitStatusState `json:"state"`
+ SHA string `json:"sha"`
+ TotalCount int `json:"total_count"`
+ Statuses []*CommitStatus `json:"statuses"`
+ Repository *Repository `json:"repository"`
+ CommitURL string `json:"commit_url"`
+ URL string `json:"url"`
}
// CreateStatusOption holds the information needed to create a new CommitStatus for a Commit
type CreateStatusOption struct {
- State CommitStatusState `json:"state"`
- TargetURL string `json:"target_url"`
- Description string `json:"description"`
- Context string `json:"context"`
+ State commitstatus.CommitStatusState `json:"state"`
+ TargetURL string `json:"target_url"`
+ Description string `json:"description"`
+ Context string `json:"context"`
}
diff --git a/modules/structs/user.go b/modules/structs/user.go
index 5ed677f239..89349cda2c 100644
--- a/modules/structs/user.go
+++ b/modules/structs/user.go
@@ -15,9 +15,9 @@ import (
type User struct {
// the user's id
ID int64 `json:"id"`
- // the user's username
+ // login of the user, same as `username`
UserName string `json:"login"`
- // the user's authentication sign-in name.
+ // identifier of the user, provided by the external authenticator (if configured)
// default: empty
LoginName string `json:"login_name"`
// The ID of the user's Authentication Source
@@ -35,9 +35,9 @@ type User struct {
// Is the user an administrator
IsAdmin bool `json:"is_admin"`
// swagger:strfmt date-time
- LastLogin time.Time `json:"last_login,omitempty"`
+ LastLogin time.Time `json:"last_login"`
// swagger:strfmt date-time
- Created time.Time `json:"created,omitempty"`
+ Created time.Time `json:"created"`
// Is user restricted
Restricted bool `json:"restricted"`
// Is user active
diff --git a/modules/structs/user_email.go b/modules/structs/user_email.go
index 9319667e8f..01895a0058 100644
--- a/modules/structs/user_email.go
+++ b/modules/structs/user_email.go
@@ -11,6 +11,7 @@ type Email struct {
Verified bool `json:"verified"`
Primary bool `json:"primary"`
UserID int64 `json:"user_id"`
+ // username of the user
UserName string `json:"username"`
}
diff --git a/modules/structs/user_gpgkey.go b/modules/structs/user_gpgkey.go
index ff9b0aea1d..deae70de33 100644
--- a/modules/structs/user_gpgkey.go
+++ b/modules/structs/user_gpgkey.go
@@ -21,9 +21,9 @@ type GPGKey struct {
CanCertify bool `json:"can_certify"`
Verified bool `json:"verified"`
// swagger:strfmt date-time
- Created time.Time `json:"created_at,omitempty"`
+ Created time.Time `json:"created_at"`
// swagger:strfmt date-time
- Expires time.Time `json:"expires_at,omitempty"`
+ Expires time.Time `json:"expires_at"`
}
// GPGKeyEmail an email attached to a GPGKey
diff --git a/modules/structs/user_key.go b/modules/structs/user_key.go
index c4c41207e1..16225a852a 100644
--- a/modules/structs/user_key.go
+++ b/modules/structs/user_key.go
@@ -15,8 +15,8 @@ type PublicKey struct {
Title string `json:"title,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
// swagger:strfmt date-time
- Created time.Time `json:"created_at,omitempty"`
- Updated time.Time `json:"last_used_at,omitempty"`
+ Created time.Time `json:"created_at"`
+ Updated time.Time `json:"last_used_at"`
Owner *User `json:"user,omitempty"`
ReadOnly bool `json:"read_only,omitempty"`
KeyType string `json:"key_type,omitempty"`
diff --git a/modules/templates/eval/eval_test.go b/modules/templates/eval/eval_test.go
index c9e514b5eb..f956f6cbdf 100644
--- a/modules/templates/eval/eval_test.go
+++ b/modules/templates/eval/eval_test.go
@@ -12,7 +12,7 @@ import (
)
func tokens(s string) (a []any) {
- for _, v := range strings.Fields(s) {
+ for v := range strings.FieldsSeq(s) {
a = append(a, v)
}
return a
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index c9d93e089c..ff3f7cfda1 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -6,7 +6,6 @@ package templates
import (
"fmt"
- "html"
"html/template"
"net/url"
"strconv"
@@ -38,9 +37,7 @@ func NewFuncMap() template.FuncMap {
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
"Iif": iif,
"Eval": evalTokens,
- "SafeHTML": safeHTML,
"HTMLFormat": htmlFormat,
- "HTMLEscape": htmlEscape,
"QueryEscape": queryEscape,
"QueryBuild": QueryBuild,
"JSEscape": jsEscapeSafe,
@@ -162,49 +159,12 @@ func NewFuncMap() template.FuncMap {
"FilenameIsImage": filenameIsImage,
"TabSizeClass": tabSizeClass,
-
- // for backward compatibility only, do not use them anymore
- "TimeSince": timeSinceLegacy,
- "TimeSinceUnix": timeSinceLegacy,
- "DateTime": dateTimeLegacy,
-
- "RenderEmoji": renderEmojiLegacy,
- "RenderLabel": renderLabelLegacy,
- "RenderLabels": renderLabelsLegacy,
- "RenderIssueTitle": renderIssueTitleLegacy,
-
- "RenderMarkdownToHtml": renderMarkdownToHtmlLegacy,
-
- "RenderCommitMessage": renderCommitMessageLegacy,
- "RenderCommitMessageLinkSubject": renderCommitMessageLinkSubjectLegacy,
- "RenderCommitBody": renderCommitBodyLegacy,
- }
-}
-
-// safeHTML render raw as HTML
-func safeHTML(s any) template.HTML {
- switch v := s.(type) {
- case string:
- return template.HTML(v)
- case template.HTML:
- return v
}
- panic(fmt.Sprintf("unexpected type %T", s))
}
-// SanitizeHTML sanitizes the input by pre-defined markdown rules
+// SanitizeHTML sanitizes the input by default sanitization rules.
func SanitizeHTML(s string) template.HTML {
- return template.HTML(markup.Sanitize(s))
-}
-
-func htmlEscape(s any) template.HTML {
- switch v := s.(type) {
- case string:
- return template.HTML(html.EscapeString(v))
- case template.HTML:
- return v
- }
- panic(fmt.Sprintf("unexpected type %T", s))
+ return markup.Sanitize(s)
}
func htmlFormat(s any, args ...any) template.HTML {
@@ -367,7 +327,3 @@ func QueryBuild(a ...any) template.URL {
}
return template.URL(s)
}
-
-func panicIfDevOrTesting() {
- setting.PanicInDevOrTesting("legacy template functions are for backward compatibility only, do not use them in new code")
-}
diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go
index 529284f7e8..8073a6e5f5 100644
--- a/modules/templates/htmlrenderer.go
+++ b/modules/templates/htmlrenderer.go
@@ -42,7 +42,7 @@ var (
var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors")
-func (h *HTMLRender) HTML(w io.Writer, status int, tplName TplName, data any, ctx context.Context) error { //nolint:revive
+func (h *HTMLRender) HTML(w io.Writer, status int, tplName TplName, data any, ctx context.Context) error { //nolint:revive // we don't use ctx, only pass it to the template executor
name := string(tplName)
if respWriter, ok := w.(http.ResponseWriter); ok {
if respWriter.Header().Get("Content-Type") == "" {
@@ -57,7 +57,7 @@ func (h *HTMLRender) HTML(w io.Writer, status int, tplName TplName, data any, ct
return t.Execute(w, data)
}
-func (h *HTMLRender) TemplateLookup(name string, ctx context.Context) (TemplateExecutor, error) { //nolint:revive
+func (h *HTMLRender) TemplateLookup(name string, ctx context.Context) (TemplateExecutor, error) { //nolint:revive // we don't use ctx, only pass it to the template executor
tmpls := h.templates.Load()
if tmpls == nil {
return nil, ErrTemplateNotInitialized
@@ -251,7 +251,7 @@ func extractErrorLine(code []byte, lineNum, posNum int, target string) string {
b := bufio.NewReader(bytes.NewReader(code))
var line []byte
var err error
- for i := 0; i < lineNum; i++ {
+ for i := range lineNum {
if line, err = b.ReadBytes('\n'); err != nil {
if i == lineNum-1 && errors.Is(err, io.EOF) {
err = nil
diff --git a/modules/templates/scopedtmpl/scopedtmpl.go b/modules/templates/scopedtmpl/scopedtmpl.go
index 2722ba97a2..34e8b9ad70 100644
--- a/modules/templates/scopedtmpl/scopedtmpl.go
+++ b/modules/templates/scopedtmpl/scopedtmpl.go
@@ -7,6 +7,7 @@ import (
"fmt"
"html/template"
"io"
+ "maps"
"reflect"
"sync"
texttemplate "text/template"
@@ -40,9 +41,7 @@ func (t *ScopedTemplate) Funcs(funcMap template.FuncMap) {
panic("cannot add new functions to frozen template set")
}
t.all.Funcs(funcMap)
- for k, v := range funcMap {
- t.parseFuncs[k] = v
- }
+ maps.Copy(t.parseFuncs, funcMap)
}
func (t *ScopedTemplate) New(name string) *template.Template {
@@ -103,31 +102,28 @@ func escapeTemplate(t *template.Template) error {
return nil
}
-//nolint:unused
type htmlTemplate struct {
- escapeErr error
- text *texttemplate.Template
+ _/*escapeErr*/ error
+ text *texttemplate.Template
}
-//nolint:unused
type textTemplateCommon struct {
- tmpl map[string]*template.Template // Map from name to defined templates.
- muTmpl sync.RWMutex // protects tmpl
- option struct {
+ _/*tmpl*/ map[string]*template.Template
+ _/*muTmpl*/ sync.RWMutex
+ _/*option*/ struct {
missingKey int
}
- muFuncs sync.RWMutex // protects parseFuncs and execFuncs
- parseFuncs texttemplate.FuncMap
- execFuncs map[string]reflect.Value
+ muFuncs sync.RWMutex
+ _/*parseFuncs*/ texttemplate.FuncMap
+ execFuncs map[string]reflect.Value
}
-//nolint:unused
type textTemplate struct {
- name string
+ _/*name*/ string
*parse.Tree
*textTemplateCommon
- leftDelim string
- rightDelim string
+ _/*leftDelim*/ string
+ _/*rightDelim*/ string
}
func ptr[T, P any](ptr *P) *T {
@@ -159,9 +155,7 @@ func newScopedTemplateSet(all *template.Template, name string) (*scopedTemplateS
textTmplPtr.muFuncs.Lock()
ts.execFuncs = map[string]reflect.Value{}
- for k, v := range textTmplPtr.execFuncs {
- ts.execFuncs[k] = v
- }
+ maps.Copy(ts.execFuncs, textTmplPtr.execFuncs)
textTmplPtr.muFuncs.Unlock()
var collectTemplates func(nodes []parse.Node)
@@ -220,9 +214,7 @@ func (ts *scopedTemplateSet) newExecutor(funcMap map[string]any) TemplateExecuto
tmpl := texttemplate.New("")
tmplPtr := ptr[textTemplate](tmpl)
tmplPtr.execFuncs = map[string]reflect.Value{}
- for k, v := range ts.execFuncs {
- tmplPtr.execFuncs[k] = v
- }
+ maps.Copy(tmplPtr.execFuncs, ts.execFuncs)
if funcMap != nil {
tmpl.Funcs(funcMap)
}
diff --git a/modules/templates/static.go b/modules/templates/static.go
deleted file mode 100644
index b5a7e561ec..0000000000
--- a/modules/templates/static.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2016 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-//go:build bindata
-
-package templates
-
-import (
- "time"
-
- "code.gitea.io/gitea/modules/assetfs"
- "code.gitea.io/gitea/modules/timeutil"
-)
-
-// GlobalModTime provide a global mod time for embedded asset files
-func GlobalModTime(filename string) time.Time {
- return timeutil.GetExecutableModTime()
-}
-
-func BuiltinAssets() *assetfs.Layer {
- return assetfs.Bindata("builtin(bindata)", Assets)
-}
diff --git a/modules/templates/templates_bindata.go b/modules/templates/templates_bindata.go
index 6f1d3cf539..a919591ecf 100644
--- a/modules/templates/templates_bindata.go
+++ b/modules/templates/templates_bindata.go
@@ -3,6 +3,21 @@
//go:build bindata
+//go:generate go run ../../build/generate-bindata.go ../../templates bindata.dat
+
package templates
-//go:generate go run ../../build/generate-bindata.go ../../templates templates bindata.go true
+import (
+ "sync"
+
+ _ "embed"
+
+ "code.gitea.io/gitea/modules/assetfs"
+)
+
+//go:embed bindata.dat
+var bindata []byte
+
+var BuiltinAssets = sync.OnceValue(func() *assetfs.Layer {
+ return assetfs.Bindata("builtin(bindata)", assetfs.NewEmbeddedFS(bindata))
+})
diff --git a/modules/templates/dynamic.go b/modules/templates/templates_dynamic.go
index e1babd83c9..e1babd83c9 100644
--- a/modules/templates/dynamic.go
+++ b/modules/templates/templates_dynamic.go
diff --git a/modules/templates/util_date_legacy.go b/modules/templates/util_date_legacy.go
deleted file mode 100644
index ceefb00447..0000000000
--- a/modules/templates/util_date_legacy.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package templates
-
-import (
- "html/template"
-
- "code.gitea.io/gitea/modules/translation"
-)
-
-func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
- panicIfDevOrTesting()
- if s, ok := datetime.(string); ok {
- datetime = parseLegacy(s)
- }
- return dateTimeFormat(format, datetime)
-}
-
-func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
- panicIfDevOrTesting()
- return TimeSince(time)
-}
diff --git a/modules/templates/util_date_test.go b/modules/templates/util_date_test.go
index f3a2409a9f..2c1f2d242e 100644
--- a/modules/templates/util_date_test.go
+++ b/modules/templates/util_date_test.go
@@ -17,12 +17,12 @@ import (
func TestDateTime(t *testing.T) {
testTz, _ := time.LoadLocation("America/New_York")
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
+ defer test.MockVariableValue(&setting.IsProd, true)()
defer test.MockVariableValue(&setting.IsInTesting, false)()
du := NewDateUtils()
refTimeStr := "2018-01-01T00:00:00Z"
- refDateStr := "2018-01-01"
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
refTimeStamp := timeutil.TimeStamp(refTime.Unix())
@@ -31,18 +31,9 @@ func TestDateTime(t *testing.T) {
assert.EqualValues(t, "-", du.AbsoluteShort(time.Time{}))
assert.EqualValues(t, "-", du.AbsoluteShort(timeutil.TimeStamp(0)))
- actual := dateTimeLegacy("short", "invalid")
- assert.EqualValues(t, `-`, actual)
-
- actual = dateTimeLegacy("short", refTimeStr)
- assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
-
- actual = du.AbsoluteShort(refTime)
+ actual := du.AbsoluteShort(refTime)
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
- actual = dateTimeLegacy("short", refDateStr)
- assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00-05:00">2018-01-01</absolute-date>`, actual)
-
actual = du.AbsoluteShort(refTimeStamp)
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual)
@@ -53,6 +44,7 @@ func TestDateTime(t *testing.T) {
func TestTimeSince(t *testing.T) {
testTz, _ := time.LoadLocation("America/New_York")
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
+ defer test.MockVariableValue(&setting.IsProd, true)()
defer test.MockVariableValue(&setting.IsInTesting, false)()
du := NewDateUtils()
@@ -67,6 +59,6 @@ func TestTimeSince(t *testing.T) {
actual = timeSinceTo(&refTime, time.Time{})
assert.EqualValues(t, `<relative-time prefix="" tense="future" datetime="2018-01-01T00:00:00Z" data-tooltip-content data-tooltip-interactive="true">2018-01-01 00:00:00 +00:00</relative-time>`, actual)
- actual = timeSinceLegacy(timeutil.TimeStampNano(refTime.UnixNano()), nil)
+ actual = du.TimeSince(timeutil.TimeStampNano(refTime.UnixNano()))
assert.EqualValues(t, `<relative-time prefix="" tense="past" datetime="2017-12-31T19:00:00-05:00" data-tooltip-content data-tooltip-interactive="true">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
}
diff --git a/modules/templates/util_json.go b/modules/templates/util_json.go
index 71a4e23d36..29a04290fa 100644
--- a/modules/templates/util_json.go
+++ b/modules/templates/util_json.go
@@ -9,11 +9,11 @@ import (
"code.gitea.io/gitea/modules/json"
)
-type JsonUtils struct{} //nolint:revive
+type JsonUtils struct{} //nolint:revive // variable naming triggers on Json, wants JSON
var jsonUtils = JsonUtils{}
-func NewJsonUtils() *JsonUtils { //nolint:revive
+func NewJsonUtils() *JsonUtils { //nolint:revive // variable naming triggers on Json, wants JSON
return &jsonUtils
}
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
index 521233db40..1056c42643 100644
--- a/modules/templates/util_render.go
+++ b/modules/templates/util_render.go
@@ -14,6 +14,8 @@ import (
"unicode"
issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/renderhelper"
+ "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/log"
@@ -34,25 +36,25 @@ func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils {
}
// RenderCommitMessage renders commit message with XSS-safe and special links.
-func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string) template.HTML {
+func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) template.HTML {
cleanMsg := template.HTMLEscapeString(msg)
- // we can safely assume that it will not return any error, since there
- // shouldn't be any special HTML.
- fullMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), cleanMsg)
+ // we can safely assume that it will not return any error, since there shouldn't be any special HTML.
+ // "repo" can be nil when rendering commit messages for deleted repositories in a user's dashboard feed.
+ fullMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), cleanMsg)
if err != nil {
log.Error("PostProcessCommitMessage: %v", err)
return ""
}
msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
if len(msgLines) == 0 {
- return template.HTML("")
+ return ""
}
return renderCodeBlock(template.HTML(msgLines[0]))
}
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
// the provided default url, handling for special links without email to links.
-func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, metas map[string]string) template.HTML {
+func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, repo *repo.Repository) template.HTML {
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
lineEnd := strings.IndexByte(msgLine, '\n')
if lineEnd > 0 {
@@ -63,9 +65,8 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
return ""
}
- // we can safely assume that it will not return any error, since there
- // shouldn't be any special HTML.
- renderedMessage, err := markup.PostProcessCommitMessageSubject(markup.NewRenderContext(ut.ctx).WithMetas(metas), urlDefault, template.HTMLEscapeString(msgLine))
+ // we can safely assume that it will not return any error, since there shouldn't be any special HTML.
+ renderedMessage, err := markup.PostProcessCommitMessageSubject(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), urlDefault, template.HTMLEscapeString(msgLine))
if err != nil {
log.Error("PostProcessCommitMessageSubject: %v", err)
return ""
@@ -74,7 +75,7 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
}
// RenderCommitBody extracts the body of a commit message without its title.
-func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) template.HTML {
+func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) template.HTML {
msgLine := strings.TrimSpace(msg)
lineEnd := strings.IndexByte(msgLine, '\n')
if lineEnd > 0 {
@@ -87,7 +88,7 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
return ""
}
- renderedMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(msgLine))
+ renderedMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), template.HTMLEscapeString(msgLine))
if err != nil {
log.Error("PostProcessCommitMessage: %v", err)
return ""
@@ -105,8 +106,8 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
}
// RenderIssueTitle renders issue/pull title with defined post processors
-func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
- renderedText, err := markup.PostProcessIssueTitle(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(text))
+func (ut *RenderUtils) RenderIssueTitle(text string, repo *repo.Repository) template.HTML {
+ renderedText, err := markup.PostProcessIssueTitle(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), template.HTMLEscapeString(text))
if err != nil {
log.Error("PostProcessIssueTitle: %v", err)
return ""
@@ -121,8 +122,23 @@ func (ut *RenderUtils) RenderIssueSimpleTitle(text string) template.HTML {
return ret
}
-// RenderLabel renders a label
+func (ut *RenderUtils) RenderLabelWithLink(label *issues_model.Label, link any) template.HTML {
+ var attrHref template.HTML
+ switch link.(type) {
+ case template.URL, string:
+ attrHref = htmlutil.HTMLFormat(`href="%s"`, link)
+ default:
+ panic(fmt.Sprintf("unexpected type %T for link", link))
+ }
+ return ut.renderLabelWithTag(label, "a", attrHref)
+}
+
func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
+ return ut.renderLabelWithTag(label, "span", "")
+}
+
+// RenderLabel renders a label
+func (ut *RenderUtils) renderLabelWithTag(label *issues_model.Label, tagName, tagAttrs template.HTML) template.HTML {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
var extraCSSClasses string
textColor := util.ContrastColor(label.Color)
@@ -136,8 +152,8 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
if labelScope == "" {
// Regular label
- return htmlutil.HTMLFormat(`<div class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s">%s</div>`,
- extraCSSClasses, textColor, label.Color, descriptionText, ut.RenderEmoji(label.Name))
+ return htmlutil.HTMLFormat(`<%s %s class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s"><span class="gt-ellipsis">%s</span></%s>`,
+ tagName, tagAttrs, extraCSSClasses, textColor, label.Color, descriptionText, ut.RenderEmoji(label.Name), tagName)
}
// Scoped label
@@ -151,7 +167,7 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
// Ensure we add the same amount of contrast also near 0 and 1.
darken := contrast + math.Max(luminance+contrast-1.0, 0.0)
lighten := contrast + math.Max(contrast-luminance, 0.0)
- // Compute factor to keep RGB values proportional.
+ // Compute the factor to keep RGB values proportional.
darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0)
lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0)
@@ -172,26 +188,29 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
if label.ExclusiveOrder > 0 {
// <scope> | <label> | <order>
- return htmlutil.HTMLFormat(`<span class="ui label %s scope-parent" data-tooltip-content title="%s">`+
+ return htmlutil.HTMLFormat(`<%s %s class="ui label %s scope-parent" data-tooltip-content title="%s">`+
`<div class="ui label scope-left" style="color: %s !important; background-color: %s !important">%s</div>`+
`<div class="ui label scope-middle" style="color: %s !important; background-color: %s !important">%s</div>`+
`<div class="ui label scope-right">%d</div>`+
- `</span>`,
+ `</%s>`,
+ tagName, tagAttrs,
extraCSSClasses, descriptionText,
textColor, scopeColor, scopeHTML,
textColor, itemColor, itemHTML,
- label.ExclusiveOrder)
+ label.ExclusiveOrder,
+ tagName)
}
// <scope> | <label>
- return htmlutil.HTMLFormat(`<span class="ui label %s scope-parent" data-tooltip-content title="%s">`+
+ return htmlutil.HTMLFormat(`<%s %s class="ui label %s scope-parent" data-tooltip-content title="%s">`+
`<div class="ui label scope-left" style="color: %s !important; background-color: %s !important">%s</div>`+
`<div class="ui label scope-right" style="color: %s !important; background-color: %s !important">%s</div>`+
- `</span>`,
+ `</%s>`,
+ tagName, tagAttrs,
extraCSSClasses, descriptionText,
textColor, scopeColor, scopeHTML,
textColor, itemColor, itemHTML,
- )
+ tagName)
}
// RenderEmoji renders html text with emoji post processors
@@ -217,7 +236,7 @@ func reactionToEmoji(reaction string) template.HTML {
return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
}
-func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
+func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive // variable naming triggers on Html, wants HTML
output, err := markdown.RenderString(markup.NewRenderContext(ut.ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), input)
if err != nil {
log.Error("RenderString: %v", err)
@@ -234,7 +253,8 @@ func (ut *RenderUtils) RenderLabels(labels []*issues_model.Label, repoLink strin
if label == nil {
continue
}
- htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, ut.RenderLabel(label))
+ link := fmt.Sprintf("%s?labels=%d", baseLink, label.ID)
+ htmlCode += string(ut.RenderLabelWithLink(label, template.URL(link)))
}
htmlCode += "</span>"
return template.HTML(htmlCode)
diff --git a/modules/templates/util_render_legacy.go b/modules/templates/util_render_legacy.go
deleted file mode 100644
index 8f7b84c83d..0000000000
--- a/modules/templates/util_render_legacy.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package templates
-
-import (
- "context"
- "html/template"
-
- issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/reqctx"
- "code.gitea.io/gitea/modules/translation"
-)
-
-func renderEmojiLegacy(ctx context.Context, text string) template.HTML {
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderEmoji(text)
-}
-
-func renderLabelLegacy(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderLabel(label)
-}
-
-func renderLabelsLegacy(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderLabels(labels, repoLink, issue)
-}
-
-func renderMarkdownToHtmlLegacy(ctx context.Context, input string) template.HTML { //nolint:revive
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).MarkdownToHtml(input)
-}
-
-func renderCommitMessageLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessage(msg, metas)
-}
-
-func renderCommitMessageLinkSubjectLegacy(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessageLinkSubject(msg, urlDefault, metas)
-}
-
-func renderIssueTitleLegacy(ctx context.Context, text string, metas map[string]string) template.HTML {
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderIssueTitle(text, metas)
-}
-
-func renderCommitBodyLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
- panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitBody(msg, metas)
-}
diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go
index 460b9dc190..5c37f084df 100644
--- a/modules/templates/util_render_test.go
+++ b/modules/templates/util_render_test.go
@@ -11,11 +11,11 @@ import (
"testing"
"code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/reqctx"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
@@ -47,19 +47,8 @@ mail@domain.com
return strings.ReplaceAll(s, "<SPACE>", " ")
}
-var testMetas = map[string]string{
- "user": "user13",
- "repo": "repo11",
- "repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
- "markdownNewLineHardBreak": "true",
- "markupAllowShortIssuePattern": "true",
-}
-
func TestMain(m *testing.M) {
- unittest.InitSettingsForTesting()
- if err := git.InitSimple(context.Background()); err != nil {
- log.Fatal("git init failed, err: %v", err)
- }
+ setting.Markdown.RenderOptionsComment.ShortIssuePattern = true
markup.Init(&markup.RenderHelperFuncs{
IsUsernameMentionable: func(ctx context.Context, username string) bool {
return username == "mention-user"
@@ -74,46 +63,52 @@ func newTestRenderUtils(t *testing.T) *RenderUtils {
return NewRenderUtils(ctx)
}
-func TestRenderCommitBody(t *testing.T) {
- defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
- type args struct {
- msg string
+func TestRenderRepoComment(t *testing.T) {
+ mockRepo := &repo.Repository{
+ ID: 1, OwnerName: "user13", Name: "repo11",
+ Owner: &user_model.User{ID: 13, Name: "user13"},
+ Units: []*repo.RepoUnit{},
}
- tests := []struct {
- name string
- args args
- want template.HTML
- }{
- {
- name: "multiple lines",
- args: args{
- msg: "first line\nsecond line",
+ t.Run("RenderCommitBody", func(t *testing.T) {
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
+ type args struct {
+ msg string
+ }
+ tests := []struct {
+ name string
+ args args
+ want template.HTML
+ }{
+ {
+ name: "multiple lines",
+ args: args{
+ msg: "first line\nsecond line",
+ },
+ want: "second line",
},
- want: "second line",
- },
- {
- name: "multiple lines with leading newlines",
- args: args{
- msg: "\n\n\n\nfirst line\nsecond line",
+ {
+ name: "multiple lines with leading newlines",
+ args: args{
+ msg: "\n\n\n\nfirst line\nsecond line",
+ },
+ want: "second line",
},
- want: "second line",
- },
- {
- name: "multiple lines with trailing newlines",
- args: args{
- msg: "first line\nsecond line\n\n\n",
+ {
+ name: "multiple lines with trailing newlines",
+ args: args{
+ msg: "first line\nsecond line\n\n\n",
+ },
+ want: "second line",
},
- want: "second line",
- },
- }
- ut := newTestRenderUtils(t)
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, nil), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
- })
- }
-
- expected := `/just/a/path.bin
+ }
+ ut := newTestRenderUtils(t)
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
+ })
+ }
+
+ expected := `/just/a/path.bin
<a href="https://example.com/file.bin">https://example.com/file.bin</a>
[local link](file.bin)
[remote link](<a href="https://example.com">https://example.com</a>)
@@ -132,22 +127,22 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<a href="/mention-user">@mention-user</a> test
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space`
- assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), testMetas)))
-}
+ assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), mockRepo)))
+ })
-func TestRenderCommitMessage(t *testing.T) {
- expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
- assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), testMetas))
-}
+ t.Run("RenderCommitMessage", func(t *testing.T) {
+ expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
+ assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), mockRepo))
+ })
-func TestRenderCommitMessageLinkSubject(t *testing.T) {
- expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
- assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
-}
+ t.Run("RenderCommitMessageLinkSubject", func(t *testing.T) {
+ expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
+ assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", mockRepo))
+ })
-func TestRenderIssueTitle(t *testing.T) {
- defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
- expected := ` space @mention-user<SPACE><SPACE>
+ t.Run("RenderIssueTitle", func(t *testing.T) {
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
+ expected := ` space @mention-user<SPACE><SPACE>
/just/a/path.bin
https://example.com/file.bin
[local link](file.bin)
@@ -168,8 +163,9 @@ mail@domain.com
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space<SPACE><SPACE>
`
- expected = strings.ReplaceAll(expected, "<SPACE>", " ")
- assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), testMetas)))
+ expected = strings.ReplaceAll(expected, "<SPACE>", " ")
+ assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), mockRepo)))
+ })
}
func TestRenderMarkdownToHtml(t *testing.T) {
@@ -209,6 +205,17 @@ func TestRenderLabels(t *testing.T) {
issue = &issues.Issue{IsPull: true}
expected = `/owner/repo/pulls?labels=123`
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)
+
+ expectedLabel := `<a href="&lt;&gt;" class="ui label " style="color: #fff !important; background-color: label-color !important;" data-tooltip-content title=""><span class="gt-ellipsis">label-name</span></a>`
+ assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, "<>")))
+ assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, template.URL("<>"))))
+
+ label = &issues.Label{ID: 123, Name: "</>", Exclusive: true}
+ expectedLabel = `<a href="" class="ui label scope-parent" data-tooltip-content title=""><div class="ui label scope-left" style="color: #fff !important; background-color: #000000 !important">&lt;</div><div class="ui label scope-right" style="color: #fff !important; background-color: #000000 !important">&gt;</div></a>`
+ assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, "")))
+ label = &issues.Label{ID: 123, Name: "</>", Exclusive: true, ExclusiveOrder: 1}
+ expectedLabel = `<a href="" class="ui label scope-parent" data-tooltip-content title=""><div class="ui label scope-left" style="color: #fff !important; background-color: #000000 !important">&lt;</div><div class="ui label scope-middle" style="color: #fff !important; background-color: #000000 !important">&gt;</div><div class="ui label scope-right">1</div></a>`
+ assert.Equal(t, expectedLabel, string(ut.RenderLabelWithLink(label, "")))
}
func TestUserMention(t *testing.T) {
diff --git a/modules/test/utils.go b/modules/test/utils.go
index 3051d3d286..53c6a3ed52 100644
--- a/modules/test/utils.go
+++ b/modules/test/utils.go
@@ -17,6 +17,7 @@ import (
// RedirectURL returns the redirect URL of a http response.
// It also works for JSONRedirect: `{"redirect": "..."}`
+// FIXME: it should separate the logic of checking from header and JSON body
func RedirectURL(resp http.ResponseWriter) string {
loc := resp.Header().Get("Location")
if loc != "" {
@@ -34,6 +35,15 @@ func RedirectURL(resp http.ResponseWriter) string {
return ""
}
+func ParseJSONError(buf []byte) (ret struct {
+ ErrorMessage string `json:"errorMessage"`
+ RenderFormat string `json:"renderFormat"`
+},
+) {
+ _ = json.Unmarshal(buf, &ret)
+ return ret
+}
+
func IsNormalPageCompleted(s string) bool {
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
}
diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go
index 8e970aa2be..60e281d403 100644
--- a/modules/testlogger/testlogger.go
+++ b/modules/testlogger/testlogger.go
@@ -92,7 +92,7 @@ func (w *testLoggerWriterCloser) Reset() {
// Printf takes a format and args and prints the string to os.Stdout
func Printf(format string, args ...any) {
if !log.CanColorStdout {
- for i := 0; i < len(args); i++ {
+ for i := range args {
if c, ok := args[i].(*log.ColoredValue); ok {
args[i] = c.Value()
}
diff --git a/modules/timeutil/executable.go b/modules/timeutil/executable.go
deleted file mode 100644
index 57ae8b2a9d..0000000000
--- a/modules/timeutil/executable.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package timeutil
-
-import (
- "os"
- "path/filepath"
- "sync"
- "time"
-
- "code.gitea.io/gitea/modules/log"
-)
-
-var (
- executablModTime = time.Now()
- executablModTimeOnce sync.Once
-)
-
-// GetExecutableModTime get executable file modified time of current process.
-func GetExecutableModTime() time.Time {
- executablModTimeOnce.Do(func() {
- exePath, err := os.Executable()
- if err != nil {
- log.Error("os.Executable: %v", err)
- return
- }
-
- exePath, err = filepath.Abs(exePath)
- if err != nil {
- log.Error("filepath.Abs: %v", err)
- return
- }
-
- exePath, err = filepath.EvalSymlinks(exePath)
- if err != nil {
- log.Error("filepath.EvalSymlinks: %v", err)
- return
- }
-
- st, err := os.Stat(exePath)
- if err != nil {
- log.Error("os.Stat: %v", err)
- return
- }
-
- executablModTime = st.ModTime()
- })
- return executablModTime
-}
diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go
index 8cb3d278ce..2e8d9c4a1e 100644
--- a/modules/typesniffer/typesniffer.go
+++ b/modules/typesniffer/typesniffer.go
@@ -6,18 +6,14 @@ package typesniffer
import (
"bytes"
"encoding/binary"
- "fmt"
- "io"
"net/http"
"regexp"
"slices"
"strings"
-
- "code.gitea.io/gitea/modules/util"
+ "sync"
)
-// Use at most this many bytes to determine Content Type.
-const sniffLen = 1024
+const SniffContentSize = 1024
const (
MimeTypeImageSvg = "image/svg+xml"
@@ -26,22 +22,30 @@ const (
MimeTypeApplicationOctetStream = "application/octet-stream"
)
-var (
- svgComment = regexp.MustCompile(`(?s)<!--.*?-->`)
- svgTagRegex = regexp.MustCompile(`(?si)\A\s*(?:(<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg\b`)
- svgTagInXMLRegex = regexp.MustCompile(`(?si)\A<\?xml\b.*?\?>\s*(?:(<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg\b`)
-)
-
-// SniffedType contains information about a blobs type.
+var globalVars = sync.OnceValue(func() (ret struct {
+ svgComment, svgTagRegex, svgTagInXMLRegex *regexp.Regexp
+},
+) {
+ ret.svgComment = regexp.MustCompile(`(?s)<!--.*?-->`)
+ ret.svgTagRegex = regexp.MustCompile(`(?si)\A\s*(?:(<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg\b`)
+ ret.svgTagInXMLRegex = regexp.MustCompile(`(?si)\A<\?xml\b.*?\?>\s*(?:(<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg\b`)
+ return ret
+})
+
+// SniffedType contains information about a blob's type.
type SniffedType struct {
contentType string
}
-// IsText etects if content format is plain text.
+// IsText detects if the content format is text family, including text/plain, text/html, text/css, etc.
func (ct SniffedType) IsText() bool {
return strings.Contains(ct.contentType, "text/")
}
+func (ct SniffedType) IsTextPlain() bool {
+ return strings.Contains(ct.contentType, "text/plain")
+}
+
// IsImage detects if data is an image format
func (ct SniffedType) IsImage() bool {
return strings.Contains(ct.contentType, "image/")
@@ -57,12 +61,12 @@ func (ct SniffedType) IsPDF() bool {
return strings.Contains(ct.contentType, "application/pdf")
}
-// IsVideo detects if data is an video format
+// IsVideo detects if data is a video format
func (ct SniffedType) IsVideo() bool {
return strings.Contains(ct.contentType, "video/")
}
-// IsAudio detects if data is an video format
+// IsAudio detects if data is a video format
func (ct SniffedType) IsAudio() bool {
return strings.Contains(ct.contentType, "audio/")
}
@@ -103,33 +107,34 @@ func detectFileTypeBox(data []byte) (brands []string, found bool) {
return brands, true
}
-// DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty.
+// DetectContentType extends http.DetectContentType with more content types. Defaults to text/plain if input is empty.
func DetectContentType(data []byte) SniffedType {
if len(data) == 0 {
- return SniffedType{"text/unknown"}
+ return SniffedType{"text/plain"}
}
ct := http.DetectContentType(data)
- if len(data) > sniffLen {
- data = data[:sniffLen]
+ if len(data) > SniffContentSize {
+ data = data[:SniffContentSize]
}
+ vars := globalVars()
// SVG is unsupported by http.DetectContentType, https://github.com/golang/go/issues/15888
detectByHTML := strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html")
detectByXML := strings.Contains(ct, "text/xml")
if detectByHTML || detectByXML {
- dataProcessed := svgComment.ReplaceAll(data, nil)
+ dataProcessed := vars.svgComment.ReplaceAll(data, nil)
dataProcessed = bytes.TrimSpace(dataProcessed)
- if detectByHTML && svgTagRegex.Match(dataProcessed) ||
- detectByXML && svgTagInXMLRegex.Match(dataProcessed) {
+ if detectByHTML && vars.svgTagRegex.Match(dataProcessed) ||
+ detectByXML && vars.svgTagInXMLRegex.Match(dataProcessed) {
ct = MimeTypeImageSvg
}
}
if strings.HasPrefix(ct, "audio/") && bytes.HasPrefix(data, []byte("ID3")) {
// The MP3 detection is quite inaccurate, any content with "ID3" prefix will result in "audio/mpeg".
- // So remove the "ID3" prefix and detect again, if result is text, then it must be text content.
+ // So remove the "ID3" prefix and detect again, then if the result is "text", it must be text content.
// This works especially because audio files contain many unprintable/invalid characters like `0x00`
ct2 := http.DetectContentType(data[3:])
if strings.HasPrefix(ct2, "text/") {
@@ -155,15 +160,3 @@ func DetectContentType(data []byte) SniffedType {
}
return SniffedType{ct}
}
-
-// DetectContentTypeFromReader guesses the content type contained in the reader.
-func DetectContentTypeFromReader(r io.Reader) (SniffedType, error) {
- buf := make([]byte, sniffLen)
- n, err := util.ReadAtMost(r, buf)
- if err != nil {
- return SniffedType{}, fmt.Errorf("DetectContentTypeFromReader io error: %w", err)
- }
- buf = buf[:n]
-
- return DetectContentType(buf), nil
-}
diff --git a/modules/typesniffer/typesniffer_test.go b/modules/typesniffer/typesniffer_test.go
index 3e5db3308b..a0c824b912 100644
--- a/modules/typesniffer/typesniffer_test.go
+++ b/modules/typesniffer/typesniffer_test.go
@@ -4,7 +4,6 @@
package typesniffer
import (
- "bytes"
"encoding/base64"
"encoding/hex"
"strings"
@@ -17,7 +16,7 @@ func TestDetectContentTypeLongerThanSniffLen(t *testing.T) {
// Pre-condition: Shorter than sniffLen detects SVG.
assert.Equal(t, "image/svg+xml", DetectContentType([]byte(`<!-- Comment --><svg></svg>`)).contentType)
// Longer than sniffLen detects something else.
- assert.NotEqual(t, "image/svg+xml", DetectContentType([]byte(`<!-- `+strings.Repeat("x", sniffLen)+` --><svg></svg>`)).contentType)
+ assert.NotEqual(t, "image/svg+xml", DetectContentType([]byte(`<!-- `+strings.Repeat("x", SniffContentSize)+` --><svg></svg>`)).contentType)
}
func TestIsTextFile(t *testing.T) {
@@ -116,22 +115,13 @@ func TestIsAudio(t *testing.T) {
assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi 🌞, ..."+"🌛"[0:2])).IsText()) // test ID3 tag with incomplete UTF8 char
}
-func TestDetectContentTypeFromReader(t *testing.T) {
- mp3, _ := base64.StdEncoding.DecodeString("SUQzBAAAAAABAFRYWFgAAAASAAADbWFqb3JfYnJhbmQAbXA0MgBUWFhYAAAAEQAAA21pbm9yX3Zl")
- st, err := DetectContentTypeFromReader(bytes.NewReader(mp3))
- assert.NoError(t, err)
- assert.True(t, st.IsAudio())
-}
-
func TestDetectContentTypeOgg(t *testing.T) {
oggAudio, _ := hex.DecodeString("4f67675300020000000000000000352f0000000000007dc39163011e01766f72626973000000000244ac0000000000000071020000000000b8014f6767530000")
- st, err := DetectContentTypeFromReader(bytes.NewReader(oggAudio))
- assert.NoError(t, err)
+ st := DetectContentType(oggAudio)
assert.True(t, st.IsAudio())
oggVideo, _ := hex.DecodeString("4f676753000200000000000000007d9747ef000000009b59daf3012a807468656f7261030201001e00110001e000010e00020000001e00000001000001000001")
- st, err = DetectContentTypeFromReader(bytes.NewReader(oggVideo))
- assert.NoError(t, err)
+ st = DetectContentType(oggVideo)
assert.True(t, st.IsVideo())
}
diff --git a/modules/util/error.go b/modules/util/error.go
index 8e67d5a82f..6b2721618e 100644
--- a/modules/util/error.go
+++ b/modules/util/error.go
@@ -17,8 +17,8 @@ var (
ErrNotExist = errors.New("resource does not exist") // also implies HTTP 404
ErrAlreadyExist = errors.New("resource already exists") // also implies HTTP 409
- // ErrUnprocessableContent implies HTTP 422, syntax of the request content was correct,
- // but server was unable to process the contained instructions
+ // ErrUnprocessableContent implies HTTP 422, the syntax of the request content is correct,
+ // but the server is unable to process the contained instructions
ErrUnprocessableContent = errors.New("unprocessable content")
)
diff --git a/modules/util/map.go b/modules/util/map.go
new file mode 100644
index 0000000000..f307faad1f
--- /dev/null
+++ b/modules/util/map.go
@@ -0,0 +1,13 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package util
+
+func GetMapValueOrDefault[T any](m map[string]any, key string, defaultValue T) T {
+ if value, ok := m[key]; ok {
+ if v, ok := value.(T); ok {
+ return v
+ }
+ }
+ return defaultValue
+}
diff --git a/modules/util/map_test.go b/modules/util/map_test.go
new file mode 100644
index 0000000000..1a141cec88
--- /dev/null
+++ b/modules/util/map_test.go
@@ -0,0 +1,26 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package util
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetMapValueOrDefault(t *testing.T) {
+ testMap := map[string]any{
+ "key1": "value1",
+ "key2": 42,
+ "key3": nil,
+ }
+
+ assert.Equal(t, "value1", GetMapValueOrDefault(testMap, "key1", "default"))
+ assert.Equal(t, 42, GetMapValueOrDefault(testMap, "key2", 0))
+
+ assert.Equal(t, "default", GetMapValueOrDefault(testMap, "key4", "default"))
+ assert.Equal(t, 100, GetMapValueOrDefault(testMap, "key5", 100))
+
+ assert.Equal(t, "default", GetMapValueOrDefault(testMap, "key3", "default"))
+}
diff --git a/modules/util/remove.go b/modules/util/remove.go
index d1e38faf5f..3db0b5a796 100644
--- a/modules/util/remove.go
+++ b/modules/util/remove.go
@@ -15,7 +15,7 @@ const windowsSharingViolationError syscall.Errno = 32
// Remove removes the named file or (empty) directory with at most 5 attempts.
func Remove(name string) error {
var err error
- for i := 0; i < 5; i++ {
+ for range 5 {
err = os.Remove(name)
if err == nil {
break
@@ -44,7 +44,7 @@ func Remove(name string) error {
// RemoveAll removes the named file or (empty) directory with at most 5 attempts.
func RemoveAll(name string) error {
var err error
- for i := 0; i < 5; i++ {
+ for range 5 {
err = os.RemoveAll(name)
if err == nil {
break
@@ -73,7 +73,7 @@ func RemoveAll(name string) error {
// Rename renames (moves) oldpath to newpath with at most 5 attempts.
func Rename(oldpath, newpath string) error {
var err error
- for i := 0; i < 5; i++ {
+ for i := range 5 {
err = os.Rename(oldpath, newpath)
if err == nil {
break
diff --git a/modules/util/rotatingfilewriter/writer_test.go b/modules/util/rotatingfilewriter/writer_test.go
index 88392797b3..f6ea1d50ae 100644
--- a/modules/util/rotatingfilewriter/writer_test.go
+++ b/modules/util/rotatingfilewriter/writer_test.go
@@ -23,7 +23,7 @@ func TestCompressOldFile(t *testing.T) {
ng, err := os.OpenFile(nonGzip, os.O_CREATE|os.O_WRONLY, 0o660)
assert.NoError(t, err)
- for i := 0; i < 999; i++ {
+ for range 999 {
f.WriteString("This is a test file\n")
ng.WriteString("This is a test file\n")
}
diff --git a/modules/util/string.go b/modules/util/string.go
index 19cf75b8b3..b9b59df3ef 100644
--- a/modules/util/string.go
+++ b/modules/util/string.go
@@ -103,10 +103,31 @@ func UnsafeStringToBytes(s string) []byte {
func SplitTrimSpace(input, sep string) []string {
input = strings.TrimSpace(input)
var stringList []string
- for _, s := range strings.Split(input, sep) {
+ for s := range strings.SplitSeq(input, sep) {
if s = strings.TrimSpace(s); s != "" {
stringList = append(stringList, s)
}
}
return stringList
}
+
+func asciiLower(b byte) byte {
+ if 'A' <= b && b <= 'Z' {
+ return b + ('a' - 'A')
+ }
+ return b
+}
+
+// AsciiEqualFold is from Golang https://cs.opensource.google/go/go/+/refs/tags/go1.24.4:src/net/http/internal/ascii/print.go
+// ASCII only. In most cases for protocols, we should only use this but not [strings.EqualFold]
+func AsciiEqualFold(s, t string) bool { //nolint:revive // PascalCase
+ if len(s) != len(t) {
+ return false
+ }
+ for i := 0; i < len(s); i++ {
+ if asciiLower(s[i]) != asciiLower(t[i]) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go
index 9f6cf5201a..ba383ba195 100644
--- a/modules/validation/helpers.go
+++ b/modules/validation/helpers.go
@@ -7,6 +7,7 @@ import (
"net"
"net/url"
"regexp"
+ "slices"
"strings"
"sync"
@@ -55,12 +56,7 @@ func IsValidSiteURL(uri string) bool {
return false
}
- for _, scheme := range setting.Service.ValidSiteURLSchemes {
- if scheme == u.Scheme {
- return true
- }
- }
- return false
+ return slices.Contains(setting.Service.ValidSiteURLSchemes, u.Scheme)
}
// IsEmailDomainListed checks whether the domain of an email address
diff --git a/modules/validation/helpers_test.go b/modules/validation/helpers_test.go
index 52f383f698..6a982965f6 100644
--- a/modules/validation/helpers_test.go
+++ b/modules/validation/helpers_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -47,7 +48,7 @@ func Test_IsValidURL(t *testing.T) {
}
func Test_IsValidExternalURL(t *testing.T) {
- setting.AppURL = "https://try.gitea.io/"
+ defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
cases := []struct {
description string
@@ -89,7 +90,7 @@ func Test_IsValidExternalURL(t *testing.T) {
}
func Test_IsValidExternalTrackerURLFormat(t *testing.T) {
- setting.AppURL = "https://try.gitea.io/"
+ defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
cases := []struct {
description string
diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go
index 03e188f509..ee4eca976e 100644
--- a/modules/web/middleware/binding.go
+++ b/modules/web/middleware/binding.go
@@ -50,7 +50,7 @@ func AssignForm(form any, data map[string]any) {
}
func getRuleBody(field reflect.StructField, prefix string) string {
- for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
+ for rule := range strings.SplitSeq(field.Tag.Get("binding"), ";") {
if strings.HasPrefix(rule, prefix) {
return rule[len(prefix) : len(rule)-1]
}
diff --git a/modules/web/router.go b/modules/web/router.go
index da06b955b1..5812ff69d4 100644
--- a/modules/web/router.go
+++ b/modules/web/router.go
@@ -125,8 +125,8 @@ func (r *Router) Methods(methods, pattern string, h ...any) {
middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
fullPattern := r.getPattern(pattern)
if strings.Contains(methods, ",") {
- methods := strings.Split(methods, ",")
- for _, method := range methods {
+ methods := strings.SplitSeq(methods, ",")
+ for method := range methods {
r.chiRouter.With(middlewares...).Method(strings.TrimSpace(method), fullPattern, handlerFunc)
}
} else {
diff --git a/modules/web/router_path.go b/modules/web/router_path.go
index baf1b522af..64154c34a5 100644
--- a/modules/web/router_path.go
+++ b/modules/web/router_path.go
@@ -6,6 +6,7 @@ package web
import (
"net/http"
"regexp"
+ "slices"
"strings"
"code.gitea.io/gitea/modules/container"
@@ -25,6 +26,7 @@ func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request)
path := chiCtx.URLParam(g.pathParam)
for _, m := range g.matchers {
if m.matchPath(chiCtx, path) {
+ chiCtx.RoutePatterns = append(chiCtx.RoutePatterns, m.pattern)
handler := m.handlerFunc
for i := len(m.middlewares) - 1; i >= 0; i-- {
handler = m.middlewares[i](handler).ServeHTTP
@@ -36,11 +38,22 @@ func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request)
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
}
+type RouterPathGroupPattern struct {
+ pattern string
+ re *regexp.Regexp
+ params []routerPathParam
+ middlewares []any
+}
+
// MatchPath matches the request method, and uses regexp to match the path.
-// The pattern uses "<...>" to define path parameters, for example: "/<name>" (different from chi router)
-// It is only designed to resolve some special cases which chi router can't handle.
+// The pattern uses "<...>" to define path parameters, for example, "/<name>" (different from chi router)
+// It is only designed to resolve some special cases that chi router can't handle.
// For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) {
+ g.MatchPattern(methods, g.PatternRegexp(pattern), h...)
+}
+
+func (g *RouterPathGroup) MatchPattern(methods string, pattern *RouterPathGroupPattern, h ...any) {
g.matchers = append(g.matchers, newRouterPathMatcher(methods, pattern, h...))
}
@@ -51,6 +64,7 @@ type routerPathParam struct {
type routerPathMatcher struct {
methods container.Set[string]
+ pattern string
re *regexp.Regexp
params []routerPathParam
middlewares []func(http.Handler) http.Handler
@@ -96,29 +110,35 @@ func isValidMethod(name string) bool {
return false
}
-func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher {
- middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h)
+func newRouterPathMatcher(methods string, patternRegexp *RouterPathGroupPattern, h ...any) *routerPathMatcher {
+ middlewares, handlerFunc := wrapMiddlewareAndHandler(patternRegexp.middlewares, h)
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
- for _, method := range strings.Split(methods, ",") {
+ for method := range strings.SplitSeq(methods, ",") {
method = strings.TrimSpace(method)
if !isValidMethod(method) {
panic("invalid HTTP method: " + method)
}
p.methods.Add(method)
}
+ p.pattern, p.re, p.params = patternRegexp.pattern, patternRegexp.re, patternRegexp.params
+ return p
+}
+
+func patternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
+ p := &RouterPathGroupPattern{middlewares: slices.Clone(h)}
re := []byte{'^'}
lastEnd := 0
for lastEnd < len(pattern) {
start := strings.IndexByte(pattern[lastEnd:], '<')
if start == -1 {
- re = append(re, pattern[lastEnd:]...)
+ re = append(re, regexp.QuoteMeta(pattern[lastEnd:])...)
break
}
end := strings.IndexByte(pattern[lastEnd+start:], '>')
if end == -1 {
panic("invalid pattern: " + pattern)
}
- re = append(re, pattern[lastEnd:lastEnd+start]...)
+ re = append(re, regexp.QuoteMeta(pattern[lastEnd:lastEnd+start])...)
partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
lastEnd += start + end + 1
@@ -140,7 +160,10 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher
p.params = append(p.params, param)
}
re = append(re, '$')
- reStr := string(re)
- p.re = regexp.MustCompile(reStr)
+ p.pattern, p.re = pattern, regexp.MustCompile(string(re))
return p
}
+
+func (g *RouterPathGroup) PatternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
+ return patternRegexp(pattern, h...)
+}
diff --git a/modules/web/router_test.go b/modules/web/router_test.go
index 21619012ea..f216aa6180 100644
--- a/modules/web/router_test.go
+++ b/modules/web/router_test.go
@@ -34,7 +34,7 @@ func TestPathProcessor(t *testing.T) {
testProcess := func(pattern, uri string, expectedPathParams map[string]string) {
chiCtx := chi.NewRouteContext()
chiCtx.RouteMethod = "GET"
- p := newRouterPathMatcher("GET", pattern, http.NotFound)
+ p := newRouterPathMatcher("GET", patternRegexp(pattern), http.NotFound)
assert.True(t, p.matchPath(chiCtx, uri), "use pattern %s to process uri %s", pattern, uri)
assert.Equal(t, expectedPathParams, chiURLParamsToMap(chiCtx), "use pattern %s to process uri %s", pattern, uri)
}
@@ -56,18 +56,23 @@ func TestRouter(t *testing.T) {
recorder.Body = buff
type resultStruct struct {
- method string
- pathParams map[string]string
- handlerMark string
+ method string
+ pathParams map[string]string
+ handlerMarks []string
+ chiRoutePattern *string
}
- var res resultStruct
+ var res resultStruct
h := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
mark := util.OptionalArg(optMark, "")
return func(resp http.ResponseWriter, req *http.Request) {
+ chiCtx := chi.RouteContext(req.Context())
res.method = req.Method
- res.pathParams = chiURLParamsToMap(chi.RouteContext(req.Context()))
- res.handlerMark = mark
+ res.pathParams = chiURLParamsToMap(chiCtx)
+ res.chiRoutePattern = util.ToPointer(chiCtx.RoutePattern())
+ if mark != "" {
+ res.handlerMarks = append(res.handlerMarks, mark)
+ }
}
}
@@ -77,6 +82,8 @@ func TestRouter(t *testing.T) {
if stop := req.FormValue("stop"); stop != "" && (mark == "" || mark == stop) {
h(stop)(resp, req)
resp.WriteHeader(http.StatusOK)
+ } else if mark != "" {
+ res.handlerMarks = append(res.handlerMarks, mark)
}
}
}
@@ -108,7 +115,7 @@ func TestRouter(t *testing.T) {
m.Delete("", h())
})
m.PathGroup("/*", func(g *RouterPathGroup) {
- g.MatchPath("GET", `/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s2"), h("match-path"))
+ g.MatchPattern("GET", g.PatternRegexp(`/<dir:*>/<file:[a-z]{1,2}>`, stopMark("s2")), stopMark("s3"), h("match-path"))
}, stopMark("s1"))
})
})
@@ -121,36 +128,47 @@ func TestRouter(t *testing.T) {
req, err := http.NewRequest(methodPathFields[0], methodPathFields[1], nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
+ if expected.chiRoutePattern == nil {
+ res.chiRoutePattern = nil
+ }
assert.Equal(t, expected, res)
})
}
t.Run("RootRouter", func(t *testing.T) {
- testRoute(t, "GET /the-user/the-repo/other", resultStruct{method: "GET", handlerMark: "not-found:/"})
+ testRoute(t, "GET /the-user/the-repo/other", resultStruct{
+ method: "GET",
+ handlerMarks: []string{"not-found:/"},
+ chiRoutePattern: util.ToPointer(""),
+ })
testRoute(t, "GET /the-user/the-repo/pulls", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "pulls"},
- handlerMark: "list-issues-b",
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "pulls"},
+ handlerMarks: []string{"list-issues-b"},
})
testRoute(t, "GET /the-user/the-repo/issues/123", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
- handlerMark: "view-issue",
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
+ handlerMarks: []string{"view-issue"},
+ chiRoutePattern: util.ToPointer("/{username}/{reponame}/{type:issues|pulls}/{index}"),
})
testRoute(t, "GET /the-user/the-repo/issues/123?stop=hijack", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
- handlerMark: "hijack",
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
+ handlerMarks: []string{"hijack"},
})
testRoute(t, "POST /the-user/the-repo/issues/123/update", resultStruct{
- method: "POST",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "index": "123"},
- handlerMark: "update-issue",
+ method: "POST",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "index": "123"},
+ handlerMarks: []string{"update-issue"},
})
})
t.Run("Sub Router", func(t *testing.T) {
- testRoute(t, "GET /api/v1/other", resultStruct{method: "GET", handlerMark: "not-found:/api/v1"})
+ testRoute(t, "GET /api/v1/other", resultStruct{
+ method: "GET",
+ handlerMarks: []string{"not-found:/api/v1"},
+ })
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches", resultStruct{
method: "GET",
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
@@ -179,31 +197,38 @@ func TestRouter(t *testing.T) {
t.Run("MatchPath", func(t *testing.T) {
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
- handlerMark: "match-path",
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
+ handlerMarks: []string{"s1", "s2", "s3", "match-path"},
})
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1%2fd2/fn", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1%2fd2/fn", "dir": "d1%2fd2", "file": "fn"},
- handlerMark: "match-path",
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1%2fd2/fn", "dir": "d1%2fd2", "file": "fn"},
+ handlerMarks: []string{"s1", "s2", "s3", "match-path"},
})
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/000", resultStruct{
- method: "GET",
- pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
- handlerMark: "not-found:/api/v1",
+ method: "GET",
+ pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
+ handlerMarks: []string{"s1", "not-found:/api/v1"},
})
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s1", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn"},
- handlerMark: "s1",
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn"},
+ handlerMarks: []string{"s1"},
})
testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s2", resultStruct{
- method: "GET",
- pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
- handlerMark: "s2",
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
+ handlerMarks: []string{"s1", "s2"},
+ })
+
+ testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s3", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
+ handlerMarks: []string{"s1", "s2", "s3"},
+ chiRoutePattern: util.ToPointer("/api/v1/repos/{username}/{reponame}/branches/<dir:*>/<file:[a-z]{1,2}>"),
})
})
}
diff --git a/modules/web/routing/logger.go b/modules/web/routing/logger.go
index e3843b1402..3bca9b3420 100644
--- a/modules/web/routing/logger.go
+++ b/modules/web/routing/logger.go
@@ -103,7 +103,10 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
status = v.WrittenStatus()
}
logf := logInfo
- if strings.HasPrefix(req.RequestURI, "/assets/") {
+ // lower the log level for some specific requests, in most cases these logs are not useful
+ if strings.HasPrefix(req.RequestURI, "/assets/") /* static assets */ ||
+ req.RequestURI == "/user/events" /* Server-Sent Events (SSE) handler */ ||
+ req.RequestURI == "/api/actions/runner.v1.RunnerService/FetchTask" /* Actions Runner polling */ {
logf = logTrace
}
message := completedMessage
diff --git a/modules/webhook/type.go b/modules/webhook/type.go
index 72ffde26a1..89c6a4bfe5 100644
--- a/modules/webhook/type.go
+++ b/modules/webhook/type.go
@@ -38,6 +38,7 @@ const (
HookEventPullRequestReview HookEventType = "pull_request_review"
// Actions event only
HookEventSchedule HookEventType = "schedule"
+ HookEventWorkflowRun HookEventType = "workflow_run"
HookEventWorkflowJob HookEventType = "workflow_job"
)
@@ -67,6 +68,7 @@ func AllEvents() []HookEventType {
HookEventRelease,
HookEventPackage,
HookEventStatus,
+ HookEventWorkflowRun,
HookEventWorkflowJob,
}
}
diff --git a/modules/zstd/zstd_test.go b/modules/zstd/zstd_test.go
index c3ca8e78f7..7fd30484ca 100644
--- a/modules/zstd/zstd_test.go
+++ b/modules/zstd/zstd_test.go
@@ -16,7 +16,7 @@ import (
)
func TestWriterReader(t *testing.T) {
- testData := prepareTestData(t, 20_000_000)
+ testData := prepareTestData(t, 1_000_000)
result := bytes.NewBuffer(nil)
@@ -64,7 +64,7 @@ func TestWriterReader(t *testing.T) {
}
func TestSeekableWriterReader(t *testing.T) {
- testData := prepareTestData(t, 20_000_000)
+ testData := prepareTestData(t, 2_000_000)
result := bytes.NewBuffer(nil)
@@ -109,7 +109,7 @@ func TestSeekableWriterReader(t *testing.T) {
reader, err := NewSeekableReader(assertReader)
require.NoError(t, err)
- _, err = reader.Seek(10_000_000, io.SeekStart)
+ _, err = reader.Seek(1_000_000, io.SeekStart)
require.NoError(t, err)
data := make([]byte, 1000)
@@ -117,7 +117,7 @@ func TestSeekableWriterReader(t *testing.T) {
require.NoError(t, err)
require.NoError(t, reader.Close())
- assert.Equal(t, testData[10_000_000:10_000_000+1000], data)
+ assert.Equal(t, testData[1_000_000:1_000_000+1000], data)
// Should seek 3 times,
// the first two times are for getting the index,
diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json
index 826eb40e87..caa068ff44 100644
--- a/options/fileicon/material-icon-rules.json
+++ b/options/fileicon/material-icon-rules.json
@@ -3131,6 +3131,34 @@
".links": "folder-link",
"_links": "folder-link",
"__links__": "folder-link",
+ "pytorch": "folder-pytorch",
+ ".pytorch": "folder-pytorch",
+ "_pytorch": "folder-pytorch",
+ "__pytorch__": "folder-pytorch",
+ "torch": "folder-pytorch",
+ ".torch": "folder-pytorch",
+ "_torch": "folder-pytorch",
+ "__torch__": "folder-pytorch",
+ "blender": "folder-blender",
+ ".blender": "folder-blender",
+ "_blender": "folder-blender",
+ "__blender__": "folder-blender",
+ "blender-assets": "folder-blender",
+ ".blender-assets": "folder-blender",
+ "_blender-assets": "folder-blender",
+ "__blender-assets__": "folder-blender",
+ "blender-files": "folder-blender",
+ ".blender-files": "folder-blender",
+ "_blender-files": "folder-blender",
+ "__blender-files__": "folder-blender",
+ "blender-project": "folder-blender",
+ ".blender-project": "folder-blender",
+ "_blender-project": "folder-blender",
+ "__blender-project__": "folder-blender",
+ "blender-models": "folder-blender",
+ ".blender-models": "folder-blender",
+ "_blender-models": "folder-blender",
+ "__blender-models__": "folder-blender",
"meta-inf": "folder-config",
".meta-inf": "folder-config",
"_meta-inf": "folder-config",
@@ -6291,7 +6319,35 @@
"links": "folder-link-open",
".links": "folder-link-open",
"_links": "folder-link-open",
- "__links__": "folder-link-open"
+ "__links__": "folder-link-open",
+ "pytorch": "folder-pytorch-open",
+ ".pytorch": "folder-pytorch-open",
+ "_pytorch": "folder-pytorch-open",
+ "__pytorch__": "folder-pytorch-open",
+ "torch": "folder-pytorch-open",
+ ".torch": "folder-pytorch-open",
+ "_torch": "folder-pytorch-open",
+ "__torch__": "folder-pytorch-open",
+ "blender": "folder-blender-open",
+ ".blender": "folder-blender-open",
+ "_blender": "folder-blender-open",
+ "__blender__": "folder-blender-open",
+ "blender-assets": "folder-blender-open",
+ ".blender-assets": "folder-blender-open",
+ "_blender-assets": "folder-blender-open",
+ "__blender-assets__": "folder-blender-open",
+ "blender-files": "folder-blender-open",
+ ".blender-files": "folder-blender-open",
+ "_blender-files": "folder-blender-open",
+ "__blender-files__": "folder-blender-open",
+ "blender-project": "folder-blender-open",
+ ".blender-project": "folder-blender-open",
+ "_blender-project": "folder-blender-open",
+ "__blender-project__": "folder-blender-open",
+ "blender-models": "folder-blender-open",
+ ".blender-models": "folder-blender-open",
+ "_blender-models": "folder-blender-open",
+ "__blender-models__": "folder-blender-open"
},
"rootFolderNames": {},
"rootFolderNamesExpanded": {},
@@ -6629,6 +6685,7 @@
"fish": "console",
"exp": "console",
"nu": "console",
+ "xsh": "console",
"ps1": "powershell",
"psm1": "powershell",
"psd1": "powershell",
@@ -6700,7 +6757,6 @@
"lbx": "lbx",
"tex": "tex",
"sty": "sty",
- "cls": "cls",
"ltx": "ltx",
"dtx": "dtx",
"pptx": "powerpoint",
@@ -7099,10 +7155,10 @@
"fast": "lisp",
"stl": "3d",
"stp": "3d",
+ "step": "3d",
"obj": "3d",
"o": "3d",
"ac": "3d",
- "blend": "3d",
"dxf": "3d",
"fbx": "3d",
"mesh": "3d",
@@ -7115,6 +7171,12 @@
"vox": "3d",
"gltf": "3d",
"glb": "3d",
+ "3ds": "3d",
+ "dae": "3d",
+ "ply": "3d",
+ "wrl": "3d",
+ "usd": "3d",
+ "usdz": "3d",
"svg": "svg",
"ai": "adobe-illustrator",
"ait": "adobe-illustrator",
@@ -7380,6 +7442,14 @@
"gnu": "gnuplot",
"smk": "snakemake",
"snakemake": "snakemake",
+ "cpn": "coloredpetrinets",
+ "pnml": "coloredpetrinets",
+ "pt": "pytorch",
+ "pth": "pytorch",
+ "pwf": "pytorch",
+ "blend": "blender",
+ "blend1": "blender",
+ "blend2": "blender",
"yaml-tmlanguage": "yaml",
"tmlanguage": "xml",
"cljx": "clojure",
@@ -7446,6 +7516,7 @@
"hintrc": "jsonc",
"babelrc": "jsonc",
"jmd": "juliamarkdown",
+ "cls": "tex",
"ctx": "latex",
"mak": "makefile",
"mkd": "markdown",
@@ -7542,7 +7613,6 @@
"opml": "xml",
"owl": "xml",
"proj": "xml",
- "pt": "xml",
"publishsettings": "xml",
"pubxml": "xml",
"pubxml.user": "xml",
@@ -7880,6 +7950,7 @@
".pubignore": "dart",
"cmakelists.txt": "cmake",
"cmakecache.txt": "cmake",
+ "CMakePresets.json": "cmake",
"semgrep.yml": "semgrep",
".semgrepignore": "semgrep",
"vue.config.js": "vue-config",
@@ -8488,6 +8559,7 @@
".mocharc.yml": "mocha",
".mocharc.yaml": "mocha",
".mocharc.js": "mocha",
+ ".mocharc.cjs": "mocha",
".mocharc.json": "mocha",
".mocharc.jsonc": "mocha",
"jenkinsfile": "jenkins",
@@ -8736,11 +8808,13 @@
"azure-pipelines-main.yaml": "azure-pipelines",
"vagrantfile": "vagrant",
"prisma.yml": "prisma",
+ "prisma.config.ts": "prisma",
".nycrc": "istanbul",
".nycrc.json": "istanbul",
".nycrc.yaml": "istanbul",
".nycrc.yml": "istanbul",
"nyc.config.js": "istanbul",
+ "nyc.config.cjs": "istanbul",
".istanbul.yml": "istanbul",
"tailwind.js": "tailwindcss",
"tailwind.ts": "tailwindcss",
@@ -9474,6 +9548,7 @@
"hadolint.yaml": "hadolint",
"hadolint.yml": "hadolint",
".rhistory": "r",
+ "cmakepresets.json": "cmake",
"cname": "http",
"sonarqube.analysis.xml": "sonarcloud",
"owners": "codeowners",
@@ -9587,7 +9662,13 @@
"tex": "tex",
"latex": "latex",
"latex-expl3": "latex",
+ "latex-class": "latex-class",
+ "latex-package": "latex-package",
+ "context": "context",
"doctex": "doctex",
+ "doctex-installer": "doctex-installer",
+ "bibtex": "bibliography",
+ "bibtex-style": "bibtex-style",
"apex": "salesforce",
"sas": "sas",
"dockerfile": "docker",
@@ -9619,8 +9700,6 @@
"vue-postcss": "vue",
"vue-html": "vue",
"lua": "lua",
- "bibtex": "bibliography",
- "bibtex-style": "bibtex-style",
"log": "log",
"jupyter": "jupyter",
"plaintext": "document",
diff --git a/options/fileicon/material-icon-svgs.json b/options/fileicon/material-icon-svgs.json
index cf06555b03..326e0a1b91 100644
--- a/options/fileicon/material-icon-svgs.json
+++ b/options/fileicon/material-icon-svgs.json
@@ -10,36 +10,36 @@
"adobe-photoshop_light": "<svg viewBox='0 0 32 32'><rect width='28' height='28' x='2' y='2' fill='#455a64' rx='4'/><path fill='#64b5f6' d='M23.744 14.716a3.7 3.7 0 0 0-1.066-.408 5.4 5.4 0 0 0-1.245-.157 2.1 2.1 0 0 0-.666.085.57.57 0 0 0-.345.24.7.7 0 0 0-.089.324.56.56 0 0 0 .111.313 1.3 1.3 0 0 0 .378.324q.386.217.79.397a7.8 7.8 0 0 1 1.71.877 2.7 2.7 0 0 1 .878.998 2.8 2.8 0 0 1 .256 1.238 2.96 2.96 0 0 1-.434 1.599 2.83 2.83 0 0 1-1.244 1.07 4.75 4.75 0 0 1-2.011.384 7 7 0 0 1-1.511-.156 4.2 4.2 0 0 1-1.134-.385.24.24 0 0 1-.122-.228v-2.092a.14.14 0 0 1 .044-.108c.034-.024.067-.012.1.012a4.6 4.6 0 0 0 1.378.59 4.8 4.8 0 0 0 1.311.18 2 2 0 0 0 .923-.169.56.56 0 0 0 .3-.505.65.65 0 0 0-.267-.48 4.6 4.6 0 0 0-1.089-.565 6.6 6.6 0 0 1-1.578-.866 3 3 0 0 1-.844-1.021 2.76 2.76 0 0 1-.256-1.226 3 3 0 0 1 .378-1.455 2.8 2.8 0 0 1 1.167-1.105A4 4 0 0 1 21.533 12a9 9 0 0 1 1.378.108 3.7 3.7 0 0 1 .956.277.2.2 0 0 1 .11.108.7.7 0 0 1 .023.144v1.96a.15.15 0 0 1-.056.12.28.28 0 0 1-.2 0M12.38 10H9.99v-.03h-2v12h2V18h2.39A3.62 3.62 0 0 0 16 14.38v-.76A3.62 3.62 0 0 0 12.38 10M14 14.38A1.626 1.626 0 0 1 12.38 16H9.99v-4h2.39A1.626 1.626 0 0 1 14 13.62Z'/></svg>",
"adobe-swc": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M4 5v22a1 1 0 0 0 1 1h22a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1m20 7c-2.926 0-4.21.722-5.012 2H22v4h-4.582C16.34 20.857 14.393 24 8 24v-4c4.559 0 5.14-1.744 6.103-4.632C15.139 12.258 16.559 8 24 8Z'/></svg>",
"adonis": "<svg viewBox='0 0 180 180'><path fill='#7c4dff' d='m79.579 25.741-66.481 115.15h63.305l11.218-19.433H47.613L79.804 65.7l20.005 34.649 11.423-19.783zm42.118 50.221-45.203 78.297h90.408z' paint-order='fill markers stroke'/></svg>",
- "advpl-include.clone": "<svg viewBox='0 0 16 16'><path fill='#00BCD4' fill-rule='evenodd' d='M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012'/></svg>",
- "advpl-ptm.clone": "<svg viewBox='0 0 16 16'><path fill='#EF5350' fill-rule='evenodd' d='M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012'/></svg>",
- "advpl-tlpp.clone": "<svg viewBox='0 0 16 16'><path fill='#FBC02D' fill-rule='evenodd' d='M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012'/></svg>",
+ "advpl-include.clone": "<svg viewBox='0 0 16 16'><path fill='#00bcd4' fill-rule='evenodd' d='M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012'/></svg>",
+ "advpl-ptm.clone": "<svg viewBox='0 0 16 16'><path fill='#ef5350' fill-rule='evenodd' d='M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012'/></svg>",
+ "advpl-tlpp.clone": "<svg viewBox='0 0 16 16'><path fill='#fbc02d' fill-rule='evenodd' d='M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012'/></svg>",
"advpl": "<svg viewBox='0 0 16 16'><path fill='#7986cb' fill-rule='evenodd' d='M6.752 1.158C2.234 1.96-.271 6.943 1.758 11.09c2.537 5.185 10.047 5.142 12.511-.07C16.69 5.9 12.321.17 6.752 1.159m.587 2.335c2.576.517 5.233 1.323 5.326 1.615.26.808.256 4.849-.004 5.34-.066.125-1.209-.012-2.08-.247l-.351-.094-.001-.437c-.005-1.308-.138-2.547-.29-2.7-.176-.176-1.312-.545-3.052-.99L5.78 5.697l-.014-.267c-.033-.6.117-1.95.232-2.093.063-.079.315-.05 1.34.157M4.029 5.39c.5.066 1.083.178 1.492.289l.178.048.03.984c.058 1.844.117 2.13.475 2.29.448.2 2.083.679 3.62 1.061l.34.084-.01.653c-.012.735-.083 1.393-.175 1.617l-.062.15-.261-.03c-.976-.113-4.175-.896-5.567-1.362-.611-.205-.759-.284-.811-.435-.23-.66-.23-4.905 0-5.337.054-.1.08-.1.75-.012'/></svg>",
- "ahk2.clone": "<svg viewBox='0 0 32 32'><path fill='#AFB42B' d='M25.333 4H6.667A2.657 2.657 0 0 0 4 6.667v18.666A2.667 2.667 0 0 0 6.667 28h18.666A2.667 2.667 0 0 0 28 25.333V6.667A2.667 2.667 0 0 0 25.333 4m-2.495 6.22a4 4 0 0 0-.163 1.01q0 .266-.058.83a9 9 0 0 0-.04.719c0 .584-.031 1.443-.092 2.55q-.074 1.253-.088 2.502c0 .412.032 1.057.097 1.91.067.865.1 1.534.1 1.988a1.62 1.62 0 0 1-.505 1.197 1.65 1.65 0 0 1-1.225.475 1.92 1.92 0 0 1-1.233-.466 1.51 1.51 0 0 1-.554-1.19q0-.644-.06-1.934-.047-.99-.056-1.979 0-.198.003-.376c-.805.065-1.766.198-2.867.398q-1.522.277-3.045.562-.032.61-.11 1.65a30 30 0 0 0-.087 2.017 1.62 1.62 0 0 1-.506 1.192 1.73 1.73 0 0 1-1.224.474l-.048.001a1.7 1.7 0 0 1-1.157-.479 1.62 1.62 0 0 1-.502-1.2c0-.615.05-1.513.155-2.738.104-1.182.157-2.077.157-2.661q0-1.15.057-3.46.054-2.302.053-3.442a1.62 1.62 0 0 1 .508-1.196 1.68 1.68 0 0 1 1.222-.478 1.7 1.7 0 0 1 1.206.484 1.63 1.63 0 0 1 .5 1.19q0 .687-.055 2.07-.036 1.01-.044 2.023.001.23-.065.905a7 7 0 0 0-.022.251l2.825-.532a28 28 0 0 1 3.086-.395q.037-.83.095-2.76a4.8 4.8 0 0 1 .466-1.778c.421-.926.957-1.395 1.591-1.395a1.75 1.75 0 0 1 1.166.434l.003.002a1.58 1.58 0 0 1 .566 1.228 1.5 1.5 0 0 1-.05.397'/></svg>",
+ "ahk2.clone": "<svg viewBox='0 0 32 32'><path fill='#afb42b' d='M25.333 4H6.667A2.657 2.657 0 0 0 4 6.667v18.666A2.667 2.667 0 0 0 6.667 28h18.666A2.667 2.667 0 0 0 28 25.333V6.667A2.667 2.667 0 0 0 25.333 4m-2.495 6.22a4 4 0 0 0-.163 1.01q0 .266-.058.83a9 9 0 0 0-.04.719c0 .584-.031 1.443-.092 2.55q-.074 1.253-.088 2.502c0 .412.032 1.057.097 1.91.067.865.1 1.534.1 1.988a1.62 1.62 0 0 1-.505 1.197 1.65 1.65 0 0 1-1.225.475 1.92 1.92 0 0 1-1.233-.466 1.51 1.51 0 0 1-.554-1.19q0-.644-.06-1.934-.047-.99-.056-1.979 0-.198.003-.376c-.805.065-1.766.198-2.867.398q-1.522.277-3.045.562-.032.61-.11 1.65a30 30 0 0 0-.087 2.017 1.62 1.62 0 0 1-.506 1.192 1.73 1.73 0 0 1-1.224.474l-.048.001a1.7 1.7 0 0 1-1.157-.479 1.62 1.62 0 0 1-.502-1.2c0-.615.05-1.513.155-2.738.104-1.182.157-2.077.157-2.661q0-1.15.057-3.46.054-2.302.053-3.442a1.62 1.62 0 0 1 .508-1.196 1.68 1.68 0 0 1 1.222-.478 1.7 1.7 0 0 1 1.206.484 1.63 1.63 0 0 1 .5 1.19q0 .687-.055 2.07-.036 1.01-.044 2.023.001.23-.065.905a7 7 0 0 0-.022.251l2.825-.532a28 28 0 0 1 3.086-.395q.037-.83.095-2.76a4.8 4.8 0 0 1 .466-1.778c.421-.926.957-1.395 1.591-1.395a1.75 1.75 0 0 1 1.166.434l.003.002a1.58 1.58 0 0 1 .566 1.228 1.5 1.5 0 0 1-.05.397'/></svg>",
"amplify": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='M14 10 5 28h12l-2-4h-4l3-6 5 10h4zm1-2 2-4 12 24h-4l-8-16z'/></svg>",
"android": "<svg viewBox='0 0 32 32'><rect width='4' height='10' x='2' y='12' fill='#8bc34a' rx='2'/><rect width='4' height='10' x='26' y='12' fill='#8bc34a' rx='2'/><path fill='#8bc34a' d='M8 12h16v12H8zm2 12h4v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2zm8 0h4v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2zm3.545-19.759 2.12-2.12A1 1 0 0 0 22.251.707l-2.326 2.326a7.97 7.97 0 0 0-7.85 0L9.75.707a1 1 0 1 0-1.414 1.414l2.12 2.12A7.97 7.97 0 0 0 8 10h16a7.97 7.97 0 0 0-2.455-5.759M14 8h-2V6h2Zm6 0h-2V6h2Z'/></svg>",
- "angular-component.clone": "<svg viewBox='0 0 24 24'><path fill='#1976D2' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
- "angular-directive.clone": "<svg viewBox='0 0 24 24'><path fill='#AB47BC' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
- "angular-guard.clone": "<svg viewBox='0 0 24 24'><path fill='#43A047' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
- "angular-interceptor.clone": "<svg viewBox='0 0 24 24'><path fill='#FF9800' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
- "angular-pipe.clone": "<svg viewBox='0 0 24 24'><path fill='#00897B' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
- "angular-resolver.clone": "<svg viewBox='0 0 24 24'><path fill='#43A047' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
- "angular-service.clone": "<svg viewBox='0 0 24 24'><path fill='#FFCA28' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
+ "angular-component.clone": "<svg viewBox='0 0 24 24'><path fill='#1976d2' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
+ "angular-directive.clone": "<svg viewBox='0 0 24 24'><path fill='#ab47bc' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
+ "angular-guard.clone": "<svg viewBox='0 0 24 24'><path fill='#43a047' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
+ "angular-interceptor.clone": "<svg viewBox='0 0 24 24'><path fill='#ff9800' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
+ "angular-pipe.clone": "<svg viewBox='0 0 24 24'><path fill='#00897b' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
+ "angular-resolver.clone": "<svg viewBox='0 0 24 24'><path fill='#43a047' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
+ "angular-service.clone": "<svg viewBox='0 0 24 24'><path fill='#ffca28' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
"angular": "<svg viewBox='0 0 24 24'><path fill='#e53935' d='M9.87 2.5 3.022 5.666l.645 10.178zm4.26 0 6.202 13.344.645-10.178zM12 7.563l-2.451 5.964h4.906zm-3.73 8.959-.954 2.308L12 21.5l4.683-2.67-.953-2.308z'/></svg>",
"antlr": "<svg viewBox='0 0 24 24'><path fill='#f44336' d='M10.355 1.614a10.469 10.483 0 0 1 11.813 7.792 10.327 10.34 0 0 1-1.565 8.673 10.583 10.597 0 0 1-14.819 2.428 10.416 10.43 0 0 1-4.222-7.14 10.641 10.656 0 0 1 .999-5.994 10.498 10.512 0 0 1 7.795-5.76m.27 3.825c-.949 2.08-1.9 4.16-2.83 6.25-.479 1.345-1.127 2.615-1.716 3.915-.174.408-.468.853-.287 1.312a1.088 1.09 0 0 0 1.575.556c.458-.261.566-.828.778-1.272.952-2.405 2.13-4.708 3.11-7.104a7.356 7.366 0 0 1 .776-1.6c.568 1.406 1.186 2.791 1.773 4.19a14.819 14.839 0 0 1 .969 2.197c-1.51-.015-3.02-.004-4.531-.01 2.073 1.233 4.202 2.379 6.305 3.562a1.094 1.094 0 0 0 1.698-1.036c-.425-1.15-1.014-2.237-1.5-3.364-.917-2.393-2.076-4.685-3.097-7.036a2.685 2.689 0 0 0-.738-1.163 1.564 1.566 0 0 0-2.285.602z'/></svg>",
"apiblueprint": "<svg viewBox='0 0 32 32'><rect width='12' height='12' x='10' y='2' fill='#42a5f5' rx='6'/><rect width='12' height='12' x='18' y='18' fill='#42a5f5' rx='6'/><rect width='12' height='12' x='2' y='18' fill='#42a5f5' rx='6'/><path fill='none' stroke='#42a5f5' stroke-miterlimit='10' stroke-width='3' d='m16 8 8 16M16 8 8 24'/></svg>",
"apollo": "<svg viewBox='0 0 32 32'><path fill='#7e57c2' d='M31.93 14.457a.51.51 0 0 0-.506-.457h-2.01a.497.497 0 0 0-.491.559l.014.134c.616 6.284-4.097 12.817-10.29 14.044A13.009 13.009 0 1 1 24.3 6h4.19a16.013 16.013 0 1 0 3.44 8.457'/><circle cx='24.533' cy='4.267' r='4.267' fill='#7e57c2'/><path fill='#7e57c2' d='M17 8h-3L8 24h3z'/><path fill='#7e57c2' d='M15 8h3l6 16h-3zm2.88 13H12v-3h4.75z'/></svg>",
"applescript": "<svg viewBox='0 0 32 32'><path fill='#78909c' d='M25.425 26.498c-1.162 1.736-2.394 3.43-4.27 3.458-1.875.042-2.477-1.106-4.605-1.106-2.142 0-2.8 1.078-4.578 1.148-1.834.07-3.22-1.848-4.396-3.542C5.183 23 3.35 16.63 5.813 12.346a6.84 6.84 0 0 1 5.767-3.514c1.792-.028 3.5 1.217 4.606 1.217 1.092 0 3.164-1.497 5.334-1.273a6.5 6.5 0 0 1 5.095 2.771 6.38 6.38 0 0 0-3.01 5.334 6.18 6.18 0 0 0 3.752 5.656 15.5 15.5 0 0 1-1.932 3.961M17.432 4.1A6.36 6.36 0 0 1 21.548 2a6.13 6.13 0 0 1-1.456 4.466 5.11 5.11 0 0 1-4.13 1.988 5.98 5.98 0 0 1 1.47-4.354'/></svg>",
"apps-script": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M6.053 20.055H21.21a3.01 3.01 0 0 1 3.049 2.966 3.01 3.01 0 0 1-3.049 2.966H6.053a3.01 3.01 0 0 1-3.049-2.966 3.01 3.01 0 0 1 3.049-2.966'/><path fill='#ffc107' d='M19.44 25.433 7.179 16.765a2.914 2.914 0 0 1-.674-4.143 3.104 3.104 0 0 1 4.258-.656l12.263 8.668a2.914 2.914 0 0 1 .674 4.143 3.104 3.104 0 0 1-4.258.656Z'/><path fill='#43a047' d='m19.489 8.05 4.683 14.026a2.95 2.95 0 0 1-1.957 3.737 3.067 3.067 0 0 1-3.841-1.904L13.69 9.884a2.95 2.95 0 0 1 1.957-3.738 3.067 3.067 0 0 1 3.842 1.905Z'/><path fill='#448aff' d='M18.363 22.076 23.047 8.05a3.067 3.067 0 0 1 3.841-1.904 2.95 2.95 0 0 1 1.958 3.737L24.162 23.91a3.067 3.067 0 0 1-3.842 1.904 2.95 2.95 0 0 1-1.957-3.737Z'/></svg>",
- "appveyor": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 256 256'><path fill='#00B8D4' fill-rule='evenodd' d='M127.646 17.356c61.588 0 110.999 49.414 110.999 110.29a110.64 110.64 0 0 1-110.999 110.999c-60.873 0-110.29-49.414-110.29-110.999 0-60.873 49.414-110.29 110.29-110.29m27.213 131.77c-12.174 15.756-34.375 18.62-49.414 6.446-15.039-11.459-17.187-33.66-5.013-49.414 12.891-15.039 35.091-17.904 50.131-6.445 15.039 12.174 17.187 34.375 4.297 49.414zm-58.723 72.331 42.252-40.82c-15.756 3.58-32.227.716-45.117-10.026-15.039-11.459-21.484-30.795-19.336-48.699L35.98 163.45s-5.013-9.31-6.446-26.498l66.602-52.278c20.052-14.323 47.266-15.04 66.602 0 21.484 17.187 25.781 48.698 10.027 72.33l-48.699 69.466c-7.161 0-21.484-2.149-27.93-5.013'/></svg>",
- "architecture": "<svg fill='none' viewBox='0 0 24 24'><path fill='#66BB6A' d='M6.278 22 6 19.556l3.167-8.723a4.37 4.37 0 0 0 1.944 1.056l-3.055 8.389zm11.666 0-1.777-1.722-3.056-8.39q.556-.138 1.042-.402a4.4 4.4 0 0 0 .903-.653l3.166 8.723zm-5.833-11.111q-1.389 0-2.361-.972-.972-.973-.972-2.361 0-1.084.624-1.932.626-.846 1.598-1.18V2h2.222v2.444a3.27 3.27 0 0 1 1.598 1.18q.624.849.624 1.932 0 1.389-.972 2.36-.972.973-2.36.973Zm0-2.222q.473 0 .792-.32t.32-.791q0-.473-.32-.793a1.08 1.08 0 0 0-.792-.319q-.472 0-.791.32t-.32.792.32.79q.319.32.791.32Z'/></svg>",
- "arduino": "<svg viewBox='0 0 32 32'><path fill='#0097A7' d='M2 14h10v2H2zm22-4h2v10h-2z'/><path fill='#0097A7' d='M20 14h10v2H20z'/><path fill='none' stroke='#0097A7' stroke-width='2' d='M2 5h4a10 10 0 0 1 10 10 10 10 0 0 0 10 10h4'/><path fill='#0097A7' d='M11.644 22A8.95 8.95 0 0 1 6 24H2v2h4a10.98 10.98 0 0 0 8.479-4ZM26 4a10.98 10.98 0 0 0-8.479 4h2.835A8.95 8.95 0 0 1 26 6h4V4Z'/></svg>",
+ "appveyor": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 256 256'><path fill='#00b8d4' fill-rule='evenodd' d='M127.646 17.356c61.588 0 110.999 49.414 110.999 110.29a110.64 110.64 0 0 1-110.999 110.999c-60.873 0-110.29-49.414-110.29-110.999 0-60.873 49.414-110.29 110.29-110.29m27.213 131.77c-12.174 15.756-34.375 18.62-49.414 6.446-15.039-11.459-17.187-33.66-5.013-49.414 12.891-15.039 35.091-17.904 50.131-6.445 15.039 12.174 17.187 34.375 4.297 49.414zm-58.723 72.331 42.252-40.82c-15.756 3.58-32.227.716-45.117-10.026-15.039-11.459-21.484-30.795-19.336-48.699L35.98 163.45s-5.013-9.31-6.446-26.498l66.602-52.278c20.052-14.323 47.266-15.04 66.602 0 21.484 17.187 25.781 48.698 10.027 72.33l-48.699 69.466c-7.161 0-21.484-2.149-27.93-5.013'/></svg>",
+ "architecture": "<svg fill='none' viewBox='0 0 24 24'><path fill='#66bb6a' d='M6.278 22 6 19.556l3.167-8.723a4.37 4.37 0 0 0 1.944 1.056l-3.055 8.389zm11.666 0-1.777-1.722-3.056-8.39q.556-.138 1.042-.402a4.4 4.4 0 0 0 .903-.653l3.166 8.723zm-5.833-11.111q-1.389 0-2.361-.972-.972-.973-.972-2.361 0-1.084.624-1.932.626-.846 1.598-1.18V2h2.222v2.444a3.27 3.27 0 0 1 1.598 1.18q.624.849.624 1.932 0 1.389-.972 2.36-.972.973-2.36.973Zm0-2.222q.473 0 .792-.32t.32-.791q0-.473-.32-.793a1.08 1.08 0 0 0-.792-.319q-.472 0-.791.32t-.32.792.32.79q.319.32.791.32Z'/></svg>",
+ "arduino": "<svg viewBox='0 0 32 32'><path fill='#0097a7' d='M2 14h10v2H2zm22-4h2v10h-2z'/><path fill='#0097a7' d='M20 14h10v2H20z'/><path fill='none' stroke='#0097a7' stroke-width='2' d='M2 5h4a10 10 0 0 1 10 10 10 10 0 0 0 10 10h4'/><path fill='#0097a7' d='M11.644 22A8.95 8.95 0 0 1 6 24H2v2h4a10.98 10.98 0 0 0 8.479-4ZM26 4a10.98 10.98 0 0 0-8.479 4h2.835A8.95 8.95 0 0 1 26 6h4V4Z'/></svg>",
"asciidoc": "<svg viewBox='0 0 32 32'><path fill='#0097a7' d='M4 18V8l5.39 10Zm0 4v3.67A2.33 2.33 0 0 0 6.33 28h8.9l-3.496-6Zm12.444 0 3.177 5.444A11.88 11.88 0 0 0 26.448 22Zm11.419-4A15 15 0 0 0 28 16 12 12 0 0 0 16 4L6 3.995q-.08 0-.158.005L14 18Z'/></svg>",
"assembly": "<svg viewBox='0 0 32 32'><path fill='#ff6e40' d='M8 6V2H4a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h4v-4H4V6Zm16-4v4h4v20h-4v4h4a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2Zm-4 4h-2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2m-2 6V8h2v4Zm-4 6h-2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2m-2 6v-4h2v4Zm0-18c0 2 0 2-2 2v2h2v4h2V6Zm8 12c0 2 0 2-2 2v2h2v4h2v-8Z'/></svg>",
"astro-config": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h6v-4H6v-2h6v-2H6v-2h6v-2H6v-2h6v-2h2V4l8 8h2v-1Z'/><path fill='#7c4dff' d='M12 12v18h18V12Zm10 16c-.9 0-2.025-1.267-2.025-3.005-.914 0-.975.464-.975 1.005-.881-.213-1-1.15-1-2h6c0 1.919-2 1.787-2 4m2.542-6a2.5 2.5 0 0 1-2.308-1.641l-.946-2.42a.305.305 0 0 0-.576 0l-.946 2.42A2.5 2.5 0 0 1 17.458 22H16l2.965-7.59a.63.63 0 0 1 .577-.41h2.916a.63.63 0 0 1 .577.41L26 22Z'/></svg>",
"astro": "<svg viewBox='0 0 32 32'><path fill='#7c4dff' d='M12.106 25.849c-1.262-1.156-1.63-3.586-1.105-5.346a5.18 5.18 0 0 0 3.484 1.66 9.68 9.68 0 0 0 5.882-.734c.215-.106.413-.247.648-.39a3.5 3.5 0 0 1 .16 1.555 4.26 4.26 0 0 1-1.798 3.021c-.404.3-.832.569-1.25.852a2.613 2.613 0 0 0-1.15 3.372l.048.161a3.4 3.4 0 0 1-1.5-1.285 3.6 3.6 0 0 1-.578-1.962 9 9 0 0 0-.05-1.037c-.114-.831-.504-1.204-1.238-1.225a1.45 1.45 0 0 0-1.507 1.18c-.012.056-.028.112-.046.178M4.901 20a17.75 17.75 0 0 1 7.4-2l2.913-8.38a.765.765 0 0 1 1.527 0L19.7 18a14.24 14.24 0 0 1 7.399 2S20.704 2.877 20.692 2.842C20.51 2.33 20.202 2 19.787 2h-7.619c-.415 0-.71.33-.904.842z'/></svg>",
"astyle": "<svg viewBox='0 0 24 24'><path fill='#ef5350' d='M8.203 5.447 5.83 6.777l1.329-2.374-1.33-2.374 2.374 1.33 2.374-1.33-1.33 2.374 1.33 2.374zm11.394 9.305 2.374-1.329-1.33 2.374 1.33 2.373-2.374-1.329-2.374 1.33 1.33-2.374-1.33-2.374zm2.374-12.724-1.33 2.374 1.33 2.374-2.374-1.33-2.374 1.33 1.33-2.374-1.33-2.374 2.374 1.33zm-8.223 10.236 2.317-2.316-2.013-2.013-2.317 2.317zm.978-5.212 2.222 2.222c.37.35.37.968 0 1.338L5.867 21.694c-.37.37-.987.37-1.339 0l-2.222-2.221c-.37-.352-.37-.969 0-1.34l11.081-11.08c.37-.37.988-.37 1.34 0z'/></svg>",
"audio": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2m6 10h-4v8a4 4 0 1 1-4-4 3.96 3.96 0 0 1 2 .555V8h6Z'/></svg>",
- "aurelia": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24'><defs><linearGradient xlink:href='#a' id='i' x1='-31.824' x2='19.682' y1='-11.741' y2='35.548' gradientTransform='scale(.95818 1.0436)' gradientUnits='userSpaceOnUse'/><linearGradient id='a' x1='-3.881' x2='2.377' y1='-1.442' y2='4.304'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#7E57C2'/></linearGradient><linearGradient xlink:href='#b' id='j' x1='12.022' x2='-15.716' y1='13.922' y2='-23.952' gradientTransform='scale(.96226 1.0392)' gradientUnits='userSpaceOnUse'/><linearGradient id='b' x1='.729' x2='-.971' y1='.844' y2='-1.477'><stop offset='0' stop-color='#5E35B1'/><stop offset='.14' stop-color='#8E24AA'/><stop offset='.29' stop-color='#AD1457'/><stop offset='.84' stop-color='#C2185B'/><stop offset='1' stop-color='#EC407A'/></linearGradient><linearGradient xlink:href='#c' id='k' x1='-23.39' x2='23.931' y1='-57.289' y2='8.573' gradientTransform='scale(1.0429 .95884)' gradientUnits='userSpaceOnUse'/><linearGradient id='c' x1='-2.839' x2='2.875' y1='-6.936' y2='1.017'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#7E57C2'/></linearGradient><linearGradient xlink:href='#d' id='l' x1='-53.331' x2='6.771' y1='-30.517' y2='18.785' gradientTransform='scale(.99898 1.001)' gradientUnits='userSpaceOnUse'/><linearGradient id='d' x1='-8.212' x2='1.02' y1='-4.691' y2='2.882'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#7E57C2'/></linearGradient><linearGradient xlink:href='#e' id='m' x1='-14.029' x2='41.998' y1='-23.111' y2='26.259' gradientTransform='scale(1.0003 .99965)' gradientUnits='userSpaceOnUse'/><linearGradient id='e' x1='-1.404' x2='4.19' y1='-2.309' y2='2.62'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#7E57C2'/></linearGradient><linearGradient xlink:href='#f' id='n' x1='31.177' x2='3.37' y1='41.442' y2='3.402' gradientTransform='scale(.96254 1.0389)' gradientUnits='userSpaceOnUse'/><linearGradient id='f' x1='1.911' x2='.204' y1='2.539' y2='.204'><stop offset='0' stop-color='#7E57C2'/><stop offset='.14' stop-color='#7B1FA2'/><stop offset='.29' stop-color='#AD1457'/><stop offset='.84' stop-color='#C2185B'/><stop offset='1' stop-color='#EC407A'/></linearGradient><linearGradient xlink:href='#g' id='o' x1='-31.905' x2='19.599' y1='-14.258' y2='42.767' gradientTransform='scale(.95823 1.0436)' gradientUnits='userSpaceOnUse'/><linearGradient id='g' x1='-3.881' x2='2.377' y1='-1.738' y2='5.19'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#7E57C2'/></linearGradient><linearGradient xlink:href='#h' id='p' x1='4.301' x2='34.534' y1='34.41' y2='4.514' gradientTransform='scale(1.002 .99796)' gradientUnits='userSpaceOnUse'/><linearGradient id='h' x1='.112' x2='.901' y1='.897' y2='.116'><stop offset='0' stop-color='#7E57C2'/><stop offset='.14' stop-color='#8E24AA'/><stop offset='.53' stop-color='#C2185B'/><stop offset='.79' stop-color='#C2185B'/><stop offset='1' stop-color='#EC407A'/></linearGradient></defs><g stroke-linejoin='round' stroke-miterlimit='1.414' clip-rule='evenodd'><path fill='url(#i)' d='M8.002 6.127 4.117 8.719.116 2.723 4 .13z' transform='translate(11.282 3.07)scale(.47102)'/><path fill='url(#j)' d='m9.179 1.887 6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z' transform='translate(12.215 13.552)scale(.47102)'/><path fill='url(#k)' d='m7.3 1.88 1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z' transform='translate(8.41 16.686)scale(.47102)'/><path fill='url(#l)' d='M2.328 1.146 4.016.02l2.619 3.925L2.75 6.537l-1.46-2.19 2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z' transform='translate(16.99 11.686)scale(.47102)'/><path fill='url(#m)' d='m5.346 9.155-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z' transform='translate(2.738 8.18)scale(.47102)'/><path fill='url(#n)' d='m14.533 9.934 1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z' transform='translate(4.753 2.36)scale(.47102)'/><path fill='url(#o)' d='M6.235 7.177 4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z' transform='translate(11.32 3.106)scale(.47102)'/><path fill='#673AB7' d='m9.632 19.05-.545-.818 2.215-1.478.546.817zm7.965-5.315-.545-.817 1.035-.691.545.817z'/><path fill='#7E57C2' d='m5.256 12.492-.564-.845 2.216-1.478.563.845zm7.965-5.315-.564-.845 1.035-.69.564.844z'/><path fill='#880E4F' d='m16.538 14.441-3.724 2.485-.545-.817 3.724-2.485z'/><path fill='#AD1457' d='m11.598 7.039.564.844-3.724 2.485-.564-.844z'/><path fill='#AB47BC' d='m4.2 6.363.703 1.054-1.053.702-.703-1.053z'/><path fill='#7E57C2' d='m7.996 18.99.703 1.054-1.054.703-.702-1.054z'/><path fill='url(#p)' d='M8.372 38.294.017 29.876 29.749.08l8.636 8.201z' transform='rotate(11.282 -5.61 25.53)scale(.47102)'/></g></svg>",
+ "aurelia": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24'><defs><linearGradient xlink:href='#a' id='i' x1='-31.824' x2='19.682' y1='-11.741' y2='35.548' gradientTransform='scale(.95818 1.0436)' gradientUnits='userSpaceOnUse'/><linearGradient id='a' x1='-3.881' x2='2.377' y1='-1.442' y2='4.304'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#7e57c2'/></linearGradient><linearGradient xlink:href='#b' id='j' x1='12.022' x2='-15.716' y1='13.922' y2='-23.952' gradientTransform='scale(.96226 1.0392)' gradientUnits='userSpaceOnUse'/><linearGradient id='b' x1='.729' x2='-.971' y1='.844' y2='-1.477'><stop offset='0' stop-color='#5e35b1'/><stop offset='.14' stop-color='#8e24aa'/><stop offset='.29' stop-color='#ad1457'/><stop offset='.84' stop-color='#c2185b'/><stop offset='1' stop-color='#ec407a'/></linearGradient><linearGradient xlink:href='#c' id='k' x1='-23.39' x2='23.931' y1='-57.289' y2='8.573' gradientTransform='scale(1.0429 .95884)' gradientUnits='userSpaceOnUse'/><linearGradient id='c' x1='-2.839' x2='2.875' y1='-6.936' y2='1.017'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#7e57c2'/></linearGradient><linearGradient xlink:href='#d' id='l' x1='-53.331' x2='6.771' y1='-30.517' y2='18.785' gradientTransform='scale(.99898 1.001)' gradientUnits='userSpaceOnUse'/><linearGradient id='d' x1='-8.212' x2='1.02' y1='-4.691' y2='2.882'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#7e57c2'/></linearGradient><linearGradient xlink:href='#e' id='m' x1='-14.029' x2='41.998' y1='-23.111' y2='26.259' gradientTransform='scale(1.0003 .99965)' gradientUnits='userSpaceOnUse'/><linearGradient id='e' x1='-1.404' x2='4.19' y1='-2.309' y2='2.62'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#7e57c2'/></linearGradient><linearGradient xlink:href='#f' id='n' x1='31.177' x2='3.37' y1='41.442' y2='3.402' gradientTransform='scale(.96254 1.0389)' gradientUnits='userSpaceOnUse'/><linearGradient id='f' x1='1.911' x2='.204' y1='2.539' y2='.204'><stop offset='0' stop-color='#7e57c2'/><stop offset='.14' stop-color='#7b1fa2'/><stop offset='.29' stop-color='#ad1457'/><stop offset='.84' stop-color='#c2185b'/><stop offset='1' stop-color='#ec407a'/></linearGradient><linearGradient xlink:href='#g' id='o' x1='-31.905' x2='19.599' y1='-14.258' y2='42.767' gradientTransform='scale(.95823 1.0436)' gradientUnits='userSpaceOnUse'/><linearGradient id='g' x1='-3.881' x2='2.377' y1='-1.738' y2='5.19'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#7e57c2'/></linearGradient><linearGradient xlink:href='#h' id='p' x1='4.301' x2='34.534' y1='34.41' y2='4.514' gradientTransform='scale(1.002 .99796)' gradientUnits='userSpaceOnUse'/><linearGradient id='h' x1='.112' x2='.901' y1='.897' y2='.116'><stop offset='0' stop-color='#7e57c2'/><stop offset='.14' stop-color='#8e24aa'/><stop offset='.53' stop-color='#c2185b'/><stop offset='.79' stop-color='#c2185b'/><stop offset='1' stop-color='#ec407a'/></linearGradient></defs><g stroke-linejoin='round' stroke-miterlimit='1.414' clip-rule='evenodd'><path fill='url(#i)' d='M8.002 6.127 4.117 8.719.116 2.723 4 .13z' transform='translate(11.282 3.07)scale(.47102)'/><path fill='url(#j)' d='m9.179 1.887 6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z' transform='translate(12.215 13.552)scale(.47102)'/><path fill='url(#k)' d='m7.3 1.88 1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z' transform='translate(8.41 16.686)scale(.47102)'/><path fill='url(#l)' d='M2.328 1.146 4.016.02l2.619 3.925L2.75 6.537l-1.46-2.19 2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z' transform='translate(16.99 11.686)scale(.47102)'/><path fill='url(#m)' d='m5.346 9.155-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z' transform='translate(2.738 8.18)scale(.47102)'/><path fill='url(#n)' d='m14.533 9.934 1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z' transform='translate(4.753 2.36)scale(.47102)'/><path fill='url(#o)' d='M6.235 7.177 4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z' transform='translate(11.32 3.106)scale(.47102)'/><path fill='#673ab7' d='m9.632 19.05-.545-.818 2.215-1.478.546.817zm7.965-5.315-.545-.817 1.035-.691.545.817z'/><path fill='#7e57c2' d='m5.256 12.492-.564-.845 2.216-1.478.563.845zm7.965-5.315-.564-.845 1.035-.69.564.844z'/><path fill='#880e4f' d='m16.538 14.441-3.724 2.485-.545-.817 3.724-2.485z'/><path fill='#ad1457' d='m11.598 7.039.564.844-3.724 2.485-.564-.844z'/><path fill='#ab47bc' d='m4.2 6.363.703 1.054-1.053.702-.703-1.053z'/><path fill='#7e57c2' d='m7.996 18.99.703 1.054-1.054.703-.702-1.054z'/><path fill='url(#p)' d='M8.372 38.294.017 29.876 29.749.08l8.636 8.201z' transform='rotate(11.282 -5.61 25.53)scale(.47102)'/></g></svg>",
"authors": "<svg viewBox='0 0 24 24'><path fill='#f44336' d='M15.787 13.71c-.275 0-.587 0-.918.047 1.098.796 1.865 1.847 1.865 3.267v2.367h5.68v-2.367c0-2.206-4.42-3.314-6.627-3.314m-7.575 0c-2.206 0-6.628 1.108-6.628 3.314v2.367H14.84v-2.367c0-2.206-4.421-3.314-6.628-3.314m0-1.894a2.84 2.84 0 0 0 2.841-2.84 2.84 2.84 0 0 0-2.84-2.84 2.84 2.84 0 0 0-2.841 2.84 2.84 2.84 0 0 0 2.84 2.84m7.575 0a2.84 2.84 0 0 0 2.84-2.84 2.84 2.84 0 0 0-2.84-2.84 2.84 2.84 0 0 0-2.84 2.84 2.84 2.84 0 0 0 2.84 2.84'/></svg>",
"auto": "<svg viewBox='0 0 24 24'><path fill='#ffc400' d='M8.48 4.17c.334.574 1.047.798 1.696.636A7.5 7.5 0 0 1 12 4.583c.62 0 1.223.075 1.799.217.647.159 1.357-.065 1.691-.64.39-.668.116-1.532-.63-1.751A10.1 10.1 0 0 0 12 2c-1.006 0-1.977.146-2.894.419-.743.22-1.015 1.083-.627 1.75Z'/><path fill='#ad1457' d='M5.039 4.772c.564-.535 1.458-.34 1.848.331.333.572.176 1.292-.284 1.769a7.4 7.4 0 0 0-1.456 2.17c-.242.552-.762.958-1.367.958-.854 0-1.496-.781-1.191-1.572a10 10 0 0 1 2.45-3.656'/><path fill='#cfd8dc' d='M3.197 12c.718 0 1.32.583 1.444 1.286.613 3.483 3.675 6.13 7.359 6.13s6.746-2.647 7.359-6.13c.124-.703.726-1.286 1.444-1.286.719 0 1.279.581 1.187 1.289C21.353 18.203 17.123 22 12 22s-9.353-3.797-9.99-8.711C1.918 12.58 2.478 12 3.197 12'/><path fill='#ff5252' d='M20.203 9.958c.857 0 1.5-.786 1.19-1.578a10 10 0 0 0-2.458-3.632c-.564-.533-1.455-.336-1.845.333-.333.573-.174 1.295.289 1.772a7.4 7.4 0 0 1 1.459 2.155c.243.548.762.95 1.365.95'/><path fill='#cfd8dc' d='M7.133 9.32c-.442-.488.053-1.262.657-1.027l4.912 1.91c1.114.434 1.538 1.84.862 2.855a1.785 1.785 0 0 1-2.83.222l-3.6-3.96Z'/></svg>",
"auto_light": "<svg viewBox='0 0 24 24'><path fill='#ffc400' d='M8.48 4.17c.334.574 1.047.798 1.696.636A7.5 7.5 0 0 1 12 4.583c.62 0 1.223.075 1.799.217.647.159 1.357-.065 1.691-.64.39-.668.116-1.532-.63-1.751A10.1 10.1 0 0 0 12 2c-1.006 0-1.977.146-2.894.419-.743.22-1.015 1.083-.627 1.75Z'/><path fill='#ad1457' d='M5.039 4.772c.564-.535 1.458-.34 1.848.331.333.572.176 1.292-.284 1.769a7.4 7.4 0 0 0-1.456 2.17c-.242.552-.762.958-1.367.958-.854 0-1.496-.781-1.191-1.572a10 10 0 0 1 2.45-3.656'/><path fill='#546e7a' d='M3.197 12c.718 0 1.32.583 1.444 1.286.613 3.483 3.675 6.13 7.359 6.13s6.746-2.647 7.359-6.13c.124-.703.726-1.286 1.444-1.286.719 0 1.279.581 1.187 1.289C21.353 18.203 17.123 22 12 22s-9.353-3.797-9.99-8.711C1.918 12.58 2.478 12 3.197 12'/><path fill='#ff5252' d='M20.203 9.958c.857 0 1.5-.786 1.19-1.578a10 10 0 0 0-2.458-3.632c-.564-.533-1.455-.336-1.845.333-.333.573-.174 1.295.289 1.772a7.4 7.4 0 0 1 1.459 2.155c.243.548.762.95 1.365.95'/><path fill='#546e7a' d='M7.133 9.32c-.442-.488.053-1.262.657-1.027l4.912 1.91c1.114.434 1.538 1.84.862 2.855a1.785 1.785 0 0 1-2.83.222l-3.6-3.96Z'/></svg>",
@@ -58,13 +58,14 @@
"bibliography": "<svg viewBox='0 0 1024 1024'><path fill='#795548' d='M96 832h832c17.728 0 32 14.272 32 32v64c0 17.728-14.272 32-32 32H96c-17.728 0-32-14.272-32-32v-64c0-17.728 14.272-32 32-32'/><path fill='#4caf50' d='M160 192h64c17.728 0 32 14.272 32 32v512c0 17.728-14.272 32-32 32h-64c-17.728 0-32-14.272-32-32V224c0-17.728 14.272-32 32-32'/><path fill='#f44336' d='M512 96c0-17.728-14.272-32-32-32H352c-17.728 0-32 14.272-32 32v640c0 17.728 14.272 32 32 32h128c17.728 0 32-14.272 32-32z'/><path fill='#2196f3' d='m530.161 158.902 57.333-27.693a31.804 31.804 0 0 1 42.634 14.936l262.693 548.17c7.66 15.984.977 35.057-14.982 42.766l-57.333 27.693a31.804 31.804 0 0 1-42.634-14.936L515.18 201.668c-7.66-15.983-.977-35.057 14.982-42.766z'/><path fill='#ffeb3b' d='M320 192v64h192v-64zm0 384v64h192v-64z'/></svg>",
"bibtex-style": "<svg viewBox='0 0 1024 1024'><path fill='#795548' d='M96 832h832c17.728 0 32 14.272 32 32v64c0 17.728-14.272 32-32 32H96c-17.728 0-32-14.272-32-32v-64c0-17.728 14.272-32 32-32'/><path fill='#4caf50' d='M160 192h64c17.728 0 32 14.272 32 32v512c0 17.728-14.272 32-32 32h-64c-17.728 0-32-14.272-32-32V224c0-17.728 14.272-32 32-32'/><path fill='#f44336' d='M512 96c0-17.728-14.272-32-32-32H352c-17.728 0-32 14.272-32 32v640c0 17.728 14.272 32 32 32h128c17.728 0 32-14.272 32-32z'/><path fill='#ffeb3b' d='M320 192v64h192v-64zm0 384v64h192v-64z'/><path fill='#bbdefb' d='M608 320h256c17.728 0 32 14.272 32 32v384c0 17.728-14.272 32-32 32H608c-17.728 0-32-14.272-32-32V352c0-17.728 14.272-32 32-32'/><path fill='#2196f3' d='M608 320c-17.673 0-32 14.327-32 32v352c35.346 0 64-28.654 64-64v-32a32 32 0 0 1 32-32c17.673 0 32-14.327 32-32v-64a32 32 0 0 1 32-32c17.673 0 32-14.327 32-32v-96z'/><path d='M745.606 339.205 924.74 473.693a15.965 15.965 0 0 1 3.19 22.401 15.965 15.965 0 0 1-22.403 3.19l-179.133-134.49a15.965 15.965 0 0 1-3.19-22.401 15.965 15.965 0 0 1 22.402-3.19z'/></svg>",
"bicep": "<svg viewBox='0 0 24 24'><path fill='#fbc02d' d='M3 18S4.15 6.885 7 3l5 1-1 3H9v7h1c1.9-2.915 5.783-3.98 8.157-2.915 4.475 1.915 2.998 5.967.148 7.905C16.025 20.548 10.113 23.05 3 18'/></svg>",
- "biome": "<svg viewBox='0 0 74 74'><path fill='#42A5F5' d='M37 9 22.745 33.69a32.2 32.2 0 0 1 16.869-.584l4.818 1.137-4.533 19.22-4.825-1.137c-5.93-1.399-11.628 1.716-14.036 6.685l-4.46-2.158c3.404-7.029 11.425-11.285 19.637-9.347l2.259-9.58A27.23 27.23 0 0 0 5 64.424l64 .001z'/></svg>",
+ "biome": "<svg viewBox='0 0 74 74'><path fill='#42a5f5' d='M37 9 22.745 33.69a32.2 32.2 0 0 1 16.869-.584l4.818 1.137-4.533 19.22-4.825-1.137c-5.93-1.399-11.628 1.716-14.036 6.685l-4.46-2.158c3.404-7.029 11.425-11.285 19.637-9.347l2.259-9.58A27.23 27.23 0 0 0 5 64.424l64 .001z'/></svg>",
"bitbucket": "<svg viewBox='0 0 24 24'><defs><linearGradient id='a' x1='64.01' x2='32.99' y1='65.26' y2='89.48' gradientUnits='userSpaceOnUse'><stop offset='.18' stop-color='#1565c0'/><stop offset='1' stop-color='#1e88e5'/></linearGradient></defs><path fill='#1e88e5' d='M2.985 3.333a.618.618 0 0 0-.617.716l2.621 15.914a.84.84 0 0 0 .822.701h12.576a.62.62 0 0 0 .618-.519l2.627-16.09a.618.618 0 0 0-.617-.716zm11.039 11.501H10.01L8.923 9.16h6.074z'/><path fill='url(#a)' d='M59.67 60.12H40.9L37.75 78.5h-13L9.4 96.73a2.7 2.7 0 0 0 1.75.66h40.74a2 2 0 0 0 2-1.68z' transform='translate(2.368 -9.404)scale(.30877)'/></svg>",
"bithound": "<svg fill-opacity='.05' viewBox='0 0 400 400'><g fill='#e53935' fill-opacity='1'><path d='M350.738 186.163c-1.32-13.024-4.224-26.312-8.36-38.72-11.88-35.464-33.968-71.808-61.864-96.888-1.232-1.056-5.896-3.872-7.656-2.904-4.576 2.552 4.048 20.064 5.104 23.232 6.512 19.36 10.648 39.864 5.984 60.104-6.248 26.752-26.752 45.496-54.12 47.784-15.048 1.232-30.184-.44-45.232 1.32-22.528 2.64-45.496 10.384-59.84 28.864-1.672 2.112-3.168 4.488-4.576 6.952h-.352c-5.544.616-11.088-1.76-13.816-3.256-.704-.44-1.408-.792-1.936-1.056-16.72-9.24-29.04-29.92-36.608-46.992-3.432-7.92-6.336-16.192-8.184-24.552-.88-3.784-.968-7.744-1.144-11.616-.088-2.376.264-5.72-1.056-7.832-2.904-4.576-6.6-.176-7.216 3.52-.968 6.072-1.848 12.056-1.584 18.216.44 10.384 3.344 20.68 7.04 30.36 5.456 14.256 13.112 27.368 23.056 39.072 4.136 4.84 8.536 9.328 13.288 13.464 4.224 3.784 9.592 6.776 12.76 11.616 3.696 5.544 4.312 12.408 3.96 18.832-.88 16.984-1.408 32.912 3.432 49.456 4.224 14.696 9.504 29.744 18.304 42.328 4.4 6.248 9.856 12.848 15.84 17.512 4.048 3.168 11.704 3.52 7.304-8.096-9.768-25.784-10.648-52.536 4.576-76.648 12.76-20.064 35.288-37.928 60.72-34.76 37.4 4.664 63.448 38.984 61.6 75.68-.528 10.296-.88 19.096-4.136 28.776-1.32 3.872-2.288 8.8-1.32 12.848 1.584 6.864 9.24 4.312 12.584-.176 9.064-12.32 18.568-24.288 27.104-36.96 27.808-41.536 41.36-89.584 36.344-139.48'/><path d='M141.21 85.051c.616 2.024 1.232 4.224 1.672 6.6.088.968.352 2.024.88 2.992 2.288 5.984 7.832 9.24 13.024 12.32 3.168 1.936 8.888 3.784 12.408 5.192 4.576 1.848 14.432-.528 19.096-.88 10.736-.88 20.68-4.664 30.536 1.056-50.512 59.224-2.816 72.424 34.144 43.912 42.24-32.56 2.464-109.384 2.464-109.384s-.88-5.984-16.896-9.504a52 52 0 0 0-4.488-2.112c-15.84-7.304-30.096 4.664-41.536 14.432-3.344 2.816-6.6 5.632-10.12 8.272-4.752 3.52-9.856 6.424-15.224 8.976-5.632 2.64-12.32 5.632-18.568 5.896-.88 0-2.552.176-4.312.528-2.728.264-4.136.968-4.752 2.2-1.056.88-1.76 2.112-1.584 3.696.176 2.2 1.232 4.048 2.376 5.456.352.088.616.264.88.352'/></g></svg>",
+ "blender": "<svg viewBox='0 0 32 32'><g fill='#ff9800' fill-rule='evenodd'><path d='M14.004 19a5 5 0 0 1 4.993-5.001 5 5 0 0 1 5.008 4.985A5 5 0 0 1 19.028 24a5 5 0 0 1-5.024-4.97z' paint-order='stroke fill markers'/><path d='m23.148 7.152-4.355-4.451s-1.488-1.534-3.022-.046-.046 3.022-.046 3.022l.165.264c.693.69 1.412 1.368 2.105 2.058h-12s-2 0-2 2 2 2 2 2h4l-6 6-1.332 1.41s-1.503 1.503 0 3.007c1.503 1.503 3.006 0 3.006 0l2.327-2.417.063.19q.06.555.173 1.09a11.02 11.02 0 0 0 8.599 8.508 11 11 0 0 0 2.216.213c6.068-.028 10.967-4.965 10.949-11.034 0-5.452-4-8.967-6.848-11.815zm-4.162 4.847a7 7 0 1 1-6.99 7.044V19a7 7 0 0 1 6.99-7'/></g></svg>",
"blink": "<svg viewBox='0 0 256 256'><path fill='#f9a825' d='M130.974 23.383c57.809 1.624 103.262 49.782 101.638 107.591s-49.782 103.262-107.59 101.639C67.303 230.989 21.85 183.01 23.383 125.293c1.533-57.81 49.602-103.443 107.41-101.91zm-.541 10.823c-51.766-1.353-94.875 39.59-96.137 91.447-1.353 51.766 39.59 94.875 91.357 96.137 51.766 1.353 94.875-39.59 96.137-91.357 1.443-51.856-39.5-94.875-91.357-96.227.09 0 0 0 0 0'/><path fill='#f9a825' d='M137.9 93.403c-4.149 3.968-2.525 12.806 3.878 19.57s15.241 8.838 19.209 4.78 2.706-12.987-3.788-19.751-15.422-8.748-19.57-4.78zm52.217-25.162c8.207 8.568 14.43 18.758 18.398 29.851 0 0 2.706 7.395-5.862 7.395H181.73s-6.674-.54-6.944 5.14c-.451 7.035-.27 12.988-.27 12.988.27 4.058 1.803 8.026 4.328 11.183l21.554 22.727a9.184 9.184 0 0 1 1.082 12.355c-10.912 18.578-28.408 32.286-49.06 38.509-6.314 1.894-6.945-3.247-6.765-6.764 0-2.255 2.616-52.397 2.616-52.397.721-5.411-.992-10.912-4.78-14.881-6.764-7.215-11.003-11.814-11.003-11.814s-4.78-4.78-11.634-11.904c-3.788-3.878-9.018-5.862-14.43-5.501H54.027c-3.427 0-8.658-.812-6.403-7.035 7.305-20.292 21.915-37.066 41.124-46.896 3.878-2.705 9.199-1.984 12.265 1.714l21.554 22.727c3.066 2.705 6.944 4.329 11.003 4.78 0 0 5.862.45 12.987.36 5.591 0 5.501-6.764 5.501-6.764s.902-13.888 1.173-20.923c-.27-2.976 1.894-5.591 4.78-5.862.991-.09 1.893.09 2.795.451 11.003 4.69 21.013 11.634 29.22 20.292z'/></svg>",
"blink_light": "<svg viewBox='0 0 256 256'><circle cx='128' cy='128' r='97.4' fill='#37474f'/><path fill='#f9a825' d='M130.974 23.383c57.809 1.624 103.262 49.782 101.638 107.591s-49.782 103.262-107.59 101.639C67.303 230.989 21.85 183.01 23.383 125.293c1.533-57.81 49.602-103.443 107.41-101.91zm-.541 10.823c-51.766-1.353-94.875 39.59-96.137 91.447-1.353 51.766 39.59 94.875 91.357 96.137 51.766 1.353 94.875-39.59 96.137-91.357 1.443-51.856-39.5-94.875-91.357-96.227.09 0 0 0 0 0'/><path fill='#f9a825' d='M137.9 93.403c-4.149 3.968-2.525 12.806 3.878 19.57s15.241 8.838 19.209 4.78 2.706-12.987-3.788-19.751-15.422-8.748-19.57-4.78zm52.217-25.162c8.207 8.568 14.43 18.758 18.398 29.851 0 0 2.706 7.395-5.862 7.395H181.73s-6.674-.54-6.944 5.14c-.451 7.035-.27 12.988-.27 12.988.27 4.058 1.803 8.026 4.328 11.183l21.554 22.727a9.184 9.184 0 0 1 1.082 12.355c-10.912 18.578-28.408 32.286-49.06 38.509-6.314 1.894-6.945-3.247-6.765-6.764 0-2.255 2.616-52.397 2.616-52.397.721-5.411-.992-10.912-4.78-14.881-6.764-7.215-11.003-11.814-11.003-11.814s-4.78-4.78-11.634-11.904c-3.788-3.878-9.018-5.862-14.43-5.501H54.027c-3.427 0-8.658-.812-6.403-7.035 7.305-20.292 21.915-37.066 41.124-46.896 3.878-2.705 9.199-1.984 12.265 1.714l21.554 22.727c3.066 2.705 6.944 4.329 11.003 4.78 0 0 5.862.45 12.987.36 5.591 0 5.501-6.764 5.501-6.764s.902-13.888 1.173-20.923c-.27-2.976 1.894-5.591 4.78-5.862.991-.09 1.893.09 2.795.451 11.003 4.69 21.013 11.634 29.22 20.292z'/></svg>",
"blitz": "<svg viewBox='0 0 24 24'><path fill='#7c4dff' d='M8.613 11.997c1.333 0 2.588.621 3.389 1.677l3.454 4.552a.28.28 0 0 1 .025.297l-1.991 3.825a.284.284 0 0 1-.477.04l-7.901-10.39zm2.375-10.385 7.9 10.39h-3.5a4.25 4.25 0 0 1-3.39-1.676L8.546 5.774a.28.28 0 0 1-.025-.297l1.99-3.825a.284.284 0 0 1 .478-.04z'/></svg>",
- "bower": "<svg viewBox='0 0 400 400'><path fill='#5D4037' d='M376.834 196.261c-18.912-18.172-113.486-29.517-143.327-32.819a88 88 0 0 0 3.692-10.58c4.068-1.78 8.46-3.438 13-4.822.553 1.632 3.159 7.885 4.644 10.853 60.004 1.655 63.085-44.591 65.525-57.26 2.387-12.389 2.265-24.359 22.847-46.241-30.663-8.936-74.759 13.85-89.53 47.762-5.55-2.08-11.114-3.615-16.615-4.565-3.943-15.905-24.474-60.215-78.352-60.215-68.215 0-142.567 56.276-142.567 151.542 0 80.078 54.672 150.258 85.559 150.258 13.49 0 25.094-10.103 27.818-19.158 2.284 6.209 9.292 25.51 11.593 30.424 3.402 7.267 19.134 13.554 26.018 6.014 8.852 4.917 25.095 7.88 33.947-5.235 17.049 3.606 32.12-6.56 32.45-18.691 8.365-.447 12.469-12.193 10.642-21.547-1.346-6.887-15.732-31.599-21.343-40.13 11.108 9.035 39.243 11.593 42.66.006 17.909 14.057 45.817 6.679 48.03-4.753 21.761 5.654 46.72-6.764 42.621-21.803 34.958-2.418 30.483-39.611 20.675-49.037z'/><path fill='#03A9F4' d='M279.494 116.935c7.529-14.938 16.99-31.25 28.94-41.34-13.153 5.3-26.139 21.146-33.817 38.083a118 118 0 0 0-11.893-6.646c10.71-22.862 35.598-41.955 63.025-43.447-18.37 16.662-11.85 51.29-26.954 69.623-4.322-4.342-14.247-12.72-19.301-16.273m-11.876 24.326c.008-.572.222-4.981.624-6.994-1.054-.248-7.601-1.529-11.015-1.449-.249 4.288 1.802 11.581 3.828 15.972 13.956-.292 24.036-4.472 29.969-8.314-5.051-2.354-13.67-4.448-20.224-5.7-.732 1.513-2.531 5.368-3.182 6.485'/><g stroke-width='.973' transform='translate(10.989 32.73)scale(.81733)'><path fill='#4CAF50' d='M250.54 277.39c.004.024.015.057.018.082-2.165-4.657-4.463-10.314-7.208-17.708 10.688 15.557 44.184 7.533 42.427-6.407 16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455 28 5.4 54.832 10.783 63.256 12.938-5.595 9.123-18.339 15.566-37.549 11.089 10.38 14.14-9.773 31.105-37.844 21.76 6.18 13.883-18.814 26.38-47.22 11.91.361 13.889-35.24 15.488-49.315.143zm55.543-70.194c32.497 2.495 86.238 7.34 119.51 11.997-2.102-10.828-7.844-13.921-25.905-18.772-19.425 2.072-68.706 6.913-93.604 6.776z'/><path fill='#FFCA28' d='M285.78 253.36c16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455-33.103-6.383-67.84-12.788-75.719-13.908 4.78.254 12.702.797 22.59 1.556 24.899.137 74.18-4.704 93.604-6.775-31.452-7.975-95.666-19.613-140.01-22.48-2.055 3.003-5.833 8.097-12.413 13.51-19.403 41.053-54.557 68.34-93.454 68.34-11.335 0-24.018-1.912-38.233-6.456-8.865 9.497-46.661 16.694-77.329 1.641 24.326 56.961 80.74 94.984 143.19 94.984 52.591 0 75.912-53.704 70.808-67.914-1.238-3.45-6.145-14.889-8.891-22.283 10.689 15.556 44.185 7.532 42.429-6.408z'/><path fill='#E0E0E0' d='M253.91 145.27c4.644-2.526 20.69-12.253 35.981-15.908a68 68 0 0 1-.536-5.12c-10.032 2.403-28.945 10.51-39.784-.661 22.866 6.9 34.283-6.149 51.09-6.149 10.014 0 24.305 2.798 35.57 7.22-9.061-8.37-38.772-33.63-75.558-33.717-8.213 9.957-17.09 31.526-6.764 54.334z'/><path fill='#F4511E' d='M115.58 253.33c14.215 4.544 26.898 6.457 38.233 6.457 38.896 0 74.05-27.29 93.454-68.341-14.351 11.978-39.291 22.228-78.241 22.228 34.694-7.866 64.56-25.156 79.753-50.427-10.68-16.998-22.263-54.603 7.07-84.33-4.512-14.497-26.475-52.766-75.095-52.766-84.85 0-155.17 71.001-155.17 166.15 0 22.525 4.547 43.65 12.67 62.664 30.666 15.054 68.462 7.858 77.327-1.64z'/><path fill='#FFCA28' d='M141.03 108.45c0 21.644 17.546 39.191 39.19 39.191s39.192-17.548 39.192-39.191-17.548-39.191-39.192-39.191-39.19 17.547-39.19 39.191'/><path fill='#5D4037' d='M156.76 108.45c0 12.958 10.507 23.463 23.463 23.463 12.96 0 23.464-10.506 23.464-23.463 0-12.959-10.504-23.464-23.464-23.464-12.957 0-23.463 10.506-23.463 23.464'/><ellipse cx='180.22' cy='98.044' fill='#FAFAFA' rx='13.673' ry='8.501'/></g></svg>",
+ "bower": "<svg viewBox='0 0 400 400'><path fill='#5d4037' d='M376.834 196.261c-18.912-18.172-113.486-29.517-143.327-32.819a88 88 0 0 0 3.692-10.58c4.068-1.78 8.46-3.438 13-4.822.553 1.632 3.159 7.885 4.644 10.853 60.004 1.655 63.085-44.591 65.525-57.26 2.387-12.389 2.265-24.359 22.847-46.241-30.663-8.936-74.759 13.85-89.53 47.762-5.55-2.08-11.114-3.615-16.615-4.565-3.943-15.905-24.474-60.215-78.352-60.215-68.215 0-142.567 56.276-142.567 151.542 0 80.078 54.672 150.258 85.559 150.258 13.49 0 25.094-10.103 27.818-19.158 2.284 6.209 9.292 25.51 11.593 30.424 3.402 7.267 19.134 13.554 26.018 6.014 8.852 4.917 25.095 7.88 33.947-5.235 17.049 3.606 32.12-6.56 32.45-18.691 8.365-.447 12.469-12.193 10.642-21.547-1.346-6.887-15.732-31.599-21.343-40.13 11.108 9.035 39.243 11.593 42.66.006 17.909 14.057 45.817 6.679 48.03-4.753 21.761 5.654 46.72-6.764 42.621-21.803 34.958-2.418 30.483-39.611 20.675-49.037z'/><path fill='#03a9f4' d='M279.494 116.935c7.529-14.938 16.99-31.25 28.94-41.34-13.153 5.3-26.139 21.146-33.817 38.083a118 118 0 0 0-11.893-6.646c10.71-22.862 35.598-41.955 63.025-43.447-18.37 16.662-11.85 51.29-26.954 69.623-4.322-4.342-14.247-12.72-19.301-16.273m-11.876 24.326c.008-.572.222-4.981.624-6.994-1.054-.248-7.601-1.529-11.015-1.449-.249 4.288 1.802 11.581 3.828 15.972 13.956-.292 24.036-4.472 29.969-8.314-5.051-2.354-13.67-4.448-20.224-5.7-.732 1.513-2.531 5.368-3.182 6.485'/><g stroke-width='.973' transform='translate(10.989 32.73)scale(.81733)'><path fill='#4caf50' d='M250.54 277.39c.004.024.015.057.018.082-2.165-4.657-4.463-10.314-7.208-17.708 10.688 15.557 44.184 7.533 42.427-6.407 16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455 28 5.4 54.832 10.783 63.256 12.938-5.595 9.123-18.339 15.566-37.549 11.089 10.38 14.14-9.773 31.105-37.844 21.76 6.18 13.883-18.814 26.38-47.22 11.91.361 13.889-35.24 15.488-49.315.143zm55.543-70.194c32.497 2.495 86.238 7.34 119.51 11.997-2.102-10.828-7.844-13.921-25.905-18.772-19.425 2.072-68.706 6.913-93.604 6.776z'/><path fill='#ffca28' d='M285.78 253.36c16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455-33.103-6.383-67.84-12.788-75.719-13.908 4.78.254 12.702.797 22.59 1.556 24.899.137 74.18-4.704 93.604-6.775-31.452-7.975-95.666-19.613-140.01-22.48-2.055 3.003-5.833 8.097-12.413 13.51-19.403 41.053-54.557 68.34-93.454 68.34-11.335 0-24.018-1.912-38.233-6.456-8.865 9.497-46.661 16.694-77.329 1.641 24.326 56.961 80.74 94.984 143.19 94.984 52.591 0 75.912-53.704 70.808-67.914-1.238-3.45-6.145-14.889-8.891-22.283 10.689 15.556 44.185 7.532 42.429-6.408z'/><path fill='#e0e0e0' d='M253.91 145.27c4.644-2.526 20.69-12.253 35.981-15.908a68 68 0 0 1-.536-5.12c-10.032 2.403-28.945 10.51-39.784-.661 22.866 6.9 34.283-6.149 51.09-6.149 10.014 0 24.305 2.798 35.57 7.22-9.061-8.37-38.772-33.63-75.558-33.717-8.213 9.957-17.09 31.526-6.764 54.334z'/><path fill='#f4511e' d='M115.58 253.33c14.215 4.544 26.898 6.457 38.233 6.457 38.896 0 74.05-27.29 93.454-68.341-14.351 11.978-39.291 22.228-78.241 22.228 34.694-7.866 64.56-25.156 79.753-50.427-10.68-16.998-22.263-54.603 7.07-84.33-4.512-14.497-26.475-52.766-75.095-52.766-84.85 0-155.17 71.001-155.17 166.15 0 22.525 4.547 43.65 12.67 62.664 30.666 15.054 68.462 7.858 77.327-1.64z'/><path fill='#ffca28' d='M141.03 108.45c0 21.644 17.546 39.191 39.19 39.191s39.192-17.548 39.192-39.191-17.548-39.191-39.192-39.191-39.19 17.547-39.19 39.191'/><path fill='#5d4037' d='M156.76 108.45c0 12.958 10.507 23.463 23.463 23.463 12.96 0 23.464-10.506 23.464-23.463 0-12.959-10.504-23.464-23.464-23.464-12.957 0-23.463 10.506-23.463 23.464'/><ellipse cx='180.22' cy='98.044' fill='#fafafa' rx='13.673' ry='8.501'/></g></svg>",
"brainfuck": "<svg viewBox='0 0 24 24'><path fill='#ff4081' d='M21.085 13.343a4.35 4.35 0 0 1-1.812 3.788l.738 1.429c.22.431.25.94.058 1.39a1.68 1.68 0 0 1-1.017.959l-.758.24a1.62 1.62 0 0 1-1.784-.527l-2.032-2.398a5.1 5.1 0 0 1-2.34-1.055 5 5 0 0 1-1.438.22 4.2 4.2 0 0 1-2.398-.757 5 5 0 0 1-1.553.211 5.6 5.6 0 0 1-2.206-.431 3.94 3.94 0 0 1-2.33-3.462c-.077-.69.038-1.39.336-2.024a3.3 3.3 0 0 1-.068-2.234 4.3 4.3 0 0 1 1.86-2.148c.557-1.62 2.12-2.704 3.837-2.589a4.404 4.404 0 0 1 5.59-.355 5 5 0 0 1 1.247-.163A4.16 4.16 0 0 1 18.37 5.01a4.61 4.61 0 0 1 3.433 4.286 5.05 5.05 0 0 1-.825 3.002c.067.345.106.69.106 1.045m-4.795-1.352c.547.067.978.48.978 1.026a.96.96 0 0 1-.959.959h-.604a4.97 4.97 0 0 1-1.553 2.196c.24.086.489.134.738.201 4.92-.067 4.344-3.068 4.344-3.116a2.486 2.486 0 0 0-2.58-2.388.96.96 0 0 1-.958-.959.96.96 0 0 1 .958-.959c1.18.029 2.312.47 3.194 1.247a5 5 0 0 0 .076-.854c-.057-1.189-.594-2.224-2.752-2.426-1.198-2.838-4.22-1.266-4.22-.383-.028.22.202.69.24.719a.96.96 0 0 1 .96.959.96.96 0 0 1-.96.959 2.25 2.25 0 0 1-1.37-.537c-.461.297-.988.48-1.535.537-.547.048-.997-.336-1.026-.863a.93.93 0 0 1 .844-1.055c.153-.02.901-.134.901-.739 0-.632.24-1.237.652-1.716-.882-.24-1.831.077-2.79 1.237-1.765-.278-2.484-.038-3.011 1.832-.911.45-1.39.767-1.602 1.726a5.65 5.65 0 0 1 3.088.24.97.97 0 0 1 .566 1.236.96.96 0 0 1-1.237.566 2.93 2.93 0 0 0-2.206-.057c-.307.259-.307.796-.307 1.218 0 .71.355 1.37.96 1.754a3.5 3.5 0 0 0 1.64.384 6 6 0 0 1-.375-.777.995.995 0 0 1 1.88-.652c.383 1.093 1.361 1.841 2.512 1.966a3.59 3.59 0 0 0 3.06-2.043c.22-1.323 1.284-1.438 2.454-1.438m1.918 7.163-.595-1.246-.68.153.958 1.199zm-4.46-8.256a.96.96 0 0 0-.872-.988 2.56 2.56 0 0 0-1.85.643 2.85 2.85 0 0 0-.806 2.1.96.96 0 0 0 .959.959.95.95 0 0 0 .959-.96c0-.258.067-.517.22-.728a.64.64 0 0 1 .413-.144c.527.029.978-.364.978-.882z'/></svg>",
"browserlist": "<svg viewBox='0 0 140 140'><path fill='#ffca28' d='M70 10.172A59.83 59.83 0 0 0 10.172 70 59.83 59.83 0 0 0 70 129.828 59.83 59.83 0 0 0 129.828 70 59.83 59.83 0 0 0 70 10.172m-4.836 8.785C65.66 23 66.516 26.28 67.736 29.18c4.779-4.287 10.265-7.546 16.041-9.02-.981 3.938-1.357 7.295-1.261 10.43 6.026-2.314 12.349-3.404 18.3-2.706-3.182 2.413-5.482 4.717-7.128 7.015-2.201 12.074 6.858 20.43 14.779 24.551a5.13 5.13 0 0 1 5.183-3.888 5.128 5.128 0 0 1 3.7 8.435V64c-.487 1.055-2.002 2.343-3.496 3.219-4.076 2.39-11.173 5.736-20.915 7.39.045 1.214.077 2.453.077 3.747 0 4.817-.485 8.291-1.385 10.699-3.3 13.313-12.648 26.76-24.695 31.95.357-4.082.197-7.484-.402-10.591-5.582 3.219-11.646 5.278-17.623 5.52h-.002c1.785-3.662 2.855-6.878 3.412-9.976-6.347.996-12.727.742-18.377-1.17 2.93-2.732 5.054-5.314 6.673-7.96-6.292-1.344-12.169-3.87-16.766-7.686 3.822-1.544 6.795-3.239 9.3-5.197-5.426-3.517-10.034-7.998-12.972-13.23 4.012-.07 7.321-.568 10.3-1.453-3.786-5.215-6.468-11.032-7.333-16.951 3.861 1.405 7.196 2.133 10.36 2.355-1.662-6.22-2.081-12.605-.768-18.436 3.03 2.634 5.824 4.48 8.63 5.815.677-6.406 2.576-12.52 5.893-17.496 1.926 3.622 3.914 6.392 6.111 8.672 2.93-5.754 6.9-10.798 11.791-14.262zM91.63 38.514c-2.395 5.514-1.665 11.297-.555 18.732a2.138 2.138 0 0 0 .28-4.178 3.419 3.419 0 1 1 .092 6.704c.574 3.882 1.157 8.18 1.421 13.125a67 67 0 0 0 3.25-.649c6.616-1.487 12.258-3.801 16.871-6.506.45-.264.884-.563 1.276-.867.366-.557.333-.957.035-1.285-4.831-1.245-10.891-4.53-15.258-8.795-4.764-4.653-7.427-10.164-7.412-16.281'/></svg>",
"browserlist_light": "<svg viewBox='0 0 140 140'><g stroke-width='.855' transform='translate(10.508 10.205)'><circle cx='59.492' cy='59.795' r='59.828' fill='#ffca28'/><path fill='#37474f' d='M54.656 8.752c-4.89 3.464-8.862 8.508-11.791 14.262-2.198-2.28-4.185-5.05-6.111-8.672-3.318 4.976-5.216 11.09-5.893 17.496-2.807-1.335-5.6-3.18-8.63-5.814-1.314 5.83-.895 12.216.767 18.436-3.164-.223-6.498-.95-10.36-2.356.865 5.92 3.548 11.737 7.333 16.951-2.978.885-6.287 1.383-10.3 1.453 2.939 5.233 7.547 9.714 12.972 13.23-2.505 1.959-5.478 3.654-9.299 5.198 4.596 3.815 10.474 6.341 16.766 7.685-1.62 2.647-3.743 5.228-6.674 7.96 5.65 1.912 12.03 2.166 18.377 1.17-.556 3.098-1.626 6.314-3.412 9.975h.002c5.977-.24 12.042-2.3 17.623-5.52.6 3.108.76 6.51.402 10.593 12.047-5.19 21.395-18.638 24.695-31.951.9-2.408 1.385-5.881 1.385-10.7 0-1.293-.031-2.531-.076-3.745 9.742-1.655 16.839-5.001 20.914-7.39 1.494-.877 3.01-2.165 3.496-3.22v-.002a5.128 5.128 0 0 0-3.7-8.435 5.13 5.13 0 0 0-5.183 3.889c-7.92-4.122-16.98-12.477-14.779-24.551 1.646-2.299 3.947-4.603 7.13-7.016-5.952-.698-12.276.392-18.302 2.707-.095-3.135.28-6.492 1.262-10.43-5.776 1.473-11.262 4.733-16.041 9.02-1.22-2.902-2.076-6.18-2.572-10.223zm26.465 19.557c-.015 6.117 2.648 11.628 7.412 16.281 4.366 4.265 10.426 7.55 15.258 8.795.298.328.331.728-.035 1.285-.392.304-.825.603-1.276.867-4.612 2.704-10.256 5.019-16.87 6.506q-1.607.361-3.25.649c-.265-4.945-.848-9.243-1.422-13.125a3.419 3.419 0 1 0-.092-6.703 2.138 2.138 0 0 1-.28 4.177c-1.11-7.435-1.84-13.218.555-18.732' data-mit-no-recolor='true'/></g></svg>",
@@ -72,14 +73,14 @@
"buck": "<svg viewBox='0 0 24 24'><g fill='#0277bd'><path d='M7.488 2.94h-1.51v4.273L7.82 9.054H5.61L3.197 6.642V2.94H1.595v4.383l3.278 3.278h4.42l1.584 1.584H6.64l3.573 3.683-5.23 5.194h2.062l5.23-5.267-1.989-1.99H20.86v.664l-4.567 2.725-3.831 3.868h2.173l2.615-2.652 5.157-3.094v-3.13h-4.862l-1.658-1.658 1.879-1.879V4.45H16.2v3.5l-1.528 1.51-1.363-1.428V4.45h-1.473v4.273l3.445 3.462h-2.102L7.488 6.55z'/><path d='M15.48 14.763h-2.062l.995 1.068z'/></g></svg>",
"bucklescript": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='M12 18h4v2h-4z'/><path fill='#26a69a' d='M4 4v24h24V4Zm14 15.5a.5.5 0 0 1-.5.5H16v2h1.5a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5H16v1.5a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5V18h1.5a.5.5 0 0 1 .5.5Zm8-2a.5.5 0 0 1-.5.5H22v2h2a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-3.5a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5H24v-2h-2a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h3.5a.5.5 0 0 1 .5.5Z'/><path fill='#26a69a' d='M12 22h4v2h-4z'/></svg>",
"buildkite": "<svg viewBox='0 0 32 32'><path fill='#00e676' d='m12 22-8-4V8l8 4zm8-14v10h4l4-4'/><path fill='#00c853' d='m12 22 8-4V8l-8 4zm8 6 8-4V14l-8 4z'/></svg>",
- "bun": "<svg viewBox='0 0 32 32'><path fill='#FFF8E1' d='M30 17.045a9.8 9.8 0 0 0-.32-2.306l-.004.034a11.2 11.2 0 0 0-5.762-6.786c-3.495-1.89-5.243-3.326-6.8-3.811h.003c-1.95-.695-3.949.82-5.825 1.927-4.52 2.481-9.573 5.45-9.28 11.417.008-.029.017-.052.026-.08a9.97 9.97 0 0 0 3.934 7.257l-.01-.006C13.747 31.473 30.05 27.292 30 17.045'/><path fill='#37474f' d='M19.855 20.236A.8.8 0 0 0 19.26 20h-6.514a.8.8 0 0 0-.596.236.51.51 0 0 0-.137.463 4.37 4.37 0 0 0 1.641 2.339 4.2 4.2 0 0 0 2.349.926 4.2 4.2 0 0 0 2.343-.926 4.37 4.37 0 0 0 1.642-2.339.5.5 0 0 0-.132-.463Z'/><ellipse cx='22.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><ellipse cx='9.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><circle cx='10' cy='16' r='2' fill='#37474f'/><circle cx='22' cy='16' r='2' fill='#37474f'/><path fill='#455a64' d='M9.996 18A2 2 0 1 0 8 15.996V16a2 2 0 0 0 1.996 2'/><circle cx='9' cy='15' r='1' fill='#FAFAFA'/><circle cx='21' cy='15' r='1' fill='#FAFAFA'/></svg>",
- "bun_light": "<svg viewBox='0 0 32 32'><path fill='#FFF8E1' d='M15.696 27.002a13.73 13.73 0 0 1-9.071-3.062 8.86 8.86 0 0 1-3.6-6.505c-.252-5.091 3.813-7.747 8.748-10.455.28-.165.537-.322.793-.48a7.8 7.8 0 0 1 3.52-1.5 2 2 0 0 1 .695.118 14.8 14.8 0 0 1 2.95 1.576c.972.6 2.182 1.348 3.707 2.173a10.14 10.14 0 0 1 5.274 6.147A8.8 8.8 0 0 1 29 17.035a8.15 8.15 0 0 1-2.525 5.959 15.6 15.6 0 0 1-10.778 4.008Z'/><path fill='#37474f' d='M16.087 6a1 1 0 0 1 .358.06l.038.013.037.012a14.5 14.5 0 0 1 2.684 1.46 72 72 0 0 0 3.767 2.205 9.17 9.17 0 0 1 4.767 5.493A8 8 0 0 1 28 17.055a7.18 7.18 0 0 1-2.234 5.233 14.6 14.6 0 0 1-10.07 3.714 12.74 12.74 0 0 1-8.415-2.816l-.027-.024-.029-.023a7.98 7.98 0 0 1-3.202-5.758c-.223-4.516 3.431-6.89 8.231-9.525l.027-.015.027-.015q.389-.231.783-.474A7.4 7.4 0 0 1 16.087 6m0-2c-1.618 0-3.248 1.19-4.795 2.103-4.52 2.481-9.56 5.41-9.267 11.376a9.9 9.9 0 0 0 3.942 7.215 14.77 14.77 0 0 0 9.73 3.308c7.122 0 14.335-4.134 14.303-10.957a9.6 9.6 0 0 0-.322-2.29 11.16 11.16 0 0 0-5.764-6.768c-3.495-1.89-5.242-3.326-6.798-3.811A3 3 0 0 0 16.086 4Z'/><path fill='#37474f' d='M19.855 20.236A.8.8 0 0 0 19.26 20h-6.514a.8.8 0 0 0-.596.236.51.51 0 0 0-.137.463 4.37 4.37 0 0 0 1.641 2.339 4.2 4.2 0 0 0 2.349.926 4.2 4.2 0 0 0 2.343-.926 4.37 4.37 0 0 0 1.642-2.339.5.5 0 0 0-.132-.463Z'/><ellipse cx='22.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><ellipse cx='9.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><circle cx='10' cy='16' r='2' fill='#37474f'/><circle cx='22' cy='16' r='2' fill='#37474f'/><path fill='#455a64' d='M9.996 18A2 2 0 1 0 8 15.996V16a2 2 0 0 0 1.996 2'/><circle cx='9' cy='15' r='1' fill='#FAFAFA'/><circle cx='21' cy='15' r='1' fill='#FAFAFA'/></svg>",
+ "bun": "<svg viewBox='0 0 32 32'><path fill='#fff8e1' d='M30 17.045a9.8 9.8 0 0 0-.32-2.306l-.004.034a11.2 11.2 0 0 0-5.762-6.786c-3.495-1.89-5.243-3.326-6.8-3.811h.003c-1.95-.695-3.949.82-5.825 1.927-4.52 2.481-9.573 5.45-9.28 11.417.008-.029.017-.052.026-.08a9.97 9.97 0 0 0 3.934 7.257l-.01-.006C13.747 31.473 30.05 27.292 30 17.045'/><path fill='#37474f' d='M19.855 20.236A.8.8 0 0 0 19.26 20h-6.514a.8.8 0 0 0-.596.236.51.51 0 0 0-.137.463 4.37 4.37 0 0 0 1.641 2.339 4.2 4.2 0 0 0 2.349.926 4.2 4.2 0 0 0 2.343-.926 4.37 4.37 0 0 0 1.642-2.339.5.5 0 0 0-.132-.463Z'/><ellipse cx='22.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><ellipse cx='9.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><circle cx='10' cy='16' r='2' fill='#37474f'/><circle cx='22' cy='16' r='2' fill='#37474f'/><path fill='#455a64' d='M9.996 18A2 2 0 1 0 8 15.996V16a2 2 0 0 0 1.996 2'/><circle cx='9' cy='15' r='1' fill='#fafafa'/><circle cx='21' cy='15' r='1' fill='#fafafa'/></svg>",
+ "bun_light": "<svg viewBox='0 0 32 32'><path fill='#fff8e1' d='M15.696 27.002a13.73 13.73 0 0 1-9.071-3.062 8.86 8.86 0 0 1-3.6-6.505c-.252-5.091 3.813-7.747 8.748-10.455.28-.165.537-.322.793-.48a7.8 7.8 0 0 1 3.52-1.5 2 2 0 0 1 .695.118 14.8 14.8 0 0 1 2.95 1.576c.972.6 2.182 1.348 3.707 2.173a10.14 10.14 0 0 1 5.274 6.147A8.8 8.8 0 0 1 29 17.035a8.15 8.15 0 0 1-2.525 5.959 15.6 15.6 0 0 1-10.778 4.008Z'/><path fill='#37474f' d='M16.087 6a1 1 0 0 1 .358.06l.038.013.037.012a14.5 14.5 0 0 1 2.684 1.46 72 72 0 0 0 3.767 2.205 9.17 9.17 0 0 1 4.767 5.493A8 8 0 0 1 28 17.055a7.18 7.18 0 0 1-2.234 5.233 14.6 14.6 0 0 1-10.07 3.714 12.74 12.74 0 0 1-8.415-2.816l-.027-.024-.029-.023a7.98 7.98 0 0 1-3.202-5.758c-.223-4.516 3.431-6.89 8.231-9.525l.027-.015.027-.015q.389-.231.783-.474A7.4 7.4 0 0 1 16.087 6m0-2c-1.618 0-3.248 1.19-4.795 2.103-4.52 2.481-9.56 5.41-9.267 11.376a9.9 9.9 0 0 0 3.942 7.215 14.77 14.77 0 0 0 9.73 3.308c7.122 0 14.335-4.134 14.303-10.957a9.6 9.6 0 0 0-.322-2.29 11.16 11.16 0 0 0-5.764-6.768c-3.495-1.89-5.242-3.326-6.798-3.811A3 3 0 0 0 16.086 4Z'/><path fill='#37474f' d='M19.855 20.236A.8.8 0 0 0 19.26 20h-6.514a.8.8 0 0 0-.596.236.51.51 0 0 0-.137.463 4.37 4.37 0 0 0 1.641 2.339 4.2 4.2 0 0 0 2.349.926 4.2 4.2 0 0 0 2.343-.926 4.37 4.37 0 0 0 1.642-2.339.5.5 0 0 0-.132-.463Z'/><ellipse cx='22.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><ellipse cx='9.5' cy='18.5' fill='#f8bbd0' rx='2.5' ry='1.5'/><circle cx='10' cy='16' r='2' fill='#37474f'/><circle cx='22' cy='16' r='2' fill='#37474f'/><path fill='#455a64' d='M9.996 18A2 2 0 1 0 8 15.996V16a2 2 0 0 0 1.996 2'/><circle cx='9' cy='15' r='1' fill='#fafafa'/><circle cx='21' cy='15' r='1' fill='#fafafa'/></svg>",
"c": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M19.563 22A5.57 5.57 0 0 1 14 16.437v-2.873A5.57 5.57 0 0 1 19.563 8H24V2h-4.437A11.563 11.563 0 0 0 8 13.563v2.873A11.564 11.564 0 0 0 19.563 28H24v-6Z'/></svg>",
"c3": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M11.563 22A5.57 5.57 0 0 1 6 16.437v-2.873A5.57 5.57 0 0 1 11.563 8H14V2h-2.437A11.563 11.563 0 0 0 0 13.563v2.873A11.564 11.564 0 0 0 11.563 28H14v-6Zm20.129-4.131A5.17 5.17 0 0 0 28 14c3.39-.894 4.268-5.241 2.792-8.108-3.305-6.304-14.028-4.545-15.407 2.381l4.89 1.255a3.17 3.17 0 0 1 3.04-2.754 3 3 0 0 1 1.814.702c1.19.911 1.249 3.785-.353 4.232A9 9 0 0 1 22 12h-2v4h2c1.7.107 3.362.577 4.23 2.313a3.4 3.4 0 0 1 .377 1.636 3.25 3.25 0 0 1-.297 1.464c-.919 1.985-3.984 2.166-5.407.749a4.43 4.43 0 0 1-1.285-2.143l-4.89 1.429c2.447 10.449 19.993 7.76 16.964-3.58'/></svg>",
- "cabal": "<svg viewBox='0 0 300 300'><g transform='translate(0 -822.52)'><rect width='107.25' height='156.59' x='405.55' y='967.22' fill='#0097A7' rx='12.306' ry='12.31' transform='matrix(-.98339 .18149 .60192 .79856 0 0)'/><rect width='108.34' height='123.15' x='-1156.5' y='1461.9' fill='#3F51B5' rx='10.69' ry='12.31' transform='matrix(-.98528 .17093 -.59175 .80612 0 0)'/><path fill='#3F51B5' d='M52.112 965.158c-1.343 3.515-26.292 23.248-25.744 27.277.548 4.03 29.812 16.023 32.04 19.027s10.545 41.668 13.603 42.5 18.828-31.274 21.548-32.932 32.808 2.503 34.15-1.01c1.343-3.515-18.174-35.352-18.721-39.381s9.732-40.12 7.502-43.125-30.06 9.427-33.118 8.594-26.793-27.3-29.514-25.643-.405 41.177-1.747 44.693z'/></g></svg>",
+ "cabal": "<svg viewBox='0 0 300 300'><g transform='translate(0 -822.52)'><rect width='107.25' height='156.59' x='405.55' y='967.22' fill='#0097a7' rx='12.306' ry='12.31' transform='matrix(-.98339 .18149 .60192 .79856 0 0)'/><rect width='108.34' height='123.15' x='-1156.5' y='1461.9' fill='#3f51b5' rx='10.69' ry='12.31' transform='matrix(-.98528 .17093 -.59175 .80612 0 0)'/><path fill='#3f51b5' d='M52.112 965.158c-1.343 3.515-26.292 23.248-25.744 27.277.548 4.03 29.812 16.023 32.04 19.027s10.545 41.668 13.603 42.5 18.828-31.274 21.548-32.932 32.808 2.503 34.15-1.01c1.343-3.515-18.174-35.352-18.721-39.381s9.732-40.12 7.502-43.125-30.06 9.427-33.118 8.594-26.793-27.3-29.514-25.643-.405 41.177-1.747 44.693z'/></g></svg>",
"caddy": "<svg viewBox='0 0 32 32'><path fill='#4fc3f7' d='M20 22v-3.53q-.008-.155-.011-.31-.003-.436-.041-.87a5.3 5.3 0 0 0-.18-.994 2.9 2.9 0 0 0-1.026-1.563A4.42 4.42 0 0 0 16.017 14a4.5 4.5 0 0 0-2.762.74 2.9 2.9 0 0 0-1.05 1.57 5 5 0 0 0-.186 1.073q-.029.448-.014.897l.004.191v3.53Z'/><path fill='#4fc3f7' d='M29 19c0-7.409-5.268-13-13-13S3 11.591 3 19c-.003 2.317 0 5 1 7.026v-.84c.001-1.673 2.264-3.002 4-3.186v-4.438C8 12.38 10.388 9.931 16 10c5.612-.07 8 2.38 8 7.562V22c1.736.184 3.999 1.513 4 3.187v.839C29 24 29.003 21.317 29 19'/></svg>",
"cadence": "<svg viewBox='0 0 32 32'><path fill='#00e676' d='M14 12h6v-1a1 1 0 0 1 1-1h7V4h-6a8 8 0 0 0-8 8m6 0h6v6h-6zm-6 6v2.65A1.35 1.35 0 0 1 12.65 22h-1.3A1.35 1.35 0 0 1 10 20.65v-1.3A1.35 1.35 0 0 1 11.35 18zv-6h-2.65A7.35 7.35 0 0 0 4 19.35v1.3A7.35 7.35 0 0 0 11.35 28h1.3A7.35 7.35 0 0 0 20 20.65V18Z'/></svg>",
- "cairo": "<svg viewBox='0 0 24 24'><path fill='#F44336' d='M13.15 7.455a1.94 1.94 0 0 1-1.938-1.94c0-1.07.87-1.94 1.939-1.94 1.07 0 1.94.87 1.94 1.94s-.87 1.94-1.94 1.94M12 2C6.477 2 2 6.477 2 12c0 2.348.811 4.506 2.166 6.212 1.092-1.532 2.258-2.977 3.721-4.18.042-.035.143-.11.272-.203a2.92 2.92 0 0 0 1.15-1.876v-.012c.37-2.438 1.371-3.279 4.152-3.279q.37.001.786.02c1.423.067 2.243.473 2.34.685a.57.57 0 0 1 .027.363l-.111-.015c-.878-.109-2.231.16-2.419 1.117-.105.544.02 1.143.072 1.693.054.567.104 1.139.099 1.711-.003.044-.035.266-.005.29-1.514-1.449-5.014.37-6.116 1.17q.17-.058.34-.122c1.05-.357 4.24-1.314 5.47-.256 1.043 1.277.104 3.634-.673 4.802a9.7 9.7 0 0 1-1.64 1.87q.184.009.369.01c5.523 0 10-4.477 10-10S17.523 2 12 2'/></svg>",
+ "cairo": "<svg viewBox='0 0 24 24'><path fill='#f44336' d='M13.15 7.455a1.94 1.94 0 0 1-1.938-1.94c0-1.07.87-1.94 1.939-1.94 1.07 0 1.94.87 1.94 1.94s-.87 1.94-1.94 1.94M12 2C6.477 2 2 6.477 2 12c0 2.348.811 4.506 2.166 6.212 1.092-1.532 2.258-2.977 3.721-4.18.042-.035.143-.11.272-.203a2.92 2.92 0 0 0 1.15-1.876v-.012c.37-2.438 1.371-3.279 4.152-3.279q.37.001.786.02c1.423.067 2.243.473 2.34.685a.57.57 0 0 1 .027.363l-.111-.015c-.878-.109-2.231.16-2.419 1.117-.105.544.02 1.143.072 1.693.054.567.104 1.139.099 1.711-.003.044-.035.266-.005.29-1.514-1.449-5.014.37-6.116 1.17q.17-.058.34-.122c1.05-.357 4.24-1.314 5.47-.256 1.043 1.277.104 3.634-.673 4.802a9.7 9.7 0 0 1-1.64 1.87q.184.009.369.01c5.523 0 10-4.477 10-10S17.523 2 12 2'/></svg>",
"cake": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M16 10a2.847 2.847 0 0 0 3-2.663v-.003a2.32 2.32 0 0 0-.435-1.372L16 2l-2.565 3.96A2.33 2.33 0 0 0 13 7.331 2.847 2.847 0 0 0 15.998 10zm6.134 13.376L20.708 22l-1.44 1.375a4.917 4.917 0 0 1-6.52 0L11.334 22l-1.466 1.375A4.79 4.79 0 0 1 4 23.871V29a1 1 0 0 0 1 1h22a1 1 0 0 0 1-1v-5.129a4.79 4.79 0 0 1-5.866-.497M24 14h-6.667v-2h-2.666v2H8a4.145 4.145 0 0 0-4 4.09v1.415A2.56 2.56 0 0 0 6.614 22a2.53 2.53 0 0 0 1.84-.726l2.88-2.71 2.813 2.71a2.764 2.764 0 0 0 3.693 0l2.826-2.71 2.868 2.71A2.649 2.649 0 0 0 28 19.505V18.09A4.145 4.145 0 0 0 24 14'/></svg>",
"capacitor": "<svg viewBox='0 0 24 24'><path fill='#4fc3f7' d='m19.081 2.35-4.795 4.795L9.62 2.482 7.05 5.05l4.664 4.665 2.57 2.57 4.705 4.705 2.57-2.57-4.705-4.705 4.795-4.797zM5.052 7.05l-2.57 2.57 4.665 4.664L2.35 19.08l2.57 2.57 4.796-4.796 4.704 4.705 2.57-2.57-7.274-7.274z' paint-order='fill markers stroke'/></svg>",
"capnp": "<svg viewBox='0 0 24 24'><path fill='#c62828' d='M17 3V2h4v8h-4c-.085-2.088-.445-4.042-3-4-2.917 0-5 2.51-5 5 0 3 .495 6.981 4.67 6.981 2.906-.26 2.99-2.705 3.33-4.981h4c0 5.806-3.314 9.052-9 9-6.154-.073-8.915-4.685-9-10-.128-6.14 4.568-9.2 10.414-9.65 1.301-.028 2.466 0 3.586.65'/></svg>",
@@ -97,7 +98,6 @@
"cline": "<svg viewBox='0 0 16 16'><path fill='#42a5f5' d='M8 1a2 2 0 0 0-2 2H5C3.338 3 2 4.338 2 6v1L1 9l1 2v1c0 1.662 1.338 3 3 3h6c1.662 0 3-1.338 3-3v-1l1-2-1-2V6c0-1.662-1.338-3-3-3h-1a2 2 0 0 0-2-2M6 7c.554 0 1 .446 1 1v2c0 .554-.446 1-1 1s-1-.446-1-1V8c0-.554.446-1 1-1m4 0c.554 0 1 .446 1 1v2c0 .554-.446 1-1 1s-1-.446-1-1V8c0-.554.446-1 1-1'/></svg>",
"clojure": "<svg viewBox='0 0 256 256'><path fill='#64dd17' d='M123.456 129.975a507 507 0 0 0-3.54 7.846c-4.406 9.981-9.284 22.127-11.066 29.908-.64 2.77-1.037 6.205-1.03 10.013 0 1.506.081 3.09.21 4.702a58.1 58.1 0 0 0 19.98 3.559 58.2 58.2 0 0 0 18.29-2.98c-1.352-1.237-2.642-2.554-3.816-4.038-7.796-9.942-12.146-24.512-19.028-49.01m-28.784-49.39C79.782 91.08 70.039 108.387 70.002 128c.037 19.32 9.487 36.403 24.002 46.94 3.56-14.83 12.485-28.41 25.868-55.63a219 219 0 0 0-2.714-7.083c-3.708-9.3-9.059-20.102-13.834-24.993-2.435-2.555-5.389-4.763-8.652-6.648'/><path fill='#7cb342' d='M178.532 194.535c-7.683-.963-14.023-2.124-19.57-4.081a69.4 69.4 0 0 1-30.958 7.249c-38.491 0-69.693-31.198-69.698-69.7 0-20.891 9.203-39.62 23.764-52.392-3.895-.94-7.956-1.49-12.104-1.482-20.45.193-42.037 11.51-51.025 42.075-.84 4.45-.64 7.813-.64 11.8 0 60.591 49.12 109.715 109.705 109.715 37.104 0 69.882-18.437 89.732-46.633-10.736 2.675-21.06 3.955-29.902 3.982-3.314 0-6.425-.177-9.305-.53'/><path fill='#29b6f6' d='M157.922 173.271c.678.336 2.213.884 4.35 1.49 14.375-10.553 23.717-27.552 23.754-46.764h-.005c-.055-32.03-25.974-57.945-58.011-58.009a58.2 58.2 0 0 0-18.213 2.961c11.779 13.426 17.443 32.613 22.922 53.6l.01.025c.01.017 1.752 5.828 4.743 13.538 2.97 7.7 7.203 17.231 11.818 24.178 3.03 4.655 6.363 8 8.632 8.981'/><path fill='#1e88e5' d='M128.009 18.29c-36.746 0-69.25 18.089-89.16 45.826 10.361-6.49 20.941-8.83 30.174-8.747 12.753.037 22.779 3.991 27.589 6.696a51 51 0 0 1 3.345 2.131 69.4 69.4 0 0 1 28.049-5.894c38.496.004 69.703 31.202 69.709 69.698h-.006c0 19.409-7.938 36.957-20.736 49.594 3.142.352 6.492.571 9.912.554 12.15.006 25.284-2.675 35.13-10.956 6.42-5.408 11.798-13.327 14.78-25.199.584-4.586.92-9.247.92-13.991 0-60.588-49.116-109.715-109.705-109.715'/></svg>",
"cloudfoundry": "<svg viewBox='0 0 24 24'><path fill='#0288d1' d='M14.13 10.1c.6-.5.98-1.19.97-1.96-.01-1.47-1.4-2.66-3.1-2.66S8.91 6.66 8.9 8.13c-.01.77.37 1.47.97 1.96.75.61 1.36 1.84 1.4 3.18.04 1.49-.06 3.22-.12 4.1-.8.11-1.36.38-1.36.69 0 .41.99.89 2.22.89s2.22-.48 2.22-.89c0-.31-.56-.58-1.36-.69-.06-.88-.16-2.6-.12-4.1.04-1.34.65-2.57 1.4-3.18z'/><path fill='#78909c' d='M17.89 18.77a2 2 0 0 1 .37-.11 2.3 2.3 0 0 1 .43-.05l-.02-.12c0-.07-.04-.16-.07-.26l-.05-.13c-.02-.04-.04-.08-.06-.11l-.06-.11a4 4 0 0 1-.44-.1 3 3 0 0 1-.4-.14c-.22-.09-.43-.2-.54-.32a.41.41 0 0 1-.16-.34.3.3 0 0 1 .07-.15.5.5 0 0 1 .16-.12l-.13-.07c-.04-.02-.08-.05-.14-.07-.05-.03-.11-.05-.17-.08-.11-.05-.22-.11-.32-.14l-.15-.06a3 3 0 0 1-.34.02c-.12 0-.25 0-.37-.01-.24-.02-.49-.05-.7-.12a2 2 0 0 1-.28-.1 1.3 1.3 0 0 1-.21-.11c-.11-.08-.19-.16-.19-.25l-.17-.03c-.1-.02-.33-.05-.47-.07 0 .02-.02.05-.02.09l-.04.17c-.07.35.13.7.47.8.83.24 1.46.64 1.64 1.13.37.99-1.18 1.96-3.52 1.96-2.35 0-3.89-.97-3.53-1.96.18-.48.78-.87 1.59-1.11a.72.72 0 0 0 .49-.84l-.05-.24-.31.04c-.14.02-.27.04-.37.06l-.17.03c.01.08-.05.17-.16.25a1.4 1.4 0 0 1-.2.12 2 2 0 0 1-.27.1 3 3 0 0 1-.33.08 3 3 0 0 1-.36.05c-.25.02-.5.02-.73 0l-.04.02c-.02.01-.07.03-.11.05l-.31.15c-.11.05-.21.12-.29.16l-.1.05-.03.02c.16.07.24.17.26.28.04.11-.01.23-.12.35a1.5 1.5 0 0 1-.22.18 2 2 0 0 1-.32.17 3.4 3.4 0 0 1-.83.26.5.5 0 0 0-.05.11l-.09.25-.04.26a.4.4 0 0 0-.01.12c.32 0 .61.06.83.14.25.08.4.21.52.36.09.16.13.33.05.53-.06.19-.22.41-.47.6l.04.03.12.08c.1.07.23.17.38.26l.44.24c.06.03.12.06.16.07l.06.03c.36-.15.75-.23 1.14-.28a3.3 3.3 0 0 1 .57-.01c.19.01.38.02.55.07.18.05.35.09.5.16a3 3 0 0 1 .42.24c.26.17.45.42.54.68l.33.01c.2.01.46.02.73.01l.72-.03.32-.03a1 1 0 0 1 .17-.38 1 1 0 0 1 .14-.16 1.6 1.6 0 0 1 .55-.39 2.3 2.3 0 0 1 .48-.17c.18-.04.35-.07.54-.08q.285-.015.57 0a4 4 0 0 1 1.15.24l.2-.11c.11-.07.28-.15.41-.25s.27-.19.35-.26l.13-.13c-.27-.19-.43-.39-.52-.58a.64.64 0 0 1-.06-.27.4.4 0 0 1 .06-.25c.07-.16.25-.27.45-.37M9.95 13.9s.35-1.32-1.17-3.03l-.05-.05a4.2 4.2 0 0 1-.98-2.7c0-2.32 1.89-4.2 4.2-4.2h.1a4.2 4.2 0 0 1 4.2 4.2 4.2 4.2 0 0 1-.99 2.7c-.01.01-.02.03-.05.05-1.34 1.35-1.17 3.03-1.17 3.03a6.14 6.14 0 0 0 4.13-5.75c.02-3.24-2.6-6-5.83-6.15h-.7c-3.23.16-5.85 2.92-5.83 6.15.01 2.59 1.67 4.9 4.13 5.75z'/></svg>",
- "cls.clone": "<svg viewBox='0 0 1024 1024'><path fill='#FF4081' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
"cmake": "<svg viewBox='0 0 24 24'><path fill='#1e88e5' d='M11.94 2.984 2.928 21.017l9.875-8.47z'/><path fill='#e53935' d='m11.958 2.982.002.29 1.312 14.499-.002.006.023.26 7.363 2.978h.415l-.158-.31-.114-.228h-.001l-8.84-17.494z'/><path fill='#7cb342' d='m8.558 16.13-5.627 4.884h17.743v-.016z'/></svg>",
"coala": "<svg viewBox='0 0 24 24'><path xmlns='http://www.w3.org/2000/svg' fill='#90a4ae' d='M22 8.95c0-2.59-1.74-3.63-3.89-3.63-.9 0-1.83.2-2.6.57-1-.69-2.17-1.09-3.51-1.09s-2.51.41-3.51 1.09c-.78-.38-1.7-.57-2.6-.57C3.74 5.32 2 6.35 2 8.95c0 2.14 1.18 3.56 2.8 4-.02.3-.04.6-.04.89 0 3.18 2.51 4.26 4.77 4.63.61.45 1.49.73 2.47.73s1.86-.28 2.47-.73c2.26-.36 4.77-1.44 4.77-4.63 0-.3-.01-.6-.04-.89 1.62-.44 2.8-1.87 2.8-4'/><path xmlns='http://www.w3.org/2000/svg' fill='#f8bbd0' d='M7.31 6.9c-.18-.02-.35-.03-.53-.03-1.72 0-3.11.83-3.11 2.9 0 1.2.47 2.12 1.19 2.68.26-2.11 1.11-4.12 2.45-5.55m9.91-.03c-.18 0-.35.01-.53.03 1.34 1.43 2.19 3.44 2.45 5.55.72-.56 1.19-1.48 1.19-2.68 0-2.07-1.39-2.9-3.11-2.9'/><path xmlns='http://www.w3.org/2000/svg' fill='#263238' d='M14.07 15.21c0 1.86-.96 2.33-2.07 2.33s-2.07-.47-2.07-2.33.96-3.36 2.07-3.36 2.07 1.51 2.07 3.36M9.5 11.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0m7.5 0a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0'/></svg>",
"cobol": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M12 0h8v4h-8z'/><path fill='#0288d1' d='M16 5A11 11 0 1 1 5 16 11.01 11.01 0 0 1 16 5m0-3a14 14 0 1 0 14 14A14 14 0 0 0 16 2'/><path fill='#0288d1' d='M32 12v8h-4v-8zm-1.858 12.485-5.657 5.657-2.313-2.313 5.657-5.657zM7.514 30.143l-5.657-5.657 2.814-2.814 5.657 5.657zM12 28h8v4h-8zm15.329-17.672L21.672 4.67l2.814-2.814 5.657 5.657zM3 12v8H0v-8zm7.328-7.329L4.67 10.328 1.857 7.514l5.657-5.657zM20 10h-4a6 6 0 0 0 0 12h4v-4h-4a2 2 0 0 1 0-4h4z'/></svg>",
@@ -106,9 +106,10 @@
"code-climate_light": "<svg viewBox='0 0 300 300'><path fill='#455a64' d='m196.19 75.562-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z'/></svg>",
"codecov": "<svg viewBox='0 0 24 24'><path fill='#ec407a' d='M12.006 2.375c-5.528.004-10.028 4.471-10.032 9.959v.025l1.706.995.023-.016a4.9 4.9 0 0 1 3.641-.773 4.75 4.75 0 0 1 2.398 1.193l.293.273.166-.363c.16-.35.346-.68.55-.98a8 8 0 0 1 .278-.372l.172-.215-.211-.176a7 7 0 0 0-3.249-1.516 7.16 7.16 0 0 0-3.359.196c.812-3.556 3.939-6.036 7.631-6.039a7.78 7.78 0 0 1 5.516 2.267 7.7 7.7 0 0 1 2.095 3.759 7.2 7.2 0 0 0-2.09-.317h-.127a7 7 0 0 0-.829.061l-.034.005a6 6 0 0 0-.327.05 7 7 0 0 0-.47.101l-.115.03q-.202.055-.403.12l-.025.008a7 7 0 0 0-.878.367l-.023.012a7 7 0 0 0-.392.214l-.03.018a6.8 6.8 0 0 0-1.77 1.516l-.063.076a7 7 0 0 0-.557.799l-.05.087a7 7 0 0 0-.195.36l-.014.025a7 7 0 0 0-.367.888l-.015.044a6.9 6.9 0 0 0-.343 2.264l.001.094a10 10 0 0 0 .018.33l.014.155.021.19.005.034.011.086q.022.158.052.316c.202 1.057.706 2.115 1.458 3.058l.034.042.035-.041c.3-.355 1.044-1.479 1.107-2.154l.001-.012-.006-.011a4.7 4.7 0 0 1-.535-2.169c0-2.52 1.982-4.613 4.51-4.764l.165-.006a4.96 4.96 0 0 1 2.9.856l.023.016 1.684-.979.022-.013v-.025a9.84 9.84 0 0 0-2.934-7.039 10 10 0 0 0-7.087-2.91'/></svg>",
"codeowners": "<svg viewBox='0 0 24 24'><path fill='#afb42b' d='m20.35 12.25 1.4 1.41-6.53 6.59-3.47-3.5 1.4-1.41 2.07 2.08zm-11.1 4.5 3 3h-10v-2c0-2.21 3.58-4 8-4l1.89.11zm1-13a4 4 0 0 1 4 4 4 4 0 0 1-4 4 4 4 0 0 1-4-4 4 4 0 0 1 4-4'/></svg>",
- "coderabbit-ai": "<svg viewBox='0 0 16 16'><path fill='#F4511E' d='M15 8.913A6.85 6.85 0 0 1 12.74 14h-1.68c.035-.162-.052-.274-.165-.35-.236-.162-.385-.431-.35-.71.093-.735.545-1.674 2.194-2.536 1.115-.588 1.32-1.8 1.398-1.978.113-.294.082-.543-.154-.781-.427-.431-.889-.822-1.449-1.075-.786-.365-1.588-.345-2.384-.051-.139.05-.108-.056-.118-.101a5.3 5.3 0 0 0-.545-1.481c-.519-.923-1.243-1.603-2.322-1.831-.16-.036-.324-.046-.488-.066-.077-.01-.118.01-.098.101.144.68.345 1.334.781 1.892.2.259.473.431.74.609.283.192.58.37.848.588.252.213.457.461.555.817C9.467 7.01 9.45 7 9.44 6.986c-.822-1.289-2.43-1.857-4.048-1.39-.15.04-.129.091-.057.198.468.71 1.11 1.217 1.87 1.608.565.289 1.151.527 1.788.608.288.036.15.228.195.457.062.355.211.466.16.436-.977-.386-1.788-.538-2.461-.538-2.78 0-3.232 2.617-3.211 2.648-.041-.016-.71-.259-.858.4-.154.67.755 1.106.755 1.106.118-.791.837-.964.925-.984-.072.04-.54.31-.684 1.045-.123.65.396 1.222.591 1.42h-1.15A6.87 6.87 0 0 1 1 8.913C1 5.093 4.129 2 7.997 2 11.867 2 15 5.094 15 8.913M9.914 14h-.74a.27.27 0 0 0 .072-.142c.062-.355-.247-.426-.247-.426H7.294s.678-.03 1.3-.284c.616-.259 1.114-.71 1.202-.832a1.93 1.93 0 0 0-.031 1.517.32.32 0 0 0 .149.167'/></svg>",
+ "coderabbit-ai": "<svg viewBox='0 0 16 16'><path fill='#f4511e' d='M15 8.913A6.85 6.85 0 0 1 12.74 14h-1.68c.035-.162-.052-.274-.165-.35-.236-.162-.385-.431-.35-.71.093-.735.545-1.674 2.194-2.536 1.115-.588 1.32-1.8 1.398-1.978.113-.294.082-.543-.154-.781-.427-.431-.889-.822-1.449-1.075-.786-.365-1.588-.345-2.384-.051-.139.05-.108-.056-.118-.101a5.3 5.3 0 0 0-.545-1.481c-.519-.923-1.243-1.603-2.322-1.831-.16-.036-.324-.046-.488-.066-.077-.01-.118.01-.098.101.144.68.345 1.334.781 1.892.2.259.473.431.74.609.283.192.58.37.848.588.252.213.457.461.555.817C9.467 7.01 9.45 7 9.44 6.986c-.822-1.289-2.43-1.857-4.048-1.39-.15.04-.129.091-.057.198.468.71 1.11 1.217 1.87 1.608.565.289 1.151.527 1.788.608.288.036.15.228.195.457.062.355.211.466.16.436-.977-.386-1.788-.538-2.461-.538-2.78 0-3.232 2.617-3.211 2.648-.041-.016-.71-.259-.858.4-.154.67.755 1.106.755 1.106.118-.791.837-.964.925-.984-.072.04-.54.31-.684 1.045-.123.65.396 1.222.591 1.42h-1.15A6.87 6.87 0 0 1 1 8.913C1 5.093 4.129 2 7.997 2 11.867 2 15 5.094 15 8.913M9.914 14h-.74a.27.27 0 0 0 .072-.142c.062-.355-.247-.426-.247-.426H7.294s.678-.03 1.3-.284c.616-.259 1.114-.71 1.202-.832a1.93 1.93 0 0 0-.031 1.517.32.32 0 0 0 .149.167'/></svg>",
"coffee": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M4 26h24v2H4zM28 4H7a1 1 0 0 0-1 1v13a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4v-4h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 8h-4V6h4Z'/></svg>",
"coldfusion": "<svg viewBox='0 0 32 32'><path fill='#0d3858' stroke='#4dd0e1' stroke-width='2' d='M3.009 3.009h25.983v25.983H3.009z'/><path fill='#4dd0e1' d='M24 9.5v-1a.5.5 0 0 0-.5-.5H22a2 2 0 0 0-2 2v2h-1.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5H20v7.5a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5V14h1.5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5H22v-2h1.5a.5.5 0 0 0 .5-.5M12 20a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h3.5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5H12a4 4 0 0 0-4 4v4a4 4 0 0 0 4 4h3.5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5Z'/></svg>",
+ "coloredpetrinets": "<svg viewBox='0 0 16 16'><rect width='3' height='1' x='9' y='7.5' fill='#b0bec5' rx='0' ry='.032'/><rect width='3' height='1' x='4' y='7.5' fill='#b0bec5' rx='0' ry='.032'/><circle cx='3' cy='8' r='2' fill='#4caf50'/><circle cx='13' cy='8' r='2' fill='#ff9800'/><rect width='4' height='4' x='6' y='6' fill='#00bcd4' rx='.59'/></svg>",
"command": "<svg viewBox='0 0 32 32'><path fill='#90a4ae' d='M24 18h-3v-4h3a6 6 0 1 0-6-6v3h-4V8a6 6 0 1 0-6 6h3v4H8a6 6 0 1 0 6 6v-3h4v3a6 6 0 1 0 6-6M21 8a3 3 0 1 1 3 3h-3ZM11 24a3 3 0 1 1-3-3h3Zm0-13H8a3 3 0 1 1 3-3Zm7 7h-4v-4h4Zm6 9a3.003 3.003 0 0 1-3-3v-3h3a3 3 0 0 1 0 6'/></svg>",
"commitizen": "<svg viewBox='0 0 32 32'><path fill='#64b5f6' d='M29.422 17.4 17.4 29.422a1.986 1.986 0 0 1-2.8 0L2.578 17.4a1.986 1.986 0 0 1 0-2.8L14.6 2.578a1.986 1.986 0 0 1 2.8 0l8.01 8.012L23 13a2 2 0 0 0-.74-.14A2.13 2.13 0 0 0 20.37 14H12a2.08 2.08 0 0 0-1.86-1.14 2.14 2.14 0 0 0 0 4.28A2.08 2.08 0 0 0 12 16h8l-3.82 3.83h-.01a1.9 1.9 0 0 0-.63-.1 2.135 2.135 0 1 0 2.14 2.13 1.8 1.8 0 0 0-.1-.61l4.17-4.17a2 2 0 0 0 .51.06A2.14 2.14 0 0 0 24.4 15a2 2 0 0 0-.06-.51l2.49-2.48 2.592 2.59a1.986 1.986 0 0 1 0 2.8'/></svg>",
"commitlint": "<svg viewBox='0 0 24 24'><path fill='#009688' d='M2.47 2.922V8.37h1.813V2.922zm12.708 1.816a7.27 7.27 0 0 0-6.946 5.127L6.1 12l2.133 2.133c.916 2.969 3.677 5.13 6.945 5.13 4.013 0 7.262-3.25 7.262-7.263s-3.25-7.262-7.262-7.262m2.942 3.703 1.342 1.63-5.49 5.488-3.179-3.467 1.34-1.34 1.838 1.838zM3.377 10.184c-.998 0-1.816.817-1.816 1.816a1.817 1.817 0 1 0 1.816-1.816M2.47 15.63v5.448h1.814V15.63z'/></svg>",
@@ -116,7 +117,8 @@
"conduct": "<svg viewBox='0 0 24 24'><path fill='#cddc39' d='m10 17-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9m-6-6a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1s-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2'/></svg>",
"console": "<svg viewBox='0 0 24 24'><path fill='#ff7043' d='M20 19V7H4v12zm0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm-7 14v-2h5v2zm-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z'/></svg>",
"container.clone": "<svg viewBox='0 0 24 24'><path fill='#00b0ff' d='M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18s-.41-.06-.57-.18l-7.9-4.44A.99.99 0 0 1 3 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.16-.12.36-.18.57-.18s.41.06.57.18l7.9 4.44c.32.17.53.5.53.88zM12 4.15 6.04 7.5 12 10.85l5.96-3.35zM5 15.91l6 3.38v-6.71L5 9.21zm14 0v-6.7l-6 3.37v6.71z'/></svg>",
- "contentlayer": "<svg fill='none' viewBox='0 0 16 16'><path fill='#651FFF' fill-rule='evenodd' stroke='#651FFF' stroke-width='1.073' d='M-2.482.404A1.93 1.93 0 0 1-.16.427l6.967 5.356a1.93 1.93 0 0 1 0 3.058L4.15 10.883l2.7 2.171c.983.79.956 2.294-.053 3.048l-7.152 5.344a1.93 1.93 0 0 1-2.439-.106l-5.596-4.996-.782-.672c-3.492-3-3.062-8.526.845-10.951zm5.6 9.65L-.13 7.444a1.93 1.93 0 0 0-2.384-.026l-2.403 1.848a1.93 1.93 0 0 0 0 3.058l2.42 1.86a1.93 1.93 0 0 0 2.352 0l3.246-2.494 2.944 2.366a.643.643 0 0 1-.018 1.016l-7.152 5.344a.64.64 0 0 1-.813-.035l-5.6-5-.796-.684c-2.839-2.439-2.482-6.935.705-8.896l.023-.014 5.888-4.349a.64.64 0 0 1 .774.008l6.967 5.356a.643.643 0 0 1 0 1.02zm-1.049.807-2.998 2.304a.64.64 0 0 1-.783 0l-2.421-1.86a.643.643 0 0 1 0-1.02l2.403-1.848a.64.64 0 0 1 .795.009z' clip-rule='evenodd' transform='matrix(.5949 0 0 .61208 9.182 1.311)'/></svg>",
+ "contentlayer": "<svg fill='none' viewBox='0 0 16 16'><path fill='#651fff' fill-rule='evenodd' stroke='#651fff' stroke-width='1.073' d='M-2.482.404A1.93 1.93 0 0 1-.16.427l6.967 5.356a1.93 1.93 0 0 1 0 3.058L4.15 10.883l2.7 2.171c.983.79.956 2.294-.053 3.048l-7.152 5.344a1.93 1.93 0 0 1-2.439-.106l-5.596-4.996-.782-.672c-3.492-3-3.062-8.526.845-10.951zm5.6 9.65L-.13 7.444a1.93 1.93 0 0 0-2.384-.026l-2.403 1.848a1.93 1.93 0 0 0 0 3.058l2.42 1.86a1.93 1.93 0 0 0 2.352 0l3.246-2.494 2.944 2.366a.643.643 0 0 1-.018 1.016l-7.152 5.344a.64.64 0 0 1-.813-.035l-5.6-5-.796-.684c-2.839-2.439-2.482-6.935.705-8.896l.023-.014 5.888-4.349a.64.64 0 0 1 .774.008l6.967 5.356a.643.643 0 0 1 0 1.02zm-1.049.807-2.998 2.304a.64.64 0 0 1-.783 0l-2.421-1.86a.643.643 0 0 1 0-1.02l2.403-1.848a.64.64 0 0 1 .795.009z' clip-rule='evenodd' transform='matrix(.5949 0 0 .61208 9.182 1.311)'/></svg>",
+ "context": "<svg viewBox='0 0 16 16'><path fill='#ffd600' d='M7 6h2c.554 0 1 .446 1 1v2c0 .554-.446 1-1 1H7c-.554 0-1-.446-1-1V7c0-.554.446-1 1-1'/><path fill='#aeea00' d='M7 0h2c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1H7c-.554 0-1-.446-1-1V1c0-.554.446-1 1-1'/><path fill='#f57f17' d='M12 3h2c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1h-2c-.554 0-1-.446-1-1V4c0-.554.446-1 1-1'/><path fill='#8e24aa' d='M12 9h2c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1h-2c-.554 0-1-.446-1-1v-3c0-.554.446-1 1-1'/><path fill='#4db6ac' d='M7 11h2c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1H7c-.554 0-1-.446-1-1v-3c0-.554.446-1 1-1'/><path fill='#e53935' d='M2 9h2c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1H2c-.554 0-1-.446-1-1v-3c0-.554.446-1 1-1'/><path fill='#1565c0' d='M2 3h2c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1H2c-.554 0-1-.446-1-1V4c0-.554.446-1 1-1'/></svg>",
"contributing": "<svg viewBox='0 0 24 24'><path fill='#ffca28' d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1s-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2'/></svg>",
"controller": "<svg viewBox='0 0 16 16'><path fill='#ffc107' d='M8.002 10.45A2.45 2.45 0 0 1 5.552 8a2.45 2.45 0 0 1 2.45-2.45A2.45 2.45 0 0 1 10.452 8a2.45 2.45 0 0 1-2.45 2.45m5.2-1.771c.029-.224.05-.448.05-.679a6 6 0 0 0-.05-.7l1.478-1.141a.35.35 0 0 0 .084-.448l-1.4-2.422a.344.344 0 0 0-.427-.154l-1.743.7a5 5 0 0 0-1.183-.686l-.26-1.855A.354.354 0 0 0 9.402 1h-2.8a.354.354 0 0 0-.35.294l-.258 1.855a5 5 0 0 0-1.183.686l-1.743-.7a.344.344 0 0 0-.427.154l-1.4 2.422a.345.345 0 0 0 .084.448L2.8 7.3a6 6 0 0 0-.05.7c0 .231.022.455.05.679L1.324 9.841a.345.345 0 0 0-.084.448l1.4 2.422c.084.154.273.21.427.154l1.743-.707c.364.28.742.518 1.183.693l.259 1.855a.354.354 0 0 0 .35.294h2.8a.354.354 0 0 0 .35-.294l.259-1.855a5 5 0 0 0 1.183-.693l1.743.707c.154.056.343 0 .427-.154l1.4-2.422a.35.35 0 0 0-.084-.448z'/></svg>",
"copilot": "<svg viewBox='0 0 16 16'><path fill='#fafafa' d='M7.998 14C4.006 14 1.11 11.457 1 10.728v-1.7c.074-.549.592-1.472 1.39-1.803l.031-.19a4 4 0 0 1 .11-.534c-.176-.444-.222-.946-.222-1.446 0-.759.112-1.544.606-2.168.507-.64 1.308-.98 2.384-1.1 1.055-.117 1.98.03 2.576.667q.066.07.121.144a2 2 0 0 1 .126-.144c.596-.638 1.52-.784 2.576-.667 1.076.12 1.877.46 2.383 1.1.495.624.607 1.41.607 2.168 0 .5-.047 1.002-.223 1.446.058.199.086.374.11.534l.033.19c.808.336 1.332 1.284 1.392 1.829v1.634C15 11.356 12.068 14 7.998 14m0-1.296c1.995 0 4.011-.969 4.377-1.25V7.738l-.02-.1c-.429.182-.94.253-1.511.253-1.003 0-1.802-.285-2.371-.865A2.82 2.812 0 0 1 8 6.38a2.835 2.828 0 0 1-.476.648c-.569.58-1.368.865-2.371.865-.57 0-1.082-.07-1.511-.254l-.02.101v3.714c.366.282 2.381 1.25 4.376 1.25M6.917 3.347c-.17-.18-.558-.36-1.472-.259-.892.099-1.294.353-1.499.611-.216.272-.323.689-.323 1.356 0 .693.113 1.022.27 1.197.142.158.454.33 1.262.33.746 0 1.171-.204 1.433-.47.275-.282.46-.722.54-1.356.102-.816-.033-1.218-.211-1.409m3.635-.259c-.913-.101-1.302.08-1.47.26-.179.19-.315.592-.212 1.408.08.634.265 1.074.54 1.356.262.266.687.47 1.434.47.807 0 1.12-.172 1.262-.33.156-.175.269-.504.269-1.197 0-.667-.108-1.084-.324-1.356-.204-.258-.606-.512-1.499-.61Z'/><path fill='#fafafa' d='M6.469 8.765a.656.655 0 0 1 .656.654v1.31a.656.655 0 0 1-1.313 0V9.42a.656.655 0 0 1 .657-.654Zm3.718.654v1.31a.656.655 0 0 1-1.312 0V9.42a.656.655 0 0 1 1.313 0z'/></svg>",
@@ -130,10 +132,10 @@
"css-map": "<svg viewBox='0 0 32 32'><path fill='#7e57c2' d='M26 11.998v2h2v14H14v-2h-2v4L30 30V11.998Z'/><path fill='#7e57c2' d='M16 14h-2v-2h-2v2c0 .193 0 .703 1.254 1.033A3.345 3.345 0 0 1 16 18h2v2h2v-2c0-.388-.562-.851-1.254-1.034C16.356 16.34 16 14.84 16 14m-3.254 2.966C10.356 16.34 10 14.84 10 14H8v-2H6v8h2v-2h4v2h2v-2c0-.388-.562-.851-1.254-1.034'/><path fill='#7e57c2' d='M2 2v21.998h22V2Zm20 12h-2v-2h-2v2c0 .193 0 .703 1.254 1.033A3.345 3.345 0 0 1 22 18v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2 2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2 2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2 2 2 0 0 1 2-2h2a2 2 0 0 1 2 2 2 2 0 0 1 2-2h2a2 2 0 0 1 2 2Z'/></svg>",
"css": "<svg viewBox='0 0 32 32'><path fill='#7e57c2' d='M20 18h-2v-2h-2v2c0 .193 0 .703 1.254 1.033A3.345 3.345 0 0 1 20 22h2v2h2v-2c0-.388-.562-.851-1.254-1.034C20.356 20.34 20 18.84 20 18m-3.254 2.966C14.356 20.34 14 18.84 14 18h-2v-2h-2v8h2v-2h4v2h2v-2c0-.388-.562-.851-1.254-1.034'/><path fill='#7e57c2' d='M24 4H4v20a4 4 0 0 0 4 4h16.16A3.84 3.84 0 0 0 28 24.16V8a4 4 0 0 0-4-4m2 14h-2v-2h-2v2c0 .193 0 .703 1.254 1.033A3.345 3.345 0 0 1 26 22v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2 2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2 2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2 2 2 0 0 1 2-2h2a2 2 0 0 1 2 2 2 2 0 0 1 2-2h2a2 2 0 0 1 2 2Z'/></svg>",
"cucumber": "<svg fill-rule='evenodd' viewBox='0 0 33 33'><path fill='#4caf50' d='M16.633 2.088c-7.028 0-12.714 5.686-12.714 12.714 0 6.187 4.435 11.327 10.288 12.471v3.64c7.609-1.148 14.346-7.187 14.848-15.117.303-4.772-2.076-9.644-6.09-12.01a10.6 10.6 0 0 0-1.455-.728l-.243-.097c-.223-.083-.448-.175-.68-.243a12.6 12.6 0 0 0-3.954-.63m2.62 4.707a1.39 1.39 0 0 0-1.213.485c-.233.31-.379.611-.534.922-.466 1.087-.31 2.252.388 3.106 1.087-.233 2.01-.927 2.475-2.014a2.45 2.45 0 0 0 .243-1.02c.048-.824-.634-1.405-1.359-1.48zm-5.654.073c-.708.067-1.382.63-1.382 1.407 0 .31.087.708.243 1.019.466 1.087 1.46 1.78 2.546 2.014.621-.854.782-2.019.316-3.106-.155-.31-.3-.616-.534-.85a1.36 1.36 0 0 0-1.188-.484zM9.79 10.603c-1.224.063-1.77 1.602-.752 2.402.31.233.612.403.922.558 1.087.466 2.344.306 3.275-.315-.233-1.01-1.023-1.936-2.11-2.402-.388-.155-.703-.243-1.092-.243-.087-.01-.161-.004-.243 0m11.961 4.707a3.55 3.55 0 0 0-2.013.583c.233 1.009 1.023 1.935 2.11 2.401.389.155.705.243 1.093.243 1.397.078 2.08-1.65.994-2.426-.31-.233-.611-.379-.922-.534a3.4 3.4 0 0 0-1.262-.267m-10.603.073a3.4 3.4 0 0 0-1.261.267c-.389.155-.69.325-.923.558-1.009.854-.33 2.48 1.068 2.402a2.5 2.5 0 0 0 1.092-.243c1.087-.466 1.859-1.392 2.014-2.401a3.47 3.47 0 0 0-1.99-.583m3.931 2.378c-1.087.233-2.009.927-2.475 2.014-.155.31-.243.684-.243.994-.077 1.32 1.724 2.03 2.5 1.02.233-.31.378-.612.534-.923.466-1.009.306-2.174-.316-3.105m2.887.073c-.621.854-.781 2.018-.315 3.105.155.311.3.616.534.85.854.93 2.65.242 2.572-.923 0-.31-.088-.708-.243-1.019-.466-1.087-1.46-1.78-2.547-2.013z'/></svg>",
- "cuda": "<svg viewBox='0 0 32 32'><path fill='#7CB342' d='M12.496 10.16c-.184 0-.314.01-.496.022V12a7 7 0 0 1 .991-.062 7.34 7.34 0 0 1 5.335 2.457l-2.72 2.156c-1.213-1.922-1.568-2.767-3.606-3v5.468a4.8 4.8 0 0 0 1.486.234c3.969 0 7.667-4.847 7.667-4.847s-3.427-4.402-8.657-4.246m-9.222 4.468A12.46 12.46 0 0 1 12 10.184V8.715c-6.407.489-12 5.602-12 5.602s3.202 8.56 12 9.337v-1.641c-6.454-.756-8.726-7.385-8.726-7.385'/><path fill='#7CB342' d='M12 13.54V12a11.17 11.17 0 0 0-6.3 2.828s1.424 4.791 6.3 5.614v-1.423a6.48 6.48 0 0 1-3.72-3.913A5.04 5.04 0 0 1 12 13.54m0-7.566v2.74l.496-.032c7.267-.234 12.014 5.624 12.014 5.624s-5.442 6.247-11.107 6.247A8.4 8.4 0 0 1 12 20.431V22a11 11 0 0 0 1.19.108c5.276 0 9.058-2.478 12.757-5.479.612.467 3.12 1.59 3.64 2.079-3.51 2.779-11.696 5.013-16.337 5.013a12 12 0 0 1-1.25-.066V26h20V6Z'/></svg>",
+ "cuda": "<svg viewBox='0 0 32 32'><path fill='#7cb342' d='M12.496 10.16c-.184 0-.314.01-.496.022V12a7 7 0 0 1 .991-.062 7.34 7.34 0 0 1 5.335 2.457l-2.72 2.156c-1.213-1.922-1.568-2.767-3.606-3v5.468a4.8 4.8 0 0 0 1.486.234c3.969 0 7.667-4.847 7.667-4.847s-3.427-4.402-8.657-4.246m-9.222 4.468A12.46 12.46 0 0 1 12 10.184V8.715c-6.407.489-12 5.602-12 5.602s3.202 8.56 12 9.337v-1.641c-6.454-.756-8.726-7.385-8.726-7.385'/><path fill='#7cb342' d='M12 13.54V12a11.17 11.17 0 0 0-6.3 2.828s1.424 4.791 6.3 5.614v-1.423a6.48 6.48 0 0 1-3.72-3.913A5.04 5.04 0 0 1 12 13.54m0-7.566v2.74l.496-.032c7.267-.234 12.014 5.624 12.014 5.624s-5.442 6.247-11.107 6.247A8.4 8.4 0 0 1 12 20.431V22a11 11 0 0 0 1.19.108c5.276 0 9.058-2.478 12.757-5.479.612.467 3.12 1.59 3.64 2.079-3.51 2.779-11.696 5.013-16.337 5.013a12 12 0 0 1-1.25-.066V26h20V6Z'/></svg>",
"cypress": "<svg viewBox='0 0 24 24'><path fill='#00bfa5' d='M11.998 2A9.993 9.993 0 0 0 2 12a9.993 9.993 0 0 0 10 10c5.528 0 10-4.473 10-10-.001-5.527-4.51-10-10.002-10m-4.69 12.146c.327.436.763.618 1.381.618.292 0 .583-.037.837-.146.255-.108.546-.255.908-.473l1.019 1.454c-.836.692-1.782 1.018-2.873 1.018-.873 0-1.6-.182-2.254-.545a3.66 3.66 0 0 1-1.454-1.599c-.327-.691-.509-1.491-.509-2.437 0-.908.182-1.745.508-2.436a3.85 3.85 0 0 1 1.457-1.672c.617-.4 1.38-.582 2.217-.582.583 0 1.128.072 1.564.254.49.19.944.46 1.345.8l-1.018 1.382a4 4 0 0 0-.836-.474c-.254-.108-.582-.145-.873-.145-1.236 0-1.854.945-1.854 2.872-.036.983.146 1.673.437 2.11zm10 2.254c-.363 1.128-.909 1.964-1.673 2.582-.763.619-1.782.946-3.054 1.055l-.254-1.708c.836-.11 1.454-.292 1.854-.583.145-.108.437-.436.437-.436l-3.019-9.673h2.508l1.746 7.236 1.855-7.236h2.436z'/></svg>",
"d": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M21.805 8.063a5 5 0 0 0-3.502.727A10.95 10.95 0 0 0 11 6H2.5a.5.5 0 0 0-.5.5v21a.5.5 0 0 0 .5.5H11a10.995 10.995 0 0 0 10.954-10.096 4.998 4.998 0 0 0-.149-9.841M11 24H6V10h5a7 7 0 0 1 0 14'/><circle cx='28' cy='7' r='1.5' fill='#f44336'/></svg>",
- "dart": "<svg viewBox='0 0 32 32'><path fill='#4FC3F7' d='M16.83 2a1.3 1.3 0 0 0-.916.377l-.013.01L7.323 7.34l8.556 8.55v.005l10.283 10.277 1.96-3.529-7.068-16.96-3.299-3.297A1.3 1.3 0 0 0 16.828 2Z'/><path fill='#01579B' d='m7.343 7.32-4.955 8.565-.01.013a1.297 1.297 0 0 0 .004 1.835l.005.005 4.106 4.107 16.064 6.314 3.632-2.015-.098-.098-.025.002L15.995 15.97h-.012z'/><path fill='#01579B' d='m7.321 7.324 8.753 8.755h.013L26.16 26.156l3.835-.73L30 14.089l-4.049-3.965a6.5 6.5 0 0 0-3.618-1.612l.002-.043L7.323 7.325Z'/><path fill='#64B5F6' d='m7.332 7.335 8.758 8.75v.013l10.079 10.071L25.436 30H14.09l-3.967-4.048a6.5 6.5 0 0 1-1.611-3.618l-.045.004Z'/></svg>",
+ "dart": "<svg viewBox='0 0 32 32'><path fill='#4fc3f7' d='M16.83 2a1.3 1.3 0 0 0-.916.377l-.013.01L7.323 7.34l8.556 8.55v.005l10.283 10.277 1.96-3.529-7.068-16.96-3.299-3.297A1.3 1.3 0 0 0 16.828 2Z'/><path fill='#01579b' d='m7.343 7.32-4.955 8.565-.01.013a1.297 1.297 0 0 0 .004 1.835l.005.005 4.106 4.107 16.064 6.314 3.632-2.015-.098-.098-.025.002L15.995 15.97h-.012z'/><path fill='#01579b' d='m7.321 7.324 8.753 8.755h.013L26.16 26.156l3.835-.73L30 14.089l-4.049-3.965a6.5 6.5 0 0 0-3.618-1.612l.002-.043L7.323 7.325Z'/><path fill='#64b5f6' d='m7.332 7.335 8.758 8.75v.013l10.079 10.071L25.436 30H14.09l-3.967-4.048a6.5 6.5 0 0 1-1.611-3.618l-.045.004Z'/></svg>",
"dart_generated": "<svg viewBox='0 0 32 32'><path fill='#90a4ae' d='M16.83 2a1.3 1.3 0 0 0-.916.377l-.013.01L7.323 7.34l8.556 8.55v.005l10.283 10.277 1.96-3.529-7.068-16.96-3.299-3.297A1.3 1.3 0 0 0 16.828 2Z'/><path fill='#455a64' d='m7.343 7.32-4.955 8.565-.01.013a1.297 1.297 0 0 0 .004 1.835l.005.005 4.106 4.107 16.064 6.314 3.632-2.015-.098-.098-.025.002L15.995 15.97h-.012z'/><path fill='#455a64' d='m7.321 7.324 8.753 8.755h.013L26.16 26.156l3.835-.73L30 14.089l-4.049-3.965a6.5 6.5 0 0 0-3.618-1.612l.002-.043L7.323 7.325Z'/><path fill='#90a4ae' d='m7.332 7.335 8.758 8.75v.013l10.079 10.071L25.436 30H14.09l-3.967-4.048a6.5 6.5 0 0 1-1.611-3.618l-.045.004Z'/></svg>",
"database": "<svg viewBox='0 0 32 32'><path fill='#ffca28' d='M16 24c-5.525 0-10-.9-10-2v4c0 1.1 4.475 2 10 2s10-.9 10-2v-4c0 1.1-4.475 2-10 2m0-8c-5.525 0-10-.9-10-2v4c0 1.1 4.475 2 10 2s10-.9 10-2v-4c0 1.1-4.475 2-10 2m0-12C10.477 4 6 4.895 6 6v4c0 1.1 4.475 2 10 2s10-.9 10-2V6c0-1.105-4.477-2-10-2'/></svg>",
"deepsource": "<svg viewBox='0 0 16 16'><path fill='#1de9b6' d='M2 2h9a1 1 0 0 1 1 .992A1 1 0 0 1 11 4H2z'/><path fill='#f44336' d='M2 12h11a1 1 0 0 1 1 1 1 1 0 0 1-1 1H2z'/><path fill='#ffb300' d='M2 9h7a1 1 0 0 0 1-1 1 1 0 0 0-1-1H2z'/></svg>",
@@ -148,31 +150,32 @@
"disc": "<svg viewBox='0 0 32 32'><path fill='#b0bec5' d='M16 12a4 4 0 1 1-4 4 4.005 4.005 0 0 1 4-4m0-10a14 14 0 1 0 14 14A14 14 0 0 0 16 2'/></svg>",
"django": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M22 2h4v4h-4zm0 8v12.13A3.88 3.88 0 0 1 18.13 26H18v4h.13A7.866 7.866 0 0 0 26 22.13V10Zm-8-8h4v20h-4z'/><path fill='#43a047' d='M11.838 12A2.165 2.165 0 0 1 14 14.162v4.955l-.77.257a5.03 5.03 0 0 1-2.812.108A3.19 3.19 0 0 1 8 16.384v-.547A3.84 3.84 0 0 1 11.838 12m0-4A7.84 7.84 0 0 0 4 15.837v.547a7.19 7.19 0 0 0 5.448 6.978 9.03 9.03 0 0 0 5.047-.194L18 22v-7.838A6.16 6.16 0 0 0 11.838 8'/></svg>",
"dll": "<svg viewBox='0 0 24 24'><path fill='#42a5f5' d='M6 2a2 2 0 0 0-2 2v16c0 1.11.89 2 2 2h6v-2H6V4h7v5h5v3h2V8l-6-6m4 12a.26.26 0 0 0-.26.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82a4.2 4.2 0 0 0 0 1l-1.06.82a.26.26 0 0 0-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.26.21h2c.11 0 .22-.09.24-.21l.19-1.32c.3-.13.57-.29.84-.47l1.23.5c.13 0 .26 0 .33-.13l1-1.73a.26.26 0 0 0-.06-.32l-1.07-.82c.02-.17.04-.33.04-.5s-.01-.33-.04-.5l1.06-.82a.26.26 0 0 0 .06-.32l-1-1.73c-.06-.13-.19-.13-.32-.13l-1.23.5c-.27-.18-.54-.35-.85-.47l-.19-1.32A.236.236 0 0 0 20 14m-1 3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5c-.84 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5'/></svg>",
- "docker": "<svg viewBox='0 0 24 24'><path fill='#0288D1' d='M21.81 10.25c-.06-.04-.56-.43-1.64-.43-.28 0-.56.03-.84.08-.21-1.4-1.38-2.11-1.43-2.14l-.29-.17-.18.27c-.24.36-.43.77-.51 1.19-.2.8-.08 1.56.33 2.21-.49.28-1.29.35-1.46.35H2.62c-.34 0-.62.28-.62.63 0 1.15.18 2.3.58 3.38.45 1.19 1.13 2.07 2 2.61.98.6 2.59.94 4.42.94.79 0 1.61-.07 2.42-.22 1.12-.2 2.2-.59 3.19-1.16A8.3 8.3 0 0 0 16.78 16c1.05-1.17 1.67-2.5 2.12-3.65h.19c1.14 0 1.85-.46 2.24-.85.26-.24.45-.53.59-.87l.08-.24zm-17.96.99h1.76c.08 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16H3.85c-.09 0-.16.07-.16.16v1.58c.01.09.07.16.16.16m2.43 0h1.76c.08 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16H6.28c-.09 0-.16.07-.16.16v1.58c.01.09.07.16.16.16m2.47 0h1.75c.1 0 .17-.07.17-.16V9.5c0-.08-.06-.16-.17-.16H8.75c-.08 0-.15.07-.15.16v1.58c0 .09.06.16.15.16m2.44 0h1.77c.08 0 .15-.07.15-.16V9.5c0-.08-.06-.16-.15-.16h-1.77c-.08 0-.15.07-.15.16v1.58c0 .09.07.16.15.16M6.28 9h1.76c.08 0 .16-.09.16-.18V7.25c0-.09-.07-.16-.16-.16H6.28c-.09 0-.16.06-.16.16v1.57c.01.09.07.18.16.18m2.47 0h1.75c.1 0 .17-.09.17-.18V7.25c0-.09-.06-.16-.17-.16H8.75c-.08 0-.15.06-.15.16v1.57c0 .09.06.18.15.18m2.44 0h1.77c.08 0 .15-.09.15-.18V7.25c0-.09-.07-.16-.15-.16h-1.77c-.08 0-.15.06-.15.16v1.57c0 .09.07.18.15.18m0-2.28h1.77c.08 0 .15-.07.15-.16V5c0-.1-.07-.17-.15-.17h-1.77c-.08 0-.15.06-.15.17v1.56c0 .08.07.16.15.16m2.46 4.52h1.76c.09 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16h-1.76c-.08 0-.15.07-.15.16v1.58c0 .09.07.16.15.16'/></svg>",
- "doctex.clone": "<svg viewBox='0 0 1024 1024'><path fill='#90A4AE' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "docker": "<svg viewBox='0 0 24 24'><path fill='#0288d1' d='M21.81 10.25c-.06-.04-.56-.43-1.64-.43-.28 0-.56.03-.84.08-.21-1.4-1.38-2.11-1.43-2.14l-.29-.17-.18.27c-.24.36-.43.77-.51 1.19-.2.8-.08 1.56.33 2.21-.49.28-1.29.35-1.46.35H2.62c-.34 0-.62.28-.62.63 0 1.15.18 2.3.58 3.38.45 1.19 1.13 2.07 2 2.61.98.6 2.59.94 4.42.94.79 0 1.61-.07 2.42-.22 1.12-.2 2.2-.59 3.19-1.16A8.3 8.3 0 0 0 16.78 16c1.05-1.17 1.67-2.5 2.12-3.65h.19c1.14 0 1.85-.46 2.24-.85.26-.24.45-.53.59-.87l.08-.24zm-17.96.99h1.76c.08 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16H3.85c-.09 0-.16.07-.16.16v1.58c.01.09.07.16.16.16m2.43 0h1.76c.08 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16H6.28c-.09 0-.16.07-.16.16v1.58c.01.09.07.16.16.16m2.47 0h1.75c.1 0 .17-.07.17-.16V9.5c0-.08-.06-.16-.17-.16H8.75c-.08 0-.15.07-.15.16v1.58c0 .09.06.16.15.16m2.44 0h1.77c.08 0 .15-.07.15-.16V9.5c0-.08-.06-.16-.15-.16h-1.77c-.08 0-.15.07-.15.16v1.58c0 .09.07.16.15.16M6.28 9h1.76c.08 0 .16-.09.16-.18V7.25c0-.09-.07-.16-.16-.16H6.28c-.09 0-.16.06-.16.16v1.57c.01.09.07.18.16.18m2.47 0h1.75c.1 0 .17-.09.17-.18V7.25c0-.09-.06-.16-.17-.16H8.75c-.08 0-.15.06-.15.16v1.57c0 .09.06.18.15.18m2.44 0h1.77c.08 0 .15-.09.15-.18V7.25c0-.09-.07-.16-.15-.16h-1.77c-.08 0-.15.06-.15.16v1.57c0 .09.07.18.15.18m0-2.28h1.77c.08 0 .15-.07.15-.16V5c0-.1-.07-.17-.15-.17h-1.77c-.08 0-.15.06-.15.17v1.56c0 .08.07.16.15.16m2.46 4.52h1.76c.09 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16h-1.76c-.08 0-.15.07-.15.16v1.58c0 .09.07.16.15.16'/></svg>",
+ "doctex-installer": "<svg viewBox='0 0 16 16'><path fill='#f57f17' d='M1.25 3 1 5h.5c.25-1.25.25-1.5.988-1.5h.262a.25.25 0 0 1 .25.25v5.5c0 .138 0 .25-.5.25H2v.5h3v-.5h-.5c-.5 0-.5-.112-.5-.25v-5.5a.25.25 0 0 1 .25-.25h.25c.75 0 .75.25 1 1.5H6l-.25-2zM6 12v2h9v-2h-.5v.5c0 .5-.112.5-.25.5h-7.5c-.138 0-.25 0-.25-.5V12zm4-10v8c-.5-.75-2-1.75-2.75-2L7 8.5c1.5.5 2.75 2 3.5 3.5.75-1.5 2-3 3.5-3.5l-.25-.5C13 8.25 11.5 9.25 11 10V2z'/></svg>",
+ "doctex.clone": "<svg viewBox='0 0 1024 1024'><path fill='#f57f17' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
"document": "<svg fill='none' viewBox='0 0 24 24'><path d='M0 0h24v24H0z'/><path fill='#42a5f5' d='M8 16h8v2H8zm0-4h8v2H8zm6-10H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8zm4 18H6V4h7v5h5z'/></svg>",
"dotjs": "<svg viewBox='0 0 400 400'><g fill='#2196f3' fill-opacity='.604' transform='translate(-6.66 100.49)'><ellipse cx='37.18' cy='-256.97' rx='110.14' ry='139.47' transform='matrix(-.3005 .95378 -.96071 -.27755 0 0)'/><ellipse cx='38.835' cy='-197.03' rx='110.14' ry='139.47' transform='matrix(-.3005 .95378 -.96071 -.27755 0 0)'/><ellipse cx='-224.78' cy='-5.066' rx='110.14' ry='139.47' transform='matrix(-.95378 -.3005 .27755 -.96071 0 0)'/><ellipse cx='-228.55' cy='-60.291' rx='110.14' ry='139.47' transform='matrix(-.95378 -.3005 .27755 -.96071 0 0)'/></g></svg>",
"drawio": "<svg viewBox='0 0 32 32'><path fill='#fb8c00' d='m25.329 20-7.001-8H20V4h-8v8h1.672l-7.001 8H4v8h8v-8H9.328L16 12.376 22.672 20H20v8h8v-8z'/></svg>",
"drizzle": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='m5.22 23.118 3.647-6.593a1.712 1.712 0 1 0-2.996-1.657L2.224 21.46a1.712 1.712 0 0 0 2.996 1.658m12.02 0 3.648-6.593a1.712 1.712 0 1 0-2.996-1.657l-3.648 6.592a1.712 1.712 0 0 0 2.996 1.658m-3.378-5.96 3.88-6.588a1.706 1.706 0 0 0-2.94-1.73l-3.88 6.588a1.706 1.706 0 0 0 2.94 1.73m12.028 0 3.88-6.588a1.706 1.706 0 0 0-2.94-1.73l-3.88 6.588a1.706 1.706 0 0 0 2.94 1.73'/></svg>",
"drone": "<svg viewBox='0 0 230 230'><path fill='#cfd8dc' d='m57.01 36.707-.835.834 34.036 34.035c-4.813 7.514-7.584 16.742-7.584 27.239 0 29.18 21.422 48.557 48.557 48.557 10.14 0 19.48-2.706 27.205-7.618l34.21 34.21c-17.726 23.411-45.89 38.151-77.601 38.151-53.627 0-97.114-42.154-97.114-97.114 0-32.685 15.38-60.84 39.125-78.293zm16.188-9.611c12.66-5.927 26.836-9.21 41.799-9.21 53.626 0 97.114 42.155 97.114 97.114 0 15.117-3.29 29.265-9.176 41.833l-30.78-30.78c4.812-7.514 7.584-16.742 7.584-27.239 0-29.18-21.422-48.557-48.557-48.557-10.14 0-19.48 2.706-27.205 7.617zm57.985 100.853c-16.281 0-29.134-11.626-29.134-29.135 0-17.508 12.853-29.134 29.134-29.134s29.134 11.626 29.134 29.134-12.853 29.135-29.134 29.135'/></svg>",
"drone_light": "<svg viewBox='0 0 230 230'><path fill='#546e7a' d='m57.011 36.707-.834.834 34.036 34.035c-4.813 7.514-7.584 16.742-7.584 27.239 0 29.18 21.422 48.557 48.557 48.557 10.14 0 19.48-2.706 27.205-7.618l34.21 34.21c-17.726 23.411-45.89 38.151-77.601 38.151-53.627 0-97.114-42.154-97.114-97.114 0-32.685 15.38-60.84 39.125-78.293zm16.19-9.611c12.66-5.927 26.835-9.21 41.798-9.21 53.626 0 97.114 42.155 97.114 97.114 0 15.117-3.29 29.265-9.176 41.833l-30.78-30.78c4.813-7.514 7.584-16.742 7.584-27.239 0-29.18-21.422-48.557-48.557-48.557-10.14 0-19.48 2.706-27.205 7.617zm57.984 100.853c-16.281 0-29.134-11.626-29.134-29.135s12.853-29.134 29.134-29.134 29.134 11.626 29.134 29.134-12.853 29.135-29.134 29.135'/></svg>",
- "dtx.clone": "<svg viewBox='0 0 1024 1024'><path fill='#90A4AE' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
- "duc": "<svg viewBox='0 0 16 16'><path fill='#FF5252' d='M12.564 8.49c.06-.232.124-.46.156-.696.096-.719-.02-1.408-.268-2.08a1 1 0 0 0-.077-.162c-.124-.193-.343-.2-.47-.01a1.3 1.3 0 0 0-.128.277c-.238.653-.553 1.26-1.049 1.758a4.6 4.6 0 0 1-.863.678c.057-.23.161-.42.248-.605.34-.721.513-1.483.619-2.264.088-.658.102-1.32-.072-1.964-.317-1.174-1.01-2.022-2.23-2.32-1.232-.301-2.266.07-3.093 1.026-.227.262-.436.541-.663.804-.377.44-.842.758-1.382.97-.163.065-.328.058-.494.06-.144 0-.29.001-.425.057-.411.168-.503.68-.172 1.03.316.334.732.465 1.176.536.54.088 1.041-.087 1.55-.216.48-.123.96-.253 1.439-.38.086-.022.197-.062.25.008.047.062-.023.158-.062.231-.117.218-.301.369-.502.504-.511.344-1.056.639-1.535 1.031-.914.751-1.528 1.684-1.673 2.877-.169 1.384.26 2.593 1.173 3.627.6.68 1.332 1.196 2.202 1.467 1.677.523 3.243.282 4.656-.784 1.448-1.092 2.2-2.583 2.438-4.356a3.5 3.5 0 0 0 .016-.934c-.051-.381-.274-.478-.585-.255q-.065.046-.134.088c-.008.005-.021-.001-.046-.004Z'/></svg>",
+ "dtx.clone": "<svg viewBox='0 0 1024 1024'><path fill='#f57f17' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "duc": "<svg viewBox='0 0 16 16'><path fill='#ff5252' d='M12.564 8.49c.06-.232.124-.46.156-.696.096-.719-.02-1.408-.268-2.08a1 1 0 0 0-.077-.162c-.124-.193-.343-.2-.47-.01a1.3 1.3 0 0 0-.128.277c-.238.653-.553 1.26-1.049 1.758a4.6 4.6 0 0 1-.863.678c.057-.23.161-.42.248-.605.34-.721.513-1.483.619-2.264.088-.658.102-1.32-.072-1.964-.317-1.174-1.01-2.022-2.23-2.32-1.232-.301-2.266.07-3.093 1.026-.227.262-.436.541-.663.804-.377.44-.842.758-1.382.97-.163.065-.328.058-.494.06-.144 0-.29.001-.425.057-.411.168-.503.68-.172 1.03.316.334.732.465 1.176.536.54.088 1.041-.087 1.55-.216.48-.123.96-.253 1.439-.38.086-.022.197-.062.25.008.047.062-.023.158-.062.231-.117.218-.301.369-.502.504-.511.344-1.056.639-1.535 1.031-.914.751-1.528 1.684-1.673 2.877-.169 1.384.26 2.593 1.173 3.627.6.68 1.332 1.196 2.202 1.467 1.677.523 3.243.282 4.656-.784 1.448-1.092 2.2-2.583 2.438-4.356a3.5 3.5 0 0 0 .016-.934c-.051-.381-.274-.478-.585-.255q-.065.046-.134.088c-.008.005-.021-.001-.046-.004Z'/></svg>",
"dune": "<svg viewBox='0 0 24 24'><path fill='#f57c00' d='m14 6-3.75 5 2.85 3.8-1.6 1.2C9.81 13.75 7 10 7 10l-6 8h22z'/></svg>",
"edge": "<svg viewBox='0 0 24 24'><path fill='#ef6c00' d='M12 15c.81 0 1.5-.3 2.11-.89.59-.61.89-1.3.89-2.11s-.3-1.5-.89-2.11C13.5 9.3 12.81 9 12 9s-1.5.3-2.11.89C9.3 10.5 9 11.19 9 12s.3 1.5.89 2.11c.61.59 1.3.89 2.11.89m0-13c2.75 0 5.1 1 7.05 2.95S22 9.25 22 12v1.45c0 1-.35 1.85-1 2.55-.7.67-1.5 1-2.5 1-1.2 0-2.19-.5-2.94-1.5-1 1-2.18 1.5-3.56 1.5-1.37 0-2.55-.5-3.54-1.46C7.5 14.55 7 13.38 7 12c0-1.37.5-2.55 1.46-3.54C9.45 7.5 10.63 7 12 7c1.38 0 2.55.5 3.54 1.46C16.5 9.45 17 10.63 17 12v1.45c0 .41.16.77.46 1.08s.65.47 1.04.47c.42 0 .77-.16 1.07-.47s.43-.67.43-1.08V12c0-2.19-.77-4.07-2.35-5.65S14.19 4 12 4s-4.07.77-5.65 2.35S4 9.81 4 12s.77 4.07 2.35 5.65S9.81 20 12 20h5v2h-5c-2.75 0-5.1-1-7.05-2.95S2 14.75 2 12s1-5.1 2.95-7.05S9.25 2 12 2'/></svg>",
- "editorconfig": "<svg clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 3473 3473'><path fill='#EDE7F6' d='M989.342 1977.409c41.146-26.835 75.137-93.922 54.564-141.33-56.353 24.151-53.67 79.61-54.564 141.33m636.877 153.851c44.724-14.311 87.66-64.402 63.509-116.283-34.886 24.151-57.248 57.247-63.51 116.284z'/><g fill='#FAFAFA'><path d='M374.827 2871.899c0 56.352 14.312 117.178 53.67 138.645 144.907 81.4 652.977 17.89 825.614-20.573 90.343-20.573 163.692-87.66 248.668-124.334 191.421-83.187 330.067-150.274 483.025-262.085 110.916-81.399 287.131-310.388 305.915-447.245l-151.169-33.991c-3.578 153.852-38.463 188.737-175.32 224.517-92.132 25.046-271.925 30.413-365.846 14.312-124.334-20.574-180.687-85.871-237.04-160.114-109.128-144.907 24.151-245.985-148.485-255.824-181.582 222.728-501.81 62.614-642.244 40.252-59.93 86.765-200.366 650.294-198.577 779.1 86.766-29.517 141.33 2.684 219.15 33.097 275.503 106.444 34.885 200.366-75.137 172.636-75.137-17.89-98.394-67.086-142.224-98.393m360.48-1285.383c111.81 21.468 211.1 67.982 305.915 115.39 154.747 76.926 182.476 66.192 196.788 173.53 1.789 19.68-1.789 30.413 54.564 48.303 94.816 29.518-54.564-23.257 199.471-22.362 151.169.894 497.337 61.72 609.148 132.384 46.513 29.519 37.568 67.087 194.999 62.615-1.79-185.16-50.986-461.557-123.44-631.51-88.554-205.733-205.733-237.04-444.561-313.966-139.54-44.725-549.217-93.922-676.235-15.207-118.967 74.243-141.33 162.798-252.246 318.439-32.202 45.619-43.83 80.504-64.403 132.384'/><path d='M1720.14 1966.675c89.45 36.674-4.472 273.714-128.806 216.466-40.252-113.6 55.458-178.003 81.398-228.99-53.67-8.05-206.627-32.2-252.246-15.206-59.036 22.363-72.454 148.486-42.041 207.522 143.118 280.87 775.523 220.94 708.436 2.684-26.835-88.555-51.88-102.867-142.224-133.28-72.454-24.15-144.907-49.196-224.517-49.196m-1124.374-31.307c71.56 68.875 233.462 79.61 338.117 84.976 13.418-138.646 25.046-242.407 135.963-234.356 54.564 74.242 25.94 161.902-31.307 218.255 97.5-.894 153.852-74.242 139.54-180.687-82.293-59.036-331.856-177.109-457.084-194.104-34.885 37.569-120.756 243.301-125.229 305.916'/></g><path d='M427.602 2820.913c59.036-5.367 212.889 39.357 225.412 89.449-95.71 11.628-217.361 2.683-225.412-89.45zm-52.775 50.986c43.83 31.307 67.087 80.504 142.224 98.393 110.022 27.73 350.64-66.192 75.137-172.636-77.82-30.413-132.384-62.614-219.15-33.096-1.789-128.807 138.646-692.336 198.577-779.101 140.435 22.362 460.662 182.476 642.244-40.252 172.636 9.84 39.357 110.917 148.485 255.824 56.353 74.243 112.706 139.54 237.04 160.114 93.921 16.1 273.714 10.734 365.846-14.312 136.857-35.78 171.742-70.665 175.32-224.517l151.17 33.99c-18.785 136.858-195 365.847-305.916 447.246-152.958 111.81-291.604 178.898-483.025 262.085-84.976 36.674-158.325 103.761-248.668 124.334-172.637 38.463-680.707 101.972-825.614 20.574-39.358-21.468-53.67-82.294-53.67-138.646M1626.22 2131.26c6.261-59.037 28.623-92.133 63.508-116.284 24.152 51.88-18.784 101.972-63.508 116.284m93.921-164.586c79.61 0 152.063 25.045 224.517 49.197 90.344 30.412 115.39 44.724 142.224 133.279 67.087 218.255-565.318 278.186-708.436-2.684-30.413-59.036-16.995-185.16 42.041-207.522 45.619-16.995 198.577 7.156 252.246 15.207-25.94 50.986-121.65 115.389-81.398 228.99 124.334 57.247 218.255-179.793 128.806-216.467m-730.798 10.734c.894-61.72-1.79-117.179 54.564-141.33 20.573 47.408-13.418 114.495-54.564 141.33m-393.576-42.041c4.473-62.615 90.344-268.347 125.229-305.916 125.228 16.995 374.791 135.068 457.084 194.104 14.312 106.445-42.04 179.793-139.54 180.687 57.247-56.353 85.87-144.013 31.307-218.255-110.917-8.05-122.545 95.71-135.963 234.356-104.655-5.367-266.558-16.1-338.117-84.976m-89.449-71.56c-33.096-91.238-33.096-233.462 107.339-245.09l-71.56 199.471c-18.783 42.936-18.783 33.096-35.779 45.62zm228.99-277.292c20.573-51.88 32.201-86.765 64.403-132.384 110.917-155.641 133.279-244.196 252.246-318.439 127.018-78.715 536.694-29.518 676.235 15.207 238.828 76.926 356.007 108.233 444.561 313.966 72.454 169.953 121.65 446.35 123.44 631.51-157.43 4.472-148.486-33.096-195-62.615-111.81-70.664-457.978-131.49-609.147-132.384-254.035-.895-104.655 51.88-199.471 22.362-56.353-17.89-52.775-28.624-54.564-48.302-14.312-107.34-42.041-96.605-196.788-173.531-94.816-47.408-194.104-93.922-305.915-115.39m1583.247-43.83c-16.995-56.352 14.312-52.775 68.876-91.238 31.307-22.362 56.353-45.619 94.816-67.086 144.013-80.504 412.36-93.922 526.854 1.789 46.514 38.463 122.545 113.6 110.917 211.994-24.151 195.893-158.325 303.232-268.347 392.68-111.811 91.239-297.865 185.16-490.18 122.546-16.101-39.358-3.578-288.92-22.363-381.053-16.995-82.293-8.05-91.238 39.358-140.435 139.54-144.907 441.878-250.457 613.62-126.123 72.454 53.67 51.88 74.243 89.449 115.39 46.513-50.092-40.252-218.256-360.48-207.522-217.36 7.156-311.282 177.109-402.52 169.058m-1302.377-508.964c4.472-124.335 118.967-381.948 233.461-471.397 138.646-107.338 283.554-208.416 496.442-87.66 52.775 29.519 50.092 44.725 55.459 118.073 4.472 70.665-1.79 96.605-19.679 153.852-141.33 456.19-259.402 194.105-712.014 302.338 16.995-148.485 145.802-280.87 217.361-349.746 122.545-118.967 211.1-195.893 395.365-170.847 50.986 84.976 56.352 138.646-5.367 237.934-82.293 132.385-102.867 124.334-90.344 214.678 64.403-16.101 84.082-78.715 113.6-141.33 179.793-375.686-81.398-421.305-241.512-352.429-107.339 45.62-298.76 256.719-361.374 383.736-12.523 25.046-25.94 57.248-37.568 84.977zm708.436 18.784c18.784-111.811 129.7-139.54 129.7-483.92 0-148.485-182.475-281.764-421.304-182.475-204.838 84.082-236.145 148.485-345.273 313.071-102.867 155.642-99.289 326.49-187.843 470.502-25.94 41.147-49.197 55.458-77.82 96.605-20.574 30.413-35.78 68.876-56.354 104.655-42.04 68.876-84.976 118.968-118.967 201.26-107.339 2.684-197.682 4.473-208.416 115.39-14.312 152.063 57.247 189.632 57.247 246.879-.894 61.72-251.351 684.285-181.581 1055.498 19.679 101.972 86.765 102.867 194.104 115.39 258.508 31.307 593.942 20.573 825.614-72.454l420.41-201.26c106.445-59.931 285.343-173.532 364.953-256.72 56.353-58.141 85.87-107.338 134.173-176.214 66.192-96.605 67.981-94.816 82.293-226.306 87.66 16.101 251.352 54.564 305.916 101.972-6.262 61.72-36.674 32.202-36.674 87.66 34.885.895 93.027-42.935 107.339-91.238-36.675-53.67-75.138-44.724-127.913-87.66 42.042-33.096 118.073-48.302 176.215-72.453 125.229-51.88 339.012-209.311 391.787-352.43 42.04-115.389 10.734-307.704-57.248-382.841-71.559-78.715-237.934-118.967-373.897-118.967-161.902 0-329.172 116.283-459.767 166.375-50.092-43.83-53.67-93.922-90.344-142.224-42.04-57.248-315.755-200.366-446.35-228.095'/><path fill='#EFEBE9' d='M2318.554 1542.686c91.238 8.05 185.16-161.902 402.52-169.058 320.228-10.734 406.993 157.43 360.48 207.521-37.569-41.146-16.995-61.72-89.45-115.389-171.741-124.334-474.079-18.784-613.62 126.123-47.407 49.197-56.352 58.142-39.357 140.435 18.785 92.133 6.262 341.695 22.362 381.053 192.316 62.614 378.37-31.307 490.181-122.545 110.022-89.45 244.196-196.788 268.347-392.681 11.628-98.394-64.403-173.531-110.917-211.994-114.494-95.71-382.841-82.293-526.854-1.79-38.463 21.468-63.51 44.725-94.816 67.087-54.564 38.464-85.871 34.886-68.876 91.238m-1302.377-508.964 43.83-77.821c11.628-27.73 25.045-59.93 37.568-84.977 62.614-127.017 254.035-338.117 361.374-383.736 160.114-68.876 421.305-23.257 241.512 352.43-29.518 62.614-49.197 125.228-113.6 141.329-12.523-90.344 8.05-82.293 90.344-214.678 61.72-99.288 56.353-152.958 5.367-237.934-184.265-25.046-272.82 51.88-395.365 170.847-71.56 68.876-200.366 201.26-217.361 349.746 452.612-108.233 570.685 153.852 712.014-302.338 17.89-57.247 24.151-83.187 19.679-153.852-5.367-73.348-2.684-88.554-55.459-118.073-212.888-120.756-357.796-19.678-496.442 87.66-114.494 89.45-228.989 347.062-233.461 471.397'/><path fill='#EEE' d='M506.317 1863.808c16.996-12.523 16.996-2.683 35.78-45.619l71.559-199.47c-140.435 11.627-140.435 153.851-107.339 245.09z'/><path fill='#EFEBE9' d='M653.014 2910.362c-12.523-50.092-166.376-94.816-225.412-89.45 8.05 92.133 129.701 101.078 225.412 89.45'/></svg>",
+ "editorconfig": "<svg clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 3473 3473'><path fill='#ede7f6' d='M989.342 1977.409c41.146-26.835 75.137-93.922 54.564-141.33-56.353 24.151-53.67 79.61-54.564 141.33m636.877 153.851c44.724-14.311 87.66-64.402 63.509-116.283-34.886 24.151-57.248 57.247-63.51 116.284z'/><g fill='#fafafa'><path d='M374.827 2871.899c0 56.352 14.312 117.178 53.67 138.645 144.907 81.4 652.977 17.89 825.614-20.573 90.343-20.573 163.692-87.66 248.668-124.334 191.421-83.187 330.067-150.274 483.025-262.085 110.916-81.399 287.131-310.388 305.915-447.245l-151.169-33.991c-3.578 153.852-38.463 188.737-175.32 224.517-92.132 25.046-271.925 30.413-365.846 14.312-124.334-20.574-180.687-85.871-237.04-160.114-109.128-144.907 24.151-245.985-148.485-255.824-181.582 222.728-501.81 62.614-642.244 40.252-59.93 86.765-200.366 650.294-198.577 779.1 86.766-29.517 141.33 2.684 219.15 33.097 275.503 106.444 34.885 200.366-75.137 172.636-75.137-17.89-98.394-67.086-142.224-98.393m360.48-1285.383c111.81 21.468 211.1 67.982 305.915 115.39 154.747 76.926 182.476 66.192 196.788 173.53 1.789 19.68-1.789 30.413 54.564 48.303 94.816 29.518-54.564-23.257 199.471-22.362 151.169.894 497.337 61.72 609.148 132.384 46.513 29.519 37.568 67.087 194.999 62.615-1.79-185.16-50.986-461.557-123.44-631.51-88.554-205.733-205.733-237.04-444.561-313.966-139.54-44.725-549.217-93.922-676.235-15.207-118.967 74.243-141.33 162.798-252.246 318.439-32.202 45.619-43.83 80.504-64.403 132.384'/><path d='M1720.14 1966.675c89.45 36.674-4.472 273.714-128.806 216.466-40.252-113.6 55.458-178.003 81.398-228.99-53.67-8.05-206.627-32.2-252.246-15.206-59.036 22.363-72.454 148.486-42.041 207.522 143.118 280.87 775.523 220.94 708.436 2.684-26.835-88.555-51.88-102.867-142.224-133.28-72.454-24.15-144.907-49.196-224.517-49.196m-1124.374-31.307c71.56 68.875 233.462 79.61 338.117 84.976 13.418-138.646 25.046-242.407 135.963-234.356 54.564 74.242 25.94 161.902-31.307 218.255 97.5-.894 153.852-74.242 139.54-180.687-82.293-59.036-331.856-177.109-457.084-194.104-34.885 37.569-120.756 243.301-125.229 305.916'/></g><path d='M427.602 2820.913c59.036-5.367 212.889 39.357 225.412 89.449-95.71 11.628-217.361 2.683-225.412-89.45zm-52.775 50.986c43.83 31.307 67.087 80.504 142.224 98.393 110.022 27.73 350.64-66.192 75.137-172.636-77.82-30.413-132.384-62.614-219.15-33.096-1.789-128.807 138.646-692.336 198.577-779.101 140.435 22.362 460.662 182.476 642.244-40.252 172.636 9.84 39.357 110.917 148.485 255.824 56.353 74.243 112.706 139.54 237.04 160.114 93.921 16.1 273.714 10.734 365.846-14.312 136.857-35.78 171.742-70.665 175.32-224.517l151.17 33.99c-18.785 136.858-195 365.847-305.916 447.246-152.958 111.81-291.604 178.898-483.025 262.085-84.976 36.674-158.325 103.761-248.668 124.334-172.637 38.463-680.707 101.972-825.614 20.574-39.358-21.468-53.67-82.294-53.67-138.646M1626.22 2131.26c6.261-59.037 28.623-92.133 63.508-116.284 24.152 51.88-18.784 101.972-63.508 116.284m93.921-164.586c79.61 0 152.063 25.045 224.517 49.197 90.344 30.412 115.39 44.724 142.224 133.279 67.087 218.255-565.318 278.186-708.436-2.684-30.413-59.036-16.995-185.16 42.041-207.522 45.619-16.995 198.577 7.156 252.246 15.207-25.94 50.986-121.65 115.389-81.398 228.99 124.334 57.247 218.255-179.793 128.806-216.467m-730.798 10.734c.894-61.72-1.79-117.179 54.564-141.33 20.573 47.408-13.418 114.495-54.564 141.33m-393.576-42.041c4.473-62.615 90.344-268.347 125.229-305.916 125.228 16.995 374.791 135.068 457.084 194.104 14.312 106.445-42.04 179.793-139.54 180.687 57.247-56.353 85.87-144.013 31.307-218.255-110.917-8.05-122.545 95.71-135.963 234.356-104.655-5.367-266.558-16.1-338.117-84.976m-89.449-71.56c-33.096-91.238-33.096-233.462 107.339-245.09l-71.56 199.471c-18.783 42.936-18.783 33.096-35.779 45.62zm228.99-277.292c20.573-51.88 32.201-86.765 64.403-132.384 110.917-155.641 133.279-244.196 252.246-318.439 127.018-78.715 536.694-29.518 676.235 15.207 238.828 76.926 356.007 108.233 444.561 313.966 72.454 169.953 121.65 446.35 123.44 631.51-157.43 4.472-148.486-33.096-195-62.615-111.81-70.664-457.978-131.49-609.147-132.384-254.035-.895-104.655 51.88-199.471 22.362-56.353-17.89-52.775-28.624-54.564-48.302-14.312-107.34-42.041-96.605-196.788-173.531-94.816-47.408-194.104-93.922-305.915-115.39m1583.247-43.83c-16.995-56.352 14.312-52.775 68.876-91.238 31.307-22.362 56.353-45.619 94.816-67.086 144.013-80.504 412.36-93.922 526.854 1.789 46.514 38.463 122.545 113.6 110.917 211.994-24.151 195.893-158.325 303.232-268.347 392.68-111.811 91.239-297.865 185.16-490.18 122.546-16.101-39.358-3.578-288.92-22.363-381.053-16.995-82.293-8.05-91.238 39.358-140.435 139.54-144.907 441.878-250.457 613.62-126.123 72.454 53.67 51.88 74.243 89.449 115.39 46.513-50.092-40.252-218.256-360.48-207.522-217.36 7.156-311.282 177.109-402.52 169.058m-1302.377-508.964c4.472-124.335 118.967-381.948 233.461-471.397 138.646-107.338 283.554-208.416 496.442-87.66 52.775 29.519 50.092 44.725 55.459 118.073 4.472 70.665-1.79 96.605-19.679 153.852-141.33 456.19-259.402 194.105-712.014 302.338 16.995-148.485 145.802-280.87 217.361-349.746 122.545-118.967 211.1-195.893 395.365-170.847 50.986 84.976 56.352 138.646-5.367 237.934-82.293 132.385-102.867 124.334-90.344 214.678 64.403-16.101 84.082-78.715 113.6-141.33 179.793-375.686-81.398-421.305-241.512-352.429-107.339 45.62-298.76 256.719-361.374 383.736-12.523 25.046-25.94 57.248-37.568 84.977zm708.436 18.784c18.784-111.811 129.7-139.54 129.7-483.92 0-148.485-182.475-281.764-421.304-182.475-204.838 84.082-236.145 148.485-345.273 313.071-102.867 155.642-99.289 326.49-187.843 470.502-25.94 41.147-49.197 55.458-77.82 96.605-20.574 30.413-35.78 68.876-56.354 104.655-42.04 68.876-84.976 118.968-118.967 201.26-107.339 2.684-197.682 4.473-208.416 115.39-14.312 152.063 57.247 189.632 57.247 246.879-.894 61.72-251.351 684.285-181.581 1055.498 19.679 101.972 86.765 102.867 194.104 115.39 258.508 31.307 593.942 20.573 825.614-72.454l420.41-201.26c106.445-59.931 285.343-173.532 364.953-256.72 56.353-58.141 85.87-107.338 134.173-176.214 66.192-96.605 67.981-94.816 82.293-226.306 87.66 16.101 251.352 54.564 305.916 101.972-6.262 61.72-36.674 32.202-36.674 87.66 34.885.895 93.027-42.935 107.339-91.238-36.675-53.67-75.138-44.724-127.913-87.66 42.042-33.096 118.073-48.302 176.215-72.453 125.229-51.88 339.012-209.311 391.787-352.43 42.04-115.389 10.734-307.704-57.248-382.841-71.559-78.715-237.934-118.967-373.897-118.967-161.902 0-329.172 116.283-459.767 166.375-50.092-43.83-53.67-93.922-90.344-142.224-42.04-57.248-315.755-200.366-446.35-228.095'/><path fill='#efebe9' d='M2318.554 1542.686c91.238 8.05 185.16-161.902 402.52-169.058 320.228-10.734 406.993 157.43 360.48 207.521-37.569-41.146-16.995-61.72-89.45-115.389-171.741-124.334-474.079-18.784-613.62 126.123-47.407 49.197-56.352 58.142-39.357 140.435 18.785 92.133 6.262 341.695 22.362 381.053 192.316 62.614 378.37-31.307 490.181-122.545 110.022-89.45 244.196-196.788 268.347-392.681 11.628-98.394-64.403-173.531-110.917-211.994-114.494-95.71-382.841-82.293-526.854-1.79-38.463 21.468-63.51 44.725-94.816 67.087-54.564 38.464-85.871 34.886-68.876 91.238m-1302.377-508.964 43.83-77.821c11.628-27.73 25.045-59.93 37.568-84.977 62.614-127.017 254.035-338.117 361.374-383.736 160.114-68.876 421.305-23.257 241.512 352.43-29.518 62.614-49.197 125.228-113.6 141.329-12.523-90.344 8.05-82.293 90.344-214.678 61.72-99.288 56.353-152.958 5.367-237.934-184.265-25.046-272.82 51.88-395.365 170.847-71.56 68.876-200.366 201.26-217.361 349.746 452.612-108.233 570.685 153.852 712.014-302.338 17.89-57.247 24.151-83.187 19.679-153.852-5.367-73.348-2.684-88.554-55.459-118.073-212.888-120.756-357.796-19.678-496.442 87.66-114.494 89.45-228.989 347.062-233.461 471.397'/><path fill='#eee' d='M506.317 1863.808c16.996-12.523 16.996-2.683 35.78-45.619l71.559-199.47c-140.435 11.627-140.435 153.851-107.339 245.09z'/><path fill='#efebe9' d='M653.014 2910.362c-12.523-50.092-166.376-94.816-225.412-89.45 8.05 92.133 129.701 101.078 225.412 89.45'/></svg>",
"ejs": "<svg fill='none' viewBox='0 0 24 24'><path fill='#ffca28' d='M8.046 4.862.908 12l7.138 7.138 2.71-2.691L6.308 12l4.446-4.447z'/><ellipse cx='14.543' cy='7.812' stroke='#ffca28' stroke-width='1.455' rx='2.101' ry='2.798'/><path fill='#ffca28' d='m20.616 4.152 1.47.69-7.783 15.005-1.47-.69z'/><ellipse cx='20.35' cy='16.198' stroke='#ffca28' stroke-width='1.455' rx='2.101' ry='2.798'/></svg>",
"elixir": "<svg viewBox='0 0 24 24'><path fill='#9575cd' d='M12.173 22.681c-3.86 0-6.99-3.64-6.99-8.13 0-3.678 2.773-8.172 4.916-10.91 1.014-1.296 2.93-2.322 2.93-2.322s-.982 5.239 1.683 7.319c2.366 1.847 4.106 4.25 4.106 6.363 0 4.232-2.784 7.68-6.645 7.68'/></svg>",
- "elm": "<svg viewBox='0 0 323 323'><path fill='#FFB300' d='m106.716 99.763 54.785 54.782 54.779-54.782z'/><path fill='#64DD17' d='M96.881 89.93H216.83l-55.18-55.184H41.7zm131.546 11.593 59.705 59.704L228.16 221.2l-59.705-59.704z'/><path fill='#00B8D4' d='m175.552 34.746 112.703 112.695V34.746z'/><path fill='#455A64' d='m34.746 281.3 119.8-119.8-119.8-119.8z'/><path fill='#FFB300' d='m288.255 175.01-53.148 53.149 53.148 53.14z'/><path fill='#00B8D4' d='M281.3 288.254 161.5 168.455l-119.8 119.8z'/></svg>",
+ "elm": "<svg viewBox='0 0 323 323'><path fill='#ffb300' d='m106.716 99.763 54.785 54.782 54.779-54.782z'/><path fill='#64dd17' d='M96.881 89.93H216.83l-55.18-55.184H41.7zm131.546 11.593 59.705 59.704L228.16 221.2l-59.705-59.704z'/><path fill='#00b8d4' d='m175.552 34.746 112.703 112.695V34.746z'/><path fill='#455a64' d='m34.746 281.3 119.8-119.8-119.8-119.8z'/><path fill='#ffb300' d='m288.255 175.01-53.148 53.149 53.148 53.14z'/><path fill='#00b8d4' d='M281.3 288.254 161.5 168.455l-119.8 119.8z'/></svg>",
"email": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M28 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2m0 6-12 6-12-6V8l12 6 12-6Z'/></svg>",
- "ember": "<svg viewBox='0 0 24 24'><path fill='#FF5722' d='M12.78 4c1.79-.03 3.18.35 4.12 1.99 2.36 5.88-6.08 8.92-6.42 9.04h-.01s-.25 1.6 2.17 1.54c2.98 0 6.1-2.31 7.3-3.3.29-.23.71-.21.97.06l.89.93c.25.27.26.69.02.97-.77.87-2.59 2.65-5.33 3.79 0 0-4.57 2.13-7.65.12-1.83-1.2-2.34-2.64-2.55-4.13 0 0-2.23-.12-3.66-.67-1.44-.57.01-2.26.01-2.26s.44-.71 1.28 0c.83.71 2.4.39 2.4.39.14-1.11.37-2.57 1.06-4.11C8.81 5.14 11 4.04 12.78 4m1.05 3.24c-.94-.91-3.67.91-3.78 5.09 0 0 .81.24 2.58-.98 1.79-1.23 2.13-3.19 1.19-4.11z'/></svg>",
+ "ember": "<svg viewBox='0 0 24 24'><path fill='#ff5722' d='M12.78 4c1.79-.03 3.18.35 4.12 1.99 2.36 5.88-6.08 8.92-6.42 9.04h-.01s-.25 1.6 2.17 1.54c2.98 0 6.1-2.31 7.3-3.3.29-.23.71-.21.97.06l.89.93c.25.27.26.69.02.97-.77.87-2.59 2.65-5.33 3.79 0 0-4.57 2.13-7.65.12-1.83-1.2-2.34-2.64-2.55-4.13 0 0-2.23-.12-3.66-.67-1.44-.57.01-2.26.01-2.26s.44-.71 1.28 0c.83.71 2.4.39 2.4.39.14-1.11.37-2.57 1.06-4.11C8.81 5.14 11 4.04 12.78 4m1.05 3.24c-.94-.91-3.67.91-3.78 5.09 0 0 .81.24 2.58-.98 1.79-1.23 2.13-3.19 1.19-4.11z'/></svg>",
"epub": "<svg viewBox='0 0 16 16'><path fill='#8bc34a' d='M8 12.401 3.601 8 8 3.601l1.468 1.466L6.534 8 8 9.467l4.4-4.4-3.833-3.832a.8.8 0 0 0-1.133 0L1.235 7.434a.8.8 0 0 0 0 1.133l6.199 6.199a.8.8 0 0 0 1.133 0l6.199-6.199a.803.803 0 0 0 0-1.133l-.9-.899z'/></svg>",
"erlang": "<svg viewBox='0 0 30 30'><path fill='#f44336' d='M5.207 4.33q-.072.075-.143.153Q1.5 8.476 1.5 15.33c0 4.418 1.155 7.862 3.459 10.34h19.415c2.553-1.152 4.127-3.43 4.127-3.43l-3.147-2.52L23.9 21.1c-.867.773-.845.931-2.315 1.78-1.495.674-3.04.966-4.634.966-2.515 0-4.423-.909-5.723-2.059-1.286-1.15-1.985-4.511-2.096-6.68l17.458.067-.183-1.472s-.847-7.129-2.541-9.372zm8.76.846c1.565 0 3.22.535 3.961 1.471.74.937.931 1.667.973 3.524H9.11c.112-1.955.436-2.81 1.373-3.698.936-.887 2.03-1.297 3.484-1.297'/></svg>",
"esbuild": "<svg viewBox='0 0 24 24'><path fill='#ffca28' d='M12 2.042A9.957 9.957 0 0 0 2.043 12 9.957 9.957 0 0 0 12 21.957 9.957 9.957 0 0 0 21.957 12 9.957 9.957 0 0 0 12 2.043zM7.617 6.425 13.192 12l-5.575 5.575-1.69-1.69L9.814 12 5.926 8.115zm5.975 0L19.166 12l-5.574 5.575-1.69-1.69L15.787 12l-3.885-3.885z'/></svg>",
"eslint": "<svg viewBox='0 0 32 32'><path fill='#3f51b5' d='M22.713 4H9.287a.5.5 0 0 0-.432.248l-6.708 11.5a.5.5 0 0 0 0 .504l6.708 11.5a.5.5 0 0 0 .432.248h13.426a.5.5 0 0 0 .432-.248l6.708-11.5a.5.5 0 0 0 0-.504l-6.708-11.5A.5.5 0 0 0 22.713 4m-6.937 20.888-7.5-3.75A.5.5 0 0 1 8 20.691v-9.382a.5.5 0 0 1 .276-.447l7.5-3.75a.5.5 0 0 1 .448 0l7.5 3.75a.5.5 0 0 1 .276.447v9.382a.5.5 0 0 1-.276.447l-7.5 3.75a.5.5 0 0 1-.448 0'/><path fill='#7986cb' d='M22 19.441v-6.882a.5.5 0 0 0-.276-.447l-5.5-2.75a.5.5 0 0 0-.448 0l-5.5 2.75a.5.5 0 0 0-.276.447v6.882a.5.5 0 0 0 .276.447l5.5 2.75a.5.5 0 0 0 .448 0l5.5-2.75a.5.5 0 0 0 .276-.447'/></svg>",
"excalidraw": "<svg viewBox='0 0 16 16'><path fill='#5c6bc0' d='m15 1-5 1-9 10 3 3 10-9zm-4 3h1v1h-1zm1 5-3 3 4 3 2-2zM7 4 4 7 2 5 1 1l4 1z'/></svg>",
"exe": "<svg viewBox='0 0 32 32'><path fill='#e64a19' d='M28 4H4a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 22H4V10h24Z'/></svg>",
- "fastlane": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 300 300'><path fill='#2979FF' d='M242.745 89.48c-11.223 0-21.398 4.463-28.867 11.7l-47.366-33.917c.295-1.238.469-2.524.469-3.854 0-9.167-7.432-16.6-16.6-16.6s-16.601 7.433-16.601 16.6c0 9.169 7.433 16.6 16.6 16.6 3.21 0 6.197-.927 8.738-2.504L217.1 119.7c4.52-9.428 14.49-16.77 25.645-16.77 15.492 0 28.051 12.558 28.051 28.05 0 12.38-8.02 22.887-19.148 26.608l3.806 12.91c16.703-5.368 28.79-21.03 28.79-39.518 0-22.92-18.579-41.5-41.5-41.5'/><path fill='#E64A19' d='M109.689 49.166c-3.389 10.669-2.22 21.69 2.405 30.977l-46.546 34.784a16.6 16.6 0 0 0-3.523-1.609c-8.716-2.768-18.026 2.053-20.794 10.768s2.053 18.026 10.767 20.794c8.716 2.769 18.026-2.052 20.795-10.767a16.46 16.46 0 0 0 .257-9.062l57.623-42.379c-7.598-7.144-11.567-18.84-8.2-29.444 4.68-14.727 20.411-22.874 35.139-18.195 11.768 3.738 19.334 14.535 19.513 26.238l13.421.28c-.059-17.5-11.299-33.721-28.873-39.304-21.788-6.921-45.063 5.13-51.984 26.92'/><path fill='#00bcd4' d='M32.81 161.347a41.37 41.37 0 0 0 30.043 7.612l18.362 54.878a16.3 16.3 0 0 0-2.621 2.8c-5.338 7.316-3.686 17.611 3.692 22.994 7.377 5.383 17.685 3.815 23.023-3.501 5.34-7.316 3.687-17.61-3.69-22.994a16.53 16.53 0 0 0-8.489-3.13l-22.086-67.718c-9.128 4.87-21.425 4.875-30.402-1.674-12.465-9.097-15.258-26.492-6.237-38.855 7.21-9.88 19.78-13.556 30.9-9.993l4.456-12.536c-16.566-5.525-35.414-.121-46.179 14.631-13.346 18.291-9.214 44.029 9.229 57.486'/><path fill='#8BC34A' d='M245.283 225.838c-3.42-10.583-10.75-18.811-19.884-23.64l17.72-55.05a16.6 16.6 0 0 0 3.796-.739c8.69-2.808 13.47-12.093 10.679-20.737-2.793-8.646-12.102-13.378-20.793-10.57-8.69 2.806-13.472 12.09-10.679 20.736a16.3 16.3 0 0 0 5.036 7.472l-22.334 67.6c10.315 1.374 20.312 8.527 23.71 19.046 4.72 14.61-3.36 30.298-18.044 35.042-11.735 3.791-24.138-.554-31.055-9.908l-11.078 7.543c10.176 14.106 28.706 20.71 46.23 15.048 21.726-7.019 33.678-30.23 26.696-51.843'/><path fill='#A0F' d='M116.724 270.244c9.003-6.587 14.547-16.139 16.291-26.33l57.906-.59a16.5 16.5 0 0 0 1.887 3.366c5.382 7.355 15.706 8.955 23.061 3.574s8.955-15.705 3.575-23.06c-5.382-7.356-15.706-8.956-23.061-3.575a16.4 16.4 0 0 0-5.54 7.137l-71.283.182c1.908 10.217-1.78 21.958-10.73 28.506-12.428 9.093-29.875 6.39-38.968-6.039-7.266-9.932-6.999-23.068-.257-32.585l-10.631-8.123c-10.249 14.11-10.752 33.77.098 48.601 13.453 18.389 39.265 22.389 57.652 8.936'/></svg>",
+ "fastlane": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 300 300'><path fill='#2979ff' d='M242.745 89.48c-11.223 0-21.398 4.463-28.867 11.7l-47.366-33.917c.295-1.238.469-2.524.469-3.854 0-9.167-7.432-16.6-16.6-16.6s-16.601 7.433-16.601 16.6c0 9.169 7.433 16.6 16.6 16.6 3.21 0 6.197-.927 8.738-2.504L217.1 119.7c4.52-9.428 14.49-16.77 25.645-16.77 15.492 0 28.051 12.558 28.051 28.05 0 12.38-8.02 22.887-19.148 26.608l3.806 12.91c16.703-5.368 28.79-21.03 28.79-39.518 0-22.92-18.579-41.5-41.5-41.5'/><path fill='#e64a19' d='M109.689 49.166c-3.389 10.669-2.22 21.69 2.405 30.977l-46.546 34.784a16.6 16.6 0 0 0-3.523-1.609c-8.716-2.768-18.026 2.053-20.794 10.768s2.053 18.026 10.767 20.794c8.716 2.769 18.026-2.052 20.795-10.767a16.46 16.46 0 0 0 .257-9.062l57.623-42.379c-7.598-7.144-11.567-18.84-8.2-29.444 4.68-14.727 20.411-22.874 35.139-18.195 11.768 3.738 19.334 14.535 19.513 26.238l13.421.28c-.059-17.5-11.299-33.721-28.873-39.304-21.788-6.921-45.063 5.13-51.984 26.92'/><path fill='#00bcd4' d='M32.81 161.347a41.37 41.37 0 0 0 30.043 7.612l18.362 54.878a16.3 16.3 0 0 0-2.621 2.8c-5.338 7.316-3.686 17.611 3.692 22.994 7.377 5.383 17.685 3.815 23.023-3.501 5.34-7.316 3.687-17.61-3.69-22.994a16.53 16.53 0 0 0-8.489-3.13l-22.086-67.718c-9.128 4.87-21.425 4.875-30.402-1.674-12.465-9.097-15.258-26.492-6.237-38.855 7.21-9.88 19.78-13.556 30.9-9.993l4.456-12.536c-16.566-5.525-35.414-.121-46.179 14.631-13.346 18.291-9.214 44.029 9.229 57.486'/><path fill='#8bc34a' d='M245.283 225.838c-3.42-10.583-10.75-18.811-19.884-23.64l17.72-55.05a16.6 16.6 0 0 0 3.796-.739c8.69-2.808 13.47-12.093 10.679-20.737-2.793-8.646-12.102-13.378-20.793-10.57-8.69 2.806-13.472 12.09-10.679 20.736a16.3 16.3 0 0 0 5.036 7.472l-22.334 67.6c10.315 1.374 20.312 8.527 23.71 19.046 4.72 14.61-3.36 30.298-18.044 35.042-11.735 3.791-24.138-.554-31.055-9.908l-11.078 7.543c10.176 14.106 28.706 20.71 46.23 15.048 21.726-7.019 33.678-30.23 26.696-51.843'/><path fill='#a0f' d='M116.724 270.244c9.003-6.587 14.547-16.139 16.291-26.33l57.906-.59a16.5 16.5 0 0 0 1.887 3.366c5.382 7.355 15.706 8.955 23.061 3.574s8.955-15.705 3.575-23.06c-5.382-7.356-15.706-8.956-23.061-3.575a16.4 16.4 0 0 0-5.54 7.137l-71.283.182c1.908 10.217-1.78 21.958-10.73 28.506-12.428 9.093-29.875 6.39-38.968-6.039-7.266-9.932-6.999-23.068-.257-32.585l-10.631-8.123c-10.249 14.11-10.752 33.77.098 48.601 13.453 18.389 39.265 22.389 57.652 8.936'/></svg>",
"favicon": "<svg viewBox='0 0 32 32'><path fill='#ffd54f' d='m16 24 10 6-4-10 8-8-10-.032L16 2l-4 10H2l8 8-4 10Z'/></svg>",
"figma": "<svg viewBox='0 0 32 32'><path fill='#f4511e' d='M12 4h4v8h-4a4 4 0 0 1-4-4 4 4 0 0 1 4-4'/><path fill='#ff8a65' d='M20 12h-4V4h4a4 4 0 0 1 4 4 4 4 0 0 1-4 4'/><rect width='8' height='8' x='16' y='12' fill='#29b6f6' rx='4' transform='rotate(180 20 16)'/><path fill='#7c4dff' d='M12 12h4v8h-4a4 4 0 0 1-4-4 4 4 0 0 1 4-4'/><path fill='#00e676' d='M12 20h4v4a4 4 0 0 1-4 4 4 4 0 0 1-4-4 4 4 0 0 1 4-4'/></svg>",
"file": "<svg viewBox='0 0 16 16'><path fill='#90a4ae' d='M8.668 6h3.664L8.668 2.332zM4 1.332h5.332l4 4v8c0 .738-.594 1.336-1.332 1.336H4a1.33 1.33 0 0 1-1.332-1.336V2.668A1.33 1.33 0 0 1 4 1.332m3.332 1.336H4v10.664h8v-6H7.332z'/></svg>",
@@ -187,8 +190,8 @@
"folder-angular": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='m22 12-6 2v10Zm4 0 6 12V14Zm-.408 8L24 16.034 22.408 20zm-4.789 4L20 26l4 2 4-2-.803-2z'/></svg>",
"folder-animation-open": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f8bbd0' d='M25 14a7 7 0 0 0-2 .29 7.04 7.04 0 0 0-4 0 7 7 0 0 0-2-.29 7 7 0 0 0 0 14 7 7 0 0 0 2-.29 7.04 7.04 0 0 0 4 0 7 7 0 0 0 2 .29 7 7 0 0 0 0-14m-13 7a5 5 0 0 1 4.01-4.9 6.98 6.98 0 0 0 0 9.8A5 5 0 0 1 12 21m8.01 4.9a4.999 4.999 0 0 1 0-9.8 6.98 6.98 0 0 0 0 9.8M23 16.41a5.011 5.011 0 0 1 0 9.18 5.011 5.011 0 0 1 0-9.18m2.99 9.49a6.98 6.98 0 0 0 0-9.8 4.999 4.999 0 0 1 0 9.8'/></svg>",
"folder-animation": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f8bbd0' d='M25 14a7 7 0 0 0-2 .29 7.04 7.04 0 0 0-4 0 7 7 0 0 0-2-.29 7 7 0 0 0 0 14 7 7 0 0 0 2-.29 7.04 7.04 0 0 0 4 0 7 7 0 0 0 2 .29 7 7 0 0 0 0-14m-13 7a5 5 0 0 1 4.01-4.9 6.98 6.98 0 0 0 0 9.8A5 5 0 0 1 12 21m8.01 4.9a4.999 4.999 0 0 1 0-9.8 6.98 6.98 0 0 0 0 9.8M23 16.41a5.011 5.011 0 0 1 0 9.18 5.011 5.011 0 0 1 0-9.18m2.99 9.49a6.98 6.98 0 0 0 0-9.8 4.999 4.999 0 0 1 0 9.8'/></svg>",
- "folder-ansible-open": "<svg viewBox='0 0 32 32'><path fill='#616161' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#9e9e9e' d='M32 21a9 9 0 1 1-9-9 9.043 9.043 0 0 1 9 9'/><path fill='#FAFAFA' d='m27.929 24.628-4-10a1 1 0 0 0-.93-.628h-.006a1 1 0 0 0-.927.641L18 26h2l1.24-3.638 5.205 3.47a1 1 0 0 0 1.484-1.204m-5.954-4.18 1.043-2.71 1.858 4.644Z'/></svg>",
- "folder-ansible": "<svg viewBox='0 0 32 32'><path fill='#616161' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#9e9e9e' d='M32 21a9 9 0 1 1-9-9 9.043 9.043 0 0 1 9 9'/><path fill='#FAFAFA' d='m27.929 24.628-4-10a1 1 0 0 0-.93-.628h-.006a1 1 0 0 0-.927.641L18 26h2l1.24-3.638 5.205 3.47a1 1 0 0 0 1.484-1.204m-5.954-4.18 1.043-2.71 1.858 4.644Z'/></svg>",
+ "folder-ansible-open": "<svg viewBox='0 0 32 32'><path fill='#616161' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#9e9e9e' d='M32 21a9 9 0 1 1-9-9 9.043 9.043 0 0 1 9 9'/><path fill='#fafafa' d='m27.929 24.628-4-10a1 1 0 0 0-.93-.628h-.006a1 1 0 0 0-.927.641L18 26h2l1.24-3.638 5.205 3.47a1 1 0 0 0 1.484-1.204m-5.954-4.18 1.043-2.71 1.858 4.644Z'/></svg>",
+ "folder-ansible": "<svg viewBox='0 0 32 32'><path fill='#616161' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#9e9e9e' d='M32 21a9 9 0 1 1-9-9 9.043 9.043 0 0 1 9 9'/><path fill='#fafafa' d='m27.929 24.628-4-10a1 1 0 0 0-.93-.628h-.006a1 1 0 0 0-.927.641L18 26h2l1.24-3.638 5.205 3.47a1 1 0 0 0 1.484-1.204m-5.954-4.18 1.043-2.71 1.858 4.644Z'/></svg>",
"folder-api-open": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fffde7' d='M20 18h-4v2h6v-6h-2zm8 0v-4h-2v6h6v-2zm-12 8h4v4h2v-6h-6zm10 0v4h2v-4h4v-2h-6z'/></svg>",
"folder-api": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fffde7' d='M20 18h-4v2h6v-6h-2zm8 0v-4h-2v6h6v-2zm-12 8h4v4h2v-6h-6zm10 0v4h2v-4h4v-2h-6z'/></svg>",
"folder-apollo-open": "<svg viewBox='0 0 32 32'><path fill='#7e57c2' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#d1c4e9' d='M25 28h3l-4-12h-4l-4 12h3l.667-2h3.01L22 24h-1.667L22 19z'/><path fill='#d1c4e9' d='M28 12a2 2 0 0 0-.416.045 10.996 10.996 0 0 0-17.102 13.473 1.003 1.003 0 0 0 1.72-1.034A8.986 8.986 0 0 1 26.1 13.406 2 2 0 0 0 26 14a2 2 0 1 0 2-2'/></svg>",
@@ -203,8 +206,8 @@
"folder-attachment": "<svg viewBox='0 0 1024 1024'><path fill='#9c27b0' d='m443.008 241.152-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848'/><path fill='#e1bee7' d='M960 416.219V544c0 53.02-42.98 96-96 96H544c-17.673 0-32-14.327-32-32s14.327-32 32-32h320v-64H544c-53.02 0-96 42.98-96 96s42.98 96 96 96h320c88.366 0 160-71.634 160-160 0-52.295-25.203-98.585-64-127.781'/></svg>",
"folder-audio-open": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M31.5 12h-5a.5.5 0 0 0-.5.5v8.055a3.9 3.9 0 0 0-3.232-.357 3.999 3.999 0 0 0 1.856 7.755A4.1 4.1 0 0 0 28 23.847V16h3.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5'/></svg>",
"folder-audio": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M31.5 12h-5a.5.5 0 0 0-.5.5v8.055a3.9 3.9 0 0 0-3.232-.357 3.999 3.999 0 0 0 1.856 7.755A4.1 4.1 0 0 0 28 23.847V16h3.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5'/></svg>",
- "folder-aurelia-open": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 32 32'><defs><linearGradient id='a' x1='1997.25' x2='2067.763' y1='2029.643' y2='2094.383' gradientTransform='matrix(.3031 0 0 .33012 -592.7 -666.868)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#7E57C2'/></linearGradient><linearGradient id='b' x1='2037.862' x2='1999.816' y1='2094.543' y2='2042.593' gradientTransform='matrix(.30439 0 0 .32873 -592.019 -659.828)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7E57C2'/><stop offset='.14' stop-color='#7B1FA2'/><stop offset='.29' stop-color='#AD1457'/><stop offset='.84' stop-color='#C2185B'/><stop offset='1' stop-color='#EC407A'/></linearGradient><linearGradient xlink:href='#a' id='c' x1='1810.238' x2='1876.912' y1='2182.482' y2='2275.279' gradientTransform='matrix(.3299 0 0 .30331 -593.502 -657.724)'/><linearGradient xlink:href='#a' id='d' x1='1884.666' x2='1966.686' y1='2101.188' y2='2168.469' gradientTransform='matrix(.31601 0 0 .31665 -588.323 -661.081)'/><linearGradient xlink:href='#a' id='e' x1='1908.618' x2='1985.086' y1='2130.411' y2='2197.794' gradientTransform='matrix(.31642 0 0 .31622 -597.877 -663.436)'/><linearGradient xlink:href='#b' id='f' x1='2058.454' x2='2020.313' y1='2123.223' y2='2071.047' gradientTransform='matrix(.30448 0 0 .32864 -597.026 -667.345)'/><linearGradient xlink:href='#a' id='g' x1='1999.965' x2='2070.546' y1='2025.692' y2='2103.839' gradientTransform='matrix(.30312 0 0 .33012 -592.673 -666.844)'/><linearGradient id='h' x1='2320.079' x2='2361.512' y1='1768.801' y2='1727.83' gradientTransform='matrix(.31084 .06202 -.06177 .30959 -596.669 -665.256)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7E57C2'/><stop offset='.14' stop-color='#7B1FA2'/><stop offset='.53' stop-color='#AD1457'/><stop offset='.79' stop-color='#C2185B'/><stop offset='1' stop-color='#EC407A'/></linearGradient></defs><path fill='#f06292' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='url(#a)' d='m25.787 15.333-1.671 1.144-1.72-2.646 1.67-1.144Z'/><path fill='url(#b)' d='M27.145 23.283 30 27.672 26.6 30l-2.855-4.389-.498-.765 3.4-2.328Z'/><path fill='url(#c)' d='m22.864 26.215.628.966-2.588 1.772-1.127-1.732.566-.387 2.023-1.385Z'/><path fill='url(#d)' d='m28.56 21.208.727-.497 1.126 1.732-1.671 1.144-.628-.967.945-.647Zm-.447 1.412-.497-.765.945-.647.498.765Z'/><path fill='url(#e)' d='m16.844 21.457-.565.387-1.72-2.647 2.587-1.772 1.206 1.855-2.022 1.385 2.023-1.385.515.791Z'/><path fill='url(#f)' d='m22.635 16.348.515.79-3.4 2.329-.515-.791-2.828-4.348 3.4-2.328Z'/><path fill='url(#g)' d='m25.062 15.83-.945.647-.515-.792-1.206-1.855 1.67-1.144 1.722 2.646Z'/><path fill='#673AB7' d='m20.84 27.6-.497-.765 2.023-1.386.497.766Zm7.273-4.98-.498-.764.945-.647.498.765Z'/><path fill='#AB47BC' d='m16.844 21.457-.515-.791 2.023-1.385.515.79Z'/><path fill='#7E57C2' d='m24.117 16.477-.514-.791.945-.648.514.791Z'/><path fill='#880E4F' d='m27.145 23.283-3.4 2.328-.497-.766 3.399-2.328Z'/><path fill='#AD1457' d='m22.635 16.347.515.792-3.4 2.329-.515-.793Z'/><path fill='#AB47BC' d='m15.88 15.715.642.986-.962.659-.642-.987Z'/><path fill='#7E57C2' d='m19.346 27.545.642.987-.962.659-.641-.988Z'/><path fill='url(#h)' d='M16.815 28.539 14 24.175l15.048-10.328L32 18.142Z'/></svg>",
- "folder-aurelia": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 32 32'><defs><linearGradient id='a' x1='1900.681' x2='1971.195' y1='2029.643' y2='2094.383' gradientTransform='matrix(.3031 0 0 .33012 -563.43 -666.868)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#7E57C2'/></linearGradient><linearGradient id='b' x1='1941.881' x2='1903.835' y1='2094.543' y2='2042.593' gradientTransform='matrix(.30439 0 0 .32873 -562.803 -659.828)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7E57C2'/><stop offset='.14' stop-color='#7B1FA2'/><stop offset='.29' stop-color='#AD1457'/><stop offset='.84' stop-color='#C2185B'/><stop offset='1' stop-color='#EC407A'/></linearGradient><linearGradient xlink:href='#a' id='c' x1='1724.927' x2='1791.601' y1='2182.482' y2='2275.279' gradientTransform='matrix(.3299 0 0 .30331 -565.358 -657.724)'/><linearGradient xlink:href='#a' id='d' x1='1793.759' x2='1875.779' y1='2101.188' y2='2168.469' gradientTransform='matrix(.31601 0 0 .31665 -559.595 -661.081)'/><linearGradient xlink:href='#a' id='e' x1='1817.883' x2='1894.351' y1='2130.411' y2='2197.794' gradientTransform='matrix(.31642 0 0 .31622 -569.167 -663.436)'/><linearGradient xlink:href='#b' id='f' x1='1962.514' x2='1924.373' y1='2123.223' y2='2071.047' gradientTransform='matrix(.30448 0 0 .32864 -567.814 -667.345)'/><linearGradient xlink:href='#a' id='g' x1='1903.406' x2='1973.987' y1='2025.692' y2='2103.839' gradientTransform='matrix(.30312 0 0 .33012 -563.404 -666.844)'/><linearGradient id='h' x1='2232.134' x2='2273.567' y1='1794.832' y2='1753.862' gradientTransform='matrix(.31084 .06202 -.06177 .30959 -567.724 -667.861)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7E57C2'/><stop offset='.14' stop-color='#7B1FA2'/><stop offset='.53' stop-color='#AD1457'/><stop offset='.79' stop-color='#C2185B'/><stop offset='1' stop-color='#EC407A'/></linearGradient></defs><path fill='#f06292' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='url(#a)' d='m25.787 15.333-1.671 1.144-1.72-2.646 1.67-1.144Z'/><path fill='url(#b)' d='M27.145 23.283 30 27.672 26.6 30l-2.855-4.389-.498-.765 3.4-2.328Z'/><path fill='url(#c)' d='m22.864 26.215.628.966-2.588 1.772-1.127-1.732.566-.387 2.023-1.385Z'/><path fill='url(#d)' d='m28.56 21.208.727-.497 1.126 1.732-1.671 1.144-.628-.967.945-.647Zm-.447 1.412-.497-.765.945-.647.498.765Z'/><path fill='url(#e)' d='m16.844 21.457-.565.387-1.72-2.647 2.587-1.772 1.206 1.855-2.022 1.385 2.023-1.385.515.791Z'/><path fill='url(#f)' d='m22.635 16.348.515.79-3.4 2.329-.515-.791-2.828-4.348 3.4-2.328Z'/><path fill='url(#g)' d='m25.062 15.83-.945.647-.515-.792-1.206-1.855 1.67-1.144 1.722 2.646Z'/><path fill='#673AB7' d='m20.84 27.6-.497-.765 2.023-1.386.497.766Zm7.273-4.98-.498-.764.945-.647.498.765Z'/><path fill='#AB47BC' d='m16.844 21.457-.515-.791 2.023-1.385.515.79Z'/><path fill='#7E57C2' d='m24.117 16.477-.514-.791.945-.648.514.791Z'/><path fill='#880E4F' d='m27.145 23.283-3.4 2.328-.497-.766 3.399-2.328Z'/><path fill='#AD1457' d='m22.635 16.347.515.792-3.4 2.329-.515-.793Z'/><path fill='#AB47BC' d='m15.88 15.715.642.986-.962.659-.642-.987Z'/><path fill='#7E57C2' d='m19.346 27.545.642.987-.962.659-.641-.988Z'/><path fill='url(#h)' d='M16.815 28.539 14 24.175l15.048-10.328L32 18.142Z'/></svg>",
+ "folder-aurelia-open": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 32 32'><defs><linearGradient id='a' x1='1997.25' x2='2067.763' y1='2029.643' y2='2094.383' gradientTransform='matrix(.3031 0 0 .33012 -592.7 -666.868)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#7e57c2'/></linearGradient><linearGradient id='b' x1='2037.862' x2='1999.816' y1='2094.543' y2='2042.593' gradientTransform='matrix(.30439 0 0 .32873 -592.019 -659.828)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7e57c2'/><stop offset='.14' stop-color='#7b1fa2'/><stop offset='.29' stop-color='#ad1457'/><stop offset='.84' stop-color='#c2185b'/><stop offset='1' stop-color='#ec407a'/></linearGradient><linearGradient xlink:href='#a' id='c' x1='1810.238' x2='1876.912' y1='2182.482' y2='2275.279' gradientTransform='matrix(.3299 0 0 .30331 -593.502 -657.724)'/><linearGradient xlink:href='#a' id='d' x1='1884.666' x2='1966.686' y1='2101.188' y2='2168.469' gradientTransform='matrix(.31601 0 0 .31665 -588.323 -661.081)'/><linearGradient xlink:href='#a' id='e' x1='1908.618' x2='1985.086' y1='2130.411' y2='2197.794' gradientTransform='matrix(.31642 0 0 .31622 -597.877 -663.436)'/><linearGradient xlink:href='#b' id='f' x1='2058.454' x2='2020.313' y1='2123.223' y2='2071.047' gradientTransform='matrix(.30448 0 0 .32864 -597.026 -667.345)'/><linearGradient xlink:href='#a' id='g' x1='1999.965' x2='2070.546' y1='2025.692' y2='2103.839' gradientTransform='matrix(.30312 0 0 .33012 -592.673 -666.844)'/><linearGradient id='h' x1='2320.079' x2='2361.512' y1='1768.801' y2='1727.83' gradientTransform='rotate(11.284 3068.743 -3352.564)scale(.31697 .3157)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7e57c2'/><stop offset='.14' stop-color='#7b1fa2'/><stop offset='.53' stop-color='#ad1457'/><stop offset='.79' stop-color='#c2185b'/><stop offset='1' stop-color='#ec407a'/></linearGradient></defs><path fill='#f06292' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='url(#a)' d='m25.787 15.333-1.671 1.144-1.72-2.646 1.67-1.144Z'/><path fill='url(#b)' d='M27.145 23.283 30 27.672 26.6 30l-2.855-4.389-.498-.765 3.4-2.328Z'/><path fill='url(#c)' d='m22.864 26.215.628.966-2.588 1.772-1.127-1.732.566-.387 2.023-1.385Z'/><path fill='url(#d)' d='m28.56 21.208.727-.497 1.126 1.732-1.671 1.144-.628-.967.945-.647Zm-.447 1.412-.497-.765.945-.647.498.765Z'/><path fill='url(#e)' d='m16.844 21.457-.565.387-1.72-2.647 2.587-1.772 1.206 1.855-2.022 1.385 2.023-1.385.515.791Z'/><path fill='url(#f)' d='m22.635 16.348.515.79-3.4 2.329-.515-.791-2.828-4.348 3.4-2.328Z'/><path fill='url(#g)' d='m25.062 15.83-.945.647-.515-.792-1.206-1.855 1.67-1.144 1.722 2.646Z'/><path fill='#673ab7' d='m20.84 27.6-.497-.765 2.023-1.386.497.766Zm7.273-4.98-.498-.764.945-.647.498.765Z'/><path fill='#ab47bc' d='m16.844 21.457-.515-.791 2.023-1.385.515.79Z'/><path fill='#7e57c2' d='m24.117 16.477-.514-.791.945-.648.514.791Z'/><path fill='#880e4f' d='m27.145 23.283-3.4 2.328-.497-.766 3.399-2.328Z'/><path fill='#ad1457' d='m22.635 16.347.515.792-3.4 2.329-.515-.793Z'/><path fill='#ab47bc' d='m15.88 15.715.642.986-.962.659-.642-.987Z'/><path fill='#7e57c2' d='m19.346 27.545.642.987-.962.659-.641-.988Z'/><path fill='url(#h)' d='M16.815 28.539 14 24.175l15.048-10.328L32 18.142Z'/></svg>",
+ "folder-aurelia": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 32 32'><defs><linearGradient id='a' x1='1900.681' x2='1971.195' y1='2029.643' y2='2094.383' gradientTransform='matrix(.3031 0 0 .33012 -563.43 -666.868)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#7e57c2'/></linearGradient><linearGradient id='b' x1='1941.881' x2='1903.835' y1='2094.543' y2='2042.593' gradientTransform='matrix(.30439 0 0 .32873 -562.803 -659.828)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7e57c2'/><stop offset='.14' stop-color='#7b1fa2'/><stop offset='.29' stop-color='#ad1457'/><stop offset='.84' stop-color='#c2185b'/><stop offset='1' stop-color='#ec407a'/></linearGradient><linearGradient xlink:href='#a' id='c' x1='1724.927' x2='1791.601' y1='2182.482' y2='2275.279' gradientTransform='matrix(.3299 0 0 .30331 -565.358 -657.724)'/><linearGradient xlink:href='#a' id='d' x1='1793.759' x2='1875.779' y1='2101.188' y2='2168.469' gradientTransform='matrix(.31601 0 0 .31665 -559.595 -661.081)'/><linearGradient xlink:href='#a' id='e' x1='1817.883' x2='1894.351' y1='2130.411' y2='2197.794' gradientTransform='matrix(.31642 0 0 .31622 -569.167 -663.436)'/><linearGradient xlink:href='#b' id='f' x1='1962.514' x2='1924.373' y1='2123.223' y2='2071.047' gradientTransform='matrix(.30448 0 0 .32864 -567.814 -667.345)'/><linearGradient xlink:href='#a' id='g' x1='1903.406' x2='1973.987' y1='2025.692' y2='2103.839' gradientTransform='matrix(.30312 0 0 .33012 -563.404 -666.844)'/><linearGradient id='h' x1='2232.134' x2='2273.567' y1='1794.832' y2='1753.862' gradientTransform='rotate(11.284 3096.4 -3207.367)scale(.31697 .3157)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7e57c2'/><stop offset='.14' stop-color='#7b1fa2'/><stop offset='.53' stop-color='#ad1457'/><stop offset='.79' stop-color='#c2185b'/><stop offset='1' stop-color='#ec407a'/></linearGradient></defs><path fill='#f06292' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='url(#a)' d='m25.787 15.333-1.671 1.144-1.72-2.646 1.67-1.144Z'/><path fill='url(#b)' d='M27.145 23.283 30 27.672 26.6 30l-2.855-4.389-.498-.765 3.4-2.328Z'/><path fill='url(#c)' d='m22.864 26.215.628.966-2.588 1.772-1.127-1.732.566-.387 2.023-1.385Z'/><path fill='url(#d)' d='m28.56 21.208.727-.497 1.126 1.732-1.671 1.144-.628-.967.945-.647Zm-.447 1.412-.497-.765.945-.647.498.765Z'/><path fill='url(#e)' d='m16.844 21.457-.565.387-1.72-2.647 2.587-1.772 1.206 1.855-2.022 1.385 2.023-1.385.515.791Z'/><path fill='url(#f)' d='m22.635 16.348.515.79-3.4 2.329-.515-.791-2.828-4.348 3.4-2.328Z'/><path fill='url(#g)' d='m25.062 15.83-.945.647-.515-.792-1.206-1.855 1.67-1.144 1.722 2.646Z'/><path fill='#673ab7' d='m20.84 27.6-.497-.765 2.023-1.386.497.766Zm7.273-4.98-.498-.764.945-.647.498.765Z'/><path fill='#ab47bc' d='m16.844 21.457-.515-.791 2.023-1.385.515.79Z'/><path fill='#7e57c2' d='m24.117 16.477-.514-.791.945-.648.514.791Z'/><path fill='#880e4f' d='m27.145 23.283-3.4 2.328-.497-.766 3.399-2.328Z'/><path fill='#ad1457' d='m22.635 16.347.515.792-3.4 2.329-.515-.793Z'/><path fill='#ab47bc' d='m15.88 15.715.642.986-.962.659-.642-.987Z'/><path fill='#7e57c2' d='m19.346 27.545.642.987-.962.659-.641-.988Z'/><path fill='url(#h)' d='M16.815 28.539 14 24.175l15.048-10.328L32 18.142Z'/></svg>",
"folder-aws-open": "<svg viewBox='0 0 32 32'><path fill='#ffb300' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffecb3' d='M27.881 19.23a6.591 6.591 0 0 0-12.308-1.76 5.278 5.278 0 0 0 .572 10.525h11.428a4.388 4.388 0 0 0 .308-8.766Z'/></svg>",
"folder-aws": "<svg viewBox='0 0 32 32'><path fill='#ffb300' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffecb3' d='M27.881 19.23a6.591 6.591 0 0 0-12.308-1.76 5.278 5.278 0 0 0 .572 10.525h11.428a4.388 4.388 0 0 0 .308-8.766Z'/></svg>",
"folder-azure-pipelines-open": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#bbdefb' d='m28 22 3.724-1.862a.5.5 0 0 0 .276-.447V12.5a.5.5 0 0 0-.5-.5h-7.191a.5.5 0 0 0-.447.276L22 16h-5.5a.5.5 0 0 0-.5.5V20l1.172 1.172 1.414-1.415L20 21.172l-1.414 1.414 2.828 2.828L22.828 24l1.415 1.414-1.415 1.414L24 28h3.5a.5.5 0 0 0 .5-.5Zm0-4a2 2 0 1 1 2-2 2 2 0 0 1-2 2M16 28v-4h-2v6h6v-2z'/></svg>",
@@ -221,10 +224,12 @@
"folder-bibliography": "<svg viewBox='0 0 1024 1024'><path fill='#a1887f' d='m443.008 241.152-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848'/><path fill='#8d6e63' d='M576 704v128c0 35.456 28.544 64 64 64h352c17.728 0 32-14.272 32-32V704z'/><path fill='#ffe082' d='M896 768v64h64v-64z'/><path fill='#fff8e1' d='M640 768c0 35.456 28.544 64 64 64h256c-35.346 0-64-28.654-64-64z'/><path fill='#ff1744' d='M704 800h64v160h-64z'/><path fill='#d7ccc8' d='M640 320c-35.456 0-64 28.544-64 64v448c0-35.346 28.654-64 64-64h352c17.728 0 32-14.272 32-32V352c0-17.728-14.272-32-32-32z'/><path fill='#5d4037' d='M640 384v32h32c32 0 32 7.163 32 16v224c0 8.837 0 16-32 16h-32v32h192c70.692 0 128-39.399 128-88-.014-40.302-39.848-75.446-96.688-85.305C884.346 514.563 895.986 493.665 896 472c0-48.601-57.308-88-128-88zm143.873 33.736C811.541 423.914 832 445.866 832 472c0 30.928-28.654 56-64 56v-96c0-8.837 7.266-16.186 15.873-14.264M768 560h64c35.346 0 64 25.072 64 56s-28.654 56-64 56h-48a16 16 0 0 1-16-16z'/></svg>",
"folder-bicep-open": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fff9c4' d='M15 25s1.15-11.115 4-15l4 1-1 3h-2v7h2c1.9-2.915 5.381-4.255 7.755-3.19 3.134 1.453 2.85 5.831 0 7.769C27.475 27.137 20.699 28.885 15 25'/></svg>",
"folder-bicep": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fff9c4' d='M15 25s1.15-11.115 4-15l4 1-1 3h-2v7h2c1.9-2.915 5.381-4.255 7.755-3.19 3.134 1.453 2.85 5.831 0 7.769C27.475 27.137 20.699 28.885 15 25'/></svg>",
- "folder-bloc-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#26A69A' d='M29 12H9.4c-.9 0-1.6.6-1.9 1.4L4 24V10h24c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.2-1.3-.5l-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h22l4.8-11.2c.4-1 0-2.2-1-2.6-.3-.1-.6-.2-.8-.2'/><path fill='#B2DFDB' d='m25 12 7 4.198v8.103L25 28.5l-7-4.212V16.21z'/></svg>",
- "folder-bloc": "<svg fill='none' viewBox='0 0 32 32'><path fill='#26A69A' d='m13.8 7.5-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h24c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.1-1.3-.5'/><path fill='#B2DFDB' d='m25 12 7 4.198v8.103L25 28.5l-7-4.212V16.21z'/></svg>",
- "folder-bower-open": "<svg viewBox='0 0 32 32'><path fill='#8d6e63' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#5d4037' d='M31.598 20.789c-1.028-1.012-6.166-1.644-7.786-1.827a5 5 0 0 0 .2-.589 6 6 0 0 1 .707-.269c.03.092.171.439.251.605a3.165 3.165 0 0 0 3.533-2.78 3 3 0 0 0 .027-.407 4 4 0 0 1 1.241-2.574c-1.666-.498-4.062.77-4.864 2.657a5 5 0 0 0-.902-.253A4.396 4.396 0 0 0 19.75 12a8.07 8.07 0 0 0-7.746 8.364l.002.07c0 4.46 2.97 8.365 4.647 8.365a1.65 1.65 0 0 0 1.511-1.067c.124.346.505 1.42.63 1.694.184.404 1.04.755 1.414.335a1.355 1.355 0 0 0 1.843-.292 1.36 1.36 0 0 0 1.724-.89 1 1 0 0 0 .039-.149c.454-.026.678-.68.578-1.2a13 13 0 0 0-1.159-2.234c.604.503 2.132.645 2.317 0 .973.782 2.488.372 2.61-.264 1.18.314 2.536-.376 2.314-1.213a1.63 1.63 0 0 0 1.525-1.719 1.66 1.66 0 0 0-.401-1.011'/><path fill='#03a9f4' d='M26.252 16.374a7.4 7.4 0 0 1 1.57-2.302 4.14 4.14 0 0 0-1.836 2.12 6 6 0 0 0-.646-.37 4.2 4.2 0 0 1 3.424-2.419c-.999.928-.644 2.855-1.464 3.876a11 11 0 0 0-1.048-.906Zm-.646 1.354a4 4 0 0 1 .035-.39 3.4 3.4 0 0 0-.6-.082 2.4 2.4 0 0 0 .208.889 3.05 3.05 0 0 0 1.629-.462 5.5 5.5 0 0 0-1.099-.318c-.04.085-.137.3-.173.362Z'/><path fill='#4caf50' d='M22.847 24.304v.003a11 11 0 0 1-.32-.805c.475.708 1.962.343 1.884-.292.728.562 2.226-.093 1.885-.88.73.348 1.561-.352 1.375-.657 1.243.245 2.434.49 2.808.588a1.43 1.43 0 0 1-1.667.505c.46.643-.434 1.414-1.68.99.274.63-.835 1.2-2.096.541.016.632-1.565.705-2.19.007Zm2.465-3.193c1.443.113 3.828.334 5.305.545-.093-.492-.348-.633-1.15-.854-.862.095-3.05.315-4.155.309'/><path fill='#ffca28' d='M24.41 23.21c.729.562 2.227-.093 1.886-.88.73.348 1.561-.352 1.375-.657-1.47-.29-3.012-.582-3.361-.633a53 53 0 0 1 1.002.07c1.106.007 3.293-.213 4.156-.307a41 41 0 0 0-6.216-1.023 3 3 0 0 1-.55.615 4.82 4.82 0 0 1-4.15 3.108 5.5 5.5 0 0 1-1.697-.293 3.54 3.54 0 0 1-3.432.074 6.94 6.94 0 0 0 6.356 4.321c2.335 0 3.37-2.443 3.143-3.09-.055-.156-.272-.677-.394-1.013.474.708 1.961.343 1.883-.291Z'/><path fill='#e0e0e0' d='M22.996 18.294a6.7 6.7 0 0 1 1.597-.724q-.016-.116-.023-.233c-.446.11-1.285.478-1.766-.03 1.015.314 1.521-.28 2.268-.28a4.7 4.7 0 0 1 1.579.329 5.27 5.27 0 0 0-3.355-1.534 2.45 2.45 0 0 0-.3 2.472'/><path fill='#f4511e' d='M16.855 23.21a5.5 5.5 0 0 0 1.698.293 4.82 4.82 0 0 0 4.148-3.109 5.25 5.25 0 0 1-3.473 1.011 5.42 5.42 0 0 0 3.54-2.294 3.14 3.14 0 0 1 .314-3.836 3.52 3.52 0 0 0-3.333-2.4 7.174 7.174 0 0 0-6.893 7.431l.004.127a7.4 7.4 0 0 0 .563 2.85 3.54 3.54 0 0 0 3.432-.074Z'/><path fill='#ffca28' d='M17.985 16.619a1.74 1.74 0 1 0 1.74-1.783 1.76 1.76 0 0 0-1.74 1.783'/><path fill='#5d4037' d='M18.683 16.619a1.042 1.042 0 1 0 1.042-1.068 1.055 1.055 0 0 0-1.042 1.068'/><ellipse cx='19.725' cy='16.145' fill='#FAFAFA' rx='.607' ry='.387'/></svg>",
- "folder-bower": "<svg viewBox='0 0 32 32'><path fill='#8d6e63' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#5d4037' d='M31.598 20.789c-1.028-1.012-6.166-1.644-7.786-1.827a5 5 0 0 0 .2-.589 6 6 0 0 1 .707-.269c.03.092.171.439.251.605a3.165 3.165 0 0 0 3.533-2.78 3 3 0 0 0 .027-.407 4 4 0 0 1 1.241-2.574c-1.666-.498-4.062.77-4.864 2.657a5 5 0 0 0-.902-.253A4.396 4.396 0 0 0 19.75 12a8.07 8.07 0 0 0-7.746 8.364l.002.07c0 4.46 2.97 8.365 4.647 8.365a1.65 1.65 0 0 0 1.511-1.067c.124.346.505 1.42.63 1.694.184.404 1.04.755 1.414.335a1.355 1.355 0 0 0 1.843-.292 1.36 1.36 0 0 0 1.724-.89 1 1 0 0 0 .039-.149c.454-.026.678-.68.578-1.2a13 13 0 0 0-1.159-2.234c.604.503 2.132.645 2.317 0 .973.782 2.488.372 2.61-.264 1.18.314 2.536-.376 2.314-1.213a1.63 1.63 0 0 0 1.525-1.719 1.66 1.66 0 0 0-.401-1.011'/><path fill='#03a9f4' d='M26.252 16.374a7.4 7.4 0 0 1 1.57-2.302 4.14 4.14 0 0 0-1.836 2.12 6 6 0 0 0-.646-.37 4.2 4.2 0 0 1 3.424-2.419c-.999.928-.644 2.855-1.464 3.876a11 11 0 0 0-1.048-.906Zm-.646 1.354a4 4 0 0 1 .035-.39 3.4 3.4 0 0 0-.6-.082 2.4 2.4 0 0 0 .208.889 3.05 3.05 0 0 0 1.629-.462 5.5 5.5 0 0 0-1.099-.318c-.04.085-.137.3-.173.362Z'/><path fill='#4caf50' d='M22.847 24.304v.003a11 11 0 0 1-.32-.805c.475.708 1.962.343 1.884-.292.728.562 2.226-.093 1.885-.88.73.348 1.561-.352 1.375-.657 1.243.245 2.434.49 2.808.588a1.43 1.43 0 0 1-1.667.505c.46.643-.434 1.414-1.68.99.274.63-.835 1.2-2.096.541.016.632-1.565.705-2.19.007Zm2.465-3.193c1.443.113 3.828.334 5.305.545-.093-.492-.348-.633-1.15-.854-.862.095-3.05.315-4.155.309'/><path fill='#ffca28' d='M24.41 23.21c.729.562 2.227-.093 1.886-.88.73.348 1.561-.352 1.375-.657-1.47-.29-3.012-.582-3.361-.633a53 53 0 0 1 1.002.07c1.106.007 3.293-.213 4.156-.307a41 41 0 0 0-6.216-1.023 3 3 0 0 1-.55.615 4.82 4.82 0 0 1-4.15 3.108 5.5 5.5 0 0 1-1.697-.293 3.54 3.54 0 0 1-3.432.074 6.94 6.94 0 0 0 6.356 4.321c2.335 0 3.37-2.443 3.143-3.09-.055-.156-.272-.677-.394-1.013.474.708 1.961.343 1.883-.291Z'/><path fill='#e0e0e0' d='M22.996 18.294a6.7 6.7 0 0 1 1.597-.724q-.016-.116-.023-.233c-.446.11-1.285.478-1.766-.03 1.015.314 1.521-.28 2.268-.28a4.7 4.7 0 0 1 1.579.329 5.27 5.27 0 0 0-3.355-1.534 2.45 2.45 0 0 0-.3 2.472'/><path fill='#f4511e' d='M16.855 23.21a5.5 5.5 0 0 0 1.698.293 4.82 4.82 0 0 0 4.148-3.109 5.25 5.25 0 0 1-3.473 1.011 5.42 5.42 0 0 0 3.54-2.294 3.14 3.14 0 0 1 .314-3.836 3.52 3.52 0 0 0-3.333-2.4 7.174 7.174 0 0 0-6.893 7.431l.004.127a7.4 7.4 0 0 0 .563 2.85 3.54 3.54 0 0 0 3.432-.074Z'/><path fill='#ffca28' d='M17.985 16.619a1.74 1.74 0 1 0 1.74-1.783 1.76 1.76 0 0 0-1.74 1.783'/><path fill='#5d4037' d='M18.683 16.619a1.042 1.042 0 1 0 1.042-1.068 1.055 1.055 0 0 0-1.042 1.068'/><ellipse cx='19.725' cy='16.145' fill='#FAFAFA' rx='.607' ry='.387'/></svg>",
+ "folder-blender-open": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><g fill='#ffe0b2' fill-rule='evenodd'><path d='M19.716 20.926a3.215 3.215 0 0 1 3.21-3.216 3.215 3.215 0 0 1 3.22 3.206 3.215 3.215 0 0 1-3.2 3.224 3.215 3.215 0 0 1-3.23-3.195z' paint-order='stroke fill markers'/><path d='m25.595 13.309-2.8-2.862s-.957-.987-1.943-.03-.03 1.943-.03 1.943l.107.17c.445.444.907.88 1.353 1.324h-7.715s-1.286 0-1.286 1.285c0 1.286 1.286 1.286 1.286 1.286h2.572l-3.858 3.858-.857.907s-.967.966 0 1.933c.966.966 1.933 0 1.933 0l1.496-1.554.04.122q.039.357.112.701a7.08 7.08 0 0 0 5.528 5.47q.692.14 1.425.137a7.073 7.073 0 0 0 7.04-7.094c0-3.505-2.572-5.766-4.403-7.597zm-2.676 3.116a4.5 4.5 0 1 1-4.494 4.529v-.028a4.5 4.5 0 0 1 4.494-4.5'/></g></svg>",
+ "folder-blender": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g fill='#ffe0b2' fill-rule='evenodd'><path d='M19.716 20.926a3.215 3.215 0 0 1 3.21-3.216 3.215 3.215 0 0 1 3.22 3.206 3.215 3.215 0 0 1-3.2 3.224 3.215 3.215 0 0 1-3.23-3.195z' paint-order='stroke fill markers'/><path d='m25.595 13.309-2.8-2.862s-.957-.987-1.943-.03-.03 1.943-.03 1.943l.107.17c.445.444.907.88 1.353 1.324h-7.715s-1.286 0-1.286 1.285c0 1.286 1.286 1.286 1.286 1.286h2.572l-3.858 3.858-.857.907s-.967.966 0 1.933c.966.966 1.933 0 1.933 0l1.496-1.554.04.122q.039.357.112.701a7.08 7.08 0 0 0 5.528 5.47q.692.14 1.425.137a7.073 7.073 0 0 0 7.04-7.094c0-3.505-2.572-5.766-4.403-7.597zm-2.676 3.116a4.5 4.5 0 1 1-4.494 4.529v-.028a4.5 4.5 0 0 1 4.494-4.5'/></g></svg>",
+ "folder-bloc-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#26a69a' d='M29 12H9.4c-.9 0-1.6.6-1.9 1.4L4 24V10h24c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.2-1.3-.5l-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h22l4.8-11.2c.4-1 0-2.2-1-2.6-.3-.1-.6-.2-.8-.2'/><path fill='#b2dfdb' d='m25 12 7 4.198v8.103L25 28.5l-7-4.212V16.21z'/></svg>",
+ "folder-bloc": "<svg fill='none' viewBox='0 0 32 32'><path fill='#26a69a' d='m13.8 7.5-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h24c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.1-1.3-.5'/><path fill='#b2dfdb' d='m25 12 7 4.198v8.103L25 28.5l-7-4.212V16.21z'/></svg>",
+ "folder-bower-open": "<svg viewBox='0 0 32 32'><path fill='#8d6e63' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#5d4037' d='M31.598 20.789c-1.028-1.012-6.166-1.644-7.786-1.827a5 5 0 0 0 .2-.589 6 6 0 0 1 .707-.269c.03.092.171.439.251.605a3.165 3.165 0 0 0 3.533-2.78 3 3 0 0 0 .027-.407 4 4 0 0 1 1.241-2.574c-1.666-.498-4.062.77-4.864 2.657a5 5 0 0 0-.902-.253A4.396 4.396 0 0 0 19.75 12a8.07 8.07 0 0 0-7.746 8.364l.002.07c0 4.46 2.97 8.365 4.647 8.365a1.65 1.65 0 0 0 1.511-1.067c.124.346.505 1.42.63 1.694.184.404 1.04.755 1.414.335a1.355 1.355 0 0 0 1.843-.292 1.36 1.36 0 0 0 1.724-.89 1 1 0 0 0 .039-.149c.454-.026.678-.68.578-1.2a13 13 0 0 0-1.159-2.234c.604.503 2.132.645 2.317 0 .973.782 2.488.372 2.61-.264 1.18.314 2.536-.376 2.314-1.213a1.63 1.63 0 0 0 1.525-1.719 1.66 1.66 0 0 0-.401-1.011'/><path fill='#03a9f4' d='M26.252 16.374a7.4 7.4 0 0 1 1.57-2.302 4.14 4.14 0 0 0-1.836 2.12 6 6 0 0 0-.646-.37 4.2 4.2 0 0 1 3.424-2.419c-.999.928-.644 2.855-1.464 3.876a11 11 0 0 0-1.048-.906Zm-.646 1.354a4 4 0 0 1 .035-.39 3.4 3.4 0 0 0-.6-.082 2.4 2.4 0 0 0 .208.889 3.05 3.05 0 0 0 1.629-.462 5.5 5.5 0 0 0-1.099-.318c-.04.085-.137.3-.173.362Z'/><path fill='#4caf50' d='M22.847 24.304v.003a11 11 0 0 1-.32-.805c.475.708 1.962.343 1.884-.292.728.562 2.226-.093 1.885-.88.73.348 1.561-.352 1.375-.657 1.243.245 2.434.49 2.808.588a1.43 1.43 0 0 1-1.667.505c.46.643-.434 1.414-1.68.99.274.63-.835 1.2-2.096.541.016.632-1.565.705-2.19.007Zm2.465-3.193c1.443.113 3.828.334 5.305.545-.093-.492-.348-.633-1.15-.854-.862.095-3.05.315-4.155.309'/><path fill='#ffca28' d='M24.41 23.21c.729.562 2.227-.093 1.886-.88.73.348 1.561-.352 1.375-.657-1.47-.29-3.012-.582-3.361-.633a53 53 0 0 1 1.002.07c1.106.007 3.293-.213 4.156-.307a41 41 0 0 0-6.216-1.023 3 3 0 0 1-.55.615 4.82 4.82 0 0 1-4.15 3.108 5.5 5.5 0 0 1-1.697-.293 3.54 3.54 0 0 1-3.432.074 6.94 6.94 0 0 0 6.356 4.321c2.335 0 3.37-2.443 3.143-3.09-.055-.156-.272-.677-.394-1.013.474.708 1.961.343 1.883-.291Z'/><path fill='#e0e0e0' d='M22.996 18.294a6.7 6.7 0 0 1 1.597-.724q-.016-.116-.023-.233c-.446.11-1.285.478-1.766-.03 1.015.314 1.521-.28 2.268-.28a4.7 4.7 0 0 1 1.579.329 5.27 5.27 0 0 0-3.355-1.534 2.45 2.45 0 0 0-.3 2.472'/><path fill='#f4511e' d='M16.855 23.21a5.5 5.5 0 0 0 1.698.293 4.82 4.82 0 0 0 4.148-3.109 5.25 5.25 0 0 1-3.473 1.011 5.42 5.42 0 0 0 3.54-2.294 3.14 3.14 0 0 1 .314-3.836 3.52 3.52 0 0 0-3.333-2.4 7.174 7.174 0 0 0-6.893 7.431l.004.127a7.4 7.4 0 0 0 .563 2.85 3.54 3.54 0 0 0 3.432-.074Z'/><path fill='#ffca28' d='M17.985 16.619a1.74 1.74 0 1 0 1.74-1.783 1.76 1.76 0 0 0-1.74 1.783'/><path fill='#5d4037' d='M18.683 16.619a1.042 1.042 0 1 0 1.042-1.068 1.055 1.055 0 0 0-1.042 1.068'/><ellipse cx='19.725' cy='16.145' fill='#fafafa' rx='.607' ry='.387'/></svg>",
+ "folder-bower": "<svg viewBox='0 0 32 32'><path fill='#8d6e63' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#5d4037' d='M31.598 20.789c-1.028-1.012-6.166-1.644-7.786-1.827a5 5 0 0 0 .2-.589 6 6 0 0 1 .707-.269c.03.092.171.439.251.605a3.165 3.165 0 0 0 3.533-2.78 3 3 0 0 0 .027-.407 4 4 0 0 1 1.241-2.574c-1.666-.498-4.062.77-4.864 2.657a5 5 0 0 0-.902-.253A4.396 4.396 0 0 0 19.75 12a8.07 8.07 0 0 0-7.746 8.364l.002.07c0 4.46 2.97 8.365 4.647 8.365a1.65 1.65 0 0 0 1.511-1.067c.124.346.505 1.42.63 1.694.184.404 1.04.755 1.414.335a1.355 1.355 0 0 0 1.843-.292 1.36 1.36 0 0 0 1.724-.89 1 1 0 0 0 .039-.149c.454-.026.678-.68.578-1.2a13 13 0 0 0-1.159-2.234c.604.503 2.132.645 2.317 0 .973.782 2.488.372 2.61-.264 1.18.314 2.536-.376 2.314-1.213a1.63 1.63 0 0 0 1.525-1.719 1.66 1.66 0 0 0-.401-1.011'/><path fill='#03a9f4' d='M26.252 16.374a7.4 7.4 0 0 1 1.57-2.302 4.14 4.14 0 0 0-1.836 2.12 6 6 0 0 0-.646-.37 4.2 4.2 0 0 1 3.424-2.419c-.999.928-.644 2.855-1.464 3.876a11 11 0 0 0-1.048-.906Zm-.646 1.354a4 4 0 0 1 .035-.39 3.4 3.4 0 0 0-.6-.082 2.4 2.4 0 0 0 .208.889 3.05 3.05 0 0 0 1.629-.462 5.5 5.5 0 0 0-1.099-.318c-.04.085-.137.3-.173.362Z'/><path fill='#4caf50' d='M22.847 24.304v.003a11 11 0 0 1-.32-.805c.475.708 1.962.343 1.884-.292.728.562 2.226-.093 1.885-.88.73.348 1.561-.352 1.375-.657 1.243.245 2.434.49 2.808.588a1.43 1.43 0 0 1-1.667.505c.46.643-.434 1.414-1.68.99.274.63-.835 1.2-2.096.541.016.632-1.565.705-2.19.007Zm2.465-3.193c1.443.113 3.828.334 5.305.545-.093-.492-.348-.633-1.15-.854-.862.095-3.05.315-4.155.309'/><path fill='#ffca28' d='M24.41 23.21c.729.562 2.227-.093 1.886-.88.73.348 1.561-.352 1.375-.657-1.47-.29-3.012-.582-3.361-.633a53 53 0 0 1 1.002.07c1.106.007 3.293-.213 4.156-.307a41 41 0 0 0-6.216-1.023 3 3 0 0 1-.55.615 4.82 4.82 0 0 1-4.15 3.108 5.5 5.5 0 0 1-1.697-.293 3.54 3.54 0 0 1-3.432.074 6.94 6.94 0 0 0 6.356 4.321c2.335 0 3.37-2.443 3.143-3.09-.055-.156-.272-.677-.394-1.013.474.708 1.961.343 1.883-.291Z'/><path fill='#e0e0e0' d='M22.996 18.294a6.7 6.7 0 0 1 1.597-.724q-.016-.116-.023-.233c-.446.11-1.285.478-1.766-.03 1.015.314 1.521-.28 2.268-.28a4.7 4.7 0 0 1 1.579.329 5.27 5.27 0 0 0-3.355-1.534 2.45 2.45 0 0 0-.3 2.472'/><path fill='#f4511e' d='M16.855 23.21a5.5 5.5 0 0 0 1.698.293 4.82 4.82 0 0 0 4.148-3.109 5.25 5.25 0 0 1-3.473 1.011 5.42 5.42 0 0 0 3.54-2.294 3.14 3.14 0 0 1 .314-3.836 3.52 3.52 0 0 0-3.333-2.4 7.174 7.174 0 0 0-6.893 7.431l.004.127a7.4 7.4 0 0 0 .563 2.85 3.54 3.54 0 0 0 3.432-.074Z'/><path fill='#ffca28' d='M17.985 16.619a1.74 1.74 0 1 0 1.74-1.783 1.76 1.76 0 0 0-1.74 1.783'/><path fill='#5d4037' d='M18.683 16.619a1.042 1.042 0 1 0 1.042-1.068 1.055 1.055 0 0 0-1.042 1.068'/><ellipse cx='19.725' cy='16.145' fill='#fafafa' rx='.607' ry='.387'/></svg>",
"folder-buildkite-open": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#c8e6c9' d='m20 24-6-2v-6l6 2zm10-2h-4v-6l6 4z'/><path fill='#a5d6a7' d='m20 24 6-2v-6l-6 2zm6 4 6-2v-6l-6 2z'/></svg>",
"folder-buildkite": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#c8e6c9' d='m20 24-6-2v-6l6 2zm10-2h-4v-6l6 4z'/><path fill='#a5d6a7' d='m20 24 6-2v-6l-6 2zm6 4 6-2v-6l-6 2z'/></svg>",
"folder-cart-open": "<svg viewBox='0 0 32 32'><path fill='#009688' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><circle cx='20' cy='26' r='2' fill='#b2dfdb'/><circle cx='28' cy='26' r='2' fill='#b2dfdb'/><path fill='#b2dfdb' d='M30.613 12H18.22l-.4-2H14v2h2.18l1.84 9.196A1 1 0 0 0 19 22h11v-2H19.82l-.4-2H30l1.561-4.684A1 1 0 0 0 30.613 12'/></svg>",
@@ -243,8 +248,8 @@
"folder-cline": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M23 12a2 2 0 0 0-2 2h-1c-2.216 0-4 1.784-4 4l-2 3 2 3c0 2.216 1.784 4 4 4h6c2.216 0 4-1.784 4-4l2-3-2-3c0-2.216-1.784-4-4-4h-1a2 2 0 0 0-2-2m-2 6c.554 0 1 .446 1 1v4c0 .554-.446 1-1 1s-1-.446-1-1v-4c0-.554.446-1 1-1m4 0c.554 0 1 .446 1 1v4c0 .554-.446 1-1 1s-1-.446-1-1v-4c0-.554.446-1 1-1'/></svg>",
"folder-cloud-functions-open": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='2' clip-rule='evenodd' viewBox='0 0 32 32'><defs><path id='a' fill='#bbdefb' d='m26 14 2-2 4 4-2 2z'/></defs><path fill='#2196f3' fill-rule='nonzero' d='M28.967 12H9.442c-.859 0-1.627.553-1.898 1.368L4 24V10h24c0-1.097-.903-2-2-2H15.124c-.468 0-.921-.164-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4c-1.097 0-2 .903-2 2v16c0 1.097.903 2 2 2h22l4.805-11.212c.107-.249.162-.517.162-.788 0-1.097-.903-2-2-2'/><path fill='#bbdefb' d='M21.982 17.988h2.037v1.996h-2.037zm-2.983.021h2.037v1.996h-2.037zm5.998 0h1.996v1.996h-1.996zM29 14h3v10h-3z'/><use xlink:href='#a' transform='translate(0 -2)'/><use xlink:href='#a' transform='matrix(1 0 0 -1 0 40)'/><path fill='#bbdefb' d='M14 14h3v10h-3z'/><use xlink:href='#a' transform='matrix(-1 0 0 1 46 -2)'/><use xlink:href='#a' transform='rotate(180 23 20)'/></svg>",
"folder-cloud-functions": "<svg xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='2' clip-rule='evenodd' viewBox='0 0 32 32'><defs><path id='a' fill='#bbdefb' d='m26 14 2-2 4 4-2 2z'/></defs><path fill='#2196f3' fill-rule='nonzero' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4c-1.097 0-2 .903-2 2v16c0 1.097.903 2 2 2h24c1.097 0 2-.903 2-2V10c0-1.097-.903-2-2-2H15.124c-.468 0-.921-.164-1.28-.464'/><path fill='#bbdefb' d='M21.982 17.988h2.037v1.996h-2.037zm-2.983.021h2.037v1.996h-2.037zm5.998 0h1.996v1.996h-1.996zM29 14h3v10h-3z'/><use xlink:href='#a' transform='translate(0 -2)'/><use xlink:href='#a' transform='matrix(1 0 0 -1 0 40)'/><path fill='#bbdefb' d='M14 14h3v10h-3z'/><use xlink:href='#a' transform='matrix(-1 0 0 1 46 -2)'/><use xlink:href='#a' transform='rotate(180 23 20)'/></svg>",
- "folder-cloudflare-open": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#EFEBE9' d='M27.881 19.229a6.591 6.591 0 0 0-12.308-1.759 5.278 5.278 0 0 0 .572 10.524h11.428a4.388 4.388 0 0 0 .308-8.765'/></svg>",
- "folder-cloudflare": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#EFEBE9' d='M27.881 19.229a6.591 6.591 0 0 0-12.308-1.759 5.278 5.278 0 0 0 .572 10.524h11.428a4.388 4.388 0 0 0 .308-8.765'/></svg>",
+ "folder-cloudflare-open": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#efebe9' d='M27.881 19.229a6.591 6.591 0 0 0-12.308-1.759 5.278 5.278 0 0 0 .572 10.524h11.428a4.388 4.388 0 0 0 .308-8.765'/></svg>",
+ "folder-cloudflare": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#efebe9' d='M27.881 19.229a6.591 6.591 0 0 0-12.308-1.759 5.278 5.278 0 0 0 .572 10.524h11.428a4.388 4.388 0 0 0 .308-8.765'/></svg>",
"folder-cluster-open": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><circle cx='21' cy='15' r='3' fill='#b2dfdb'/><circle cx='17' cy='23' r='3' fill='#b2dfdb'/><circle cx='27' cy='27' r='3' fill='#b2dfdb'/></svg>",
"folder-cluster": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><circle cx='21' cy='15' r='3' fill='#b2dfdb'/><circle cx='17' cy='23' r='3' fill='#b2dfdb'/><circle cx='27' cy='27' r='3' fill='#b2dfdb'/></svg>",
"folder-cobol-open": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M23.448 10.106a10 10 0 0 0-2.896 0l-.634 1.653a8.5 8.5 0 0 0-2.273.942l-1.618-.72a10 10 0 0 0-2.047 2.046l.721 1.618a8.5 8.5 0 0 0-.942 2.273l-1.653.634a10 10 0 0 0 0 2.896l1.653.634a8.5 8.5 0 0 0 .942 2.273l-.72 1.618a10 10 0 0 0 2.046 2.047l1.618-.721a8.5 8.5 0 0 0 2.273.942l.634 1.653a10 10 0 0 0 2.896 0l.634-1.653a8.5 8.5 0 0 0 2.273-.942l1.618.72a10 10 0 0 0 2.047-2.046l-.721-1.618a8.5 8.5 0 0 0 .942-2.273l1.653-.634a10 10 0 0 0 0-2.896l-1.653-.634a8.5 8.5 0 0 0-.942-2.273l.72-1.618a10 10 0 0 0-2.046-2.047l-1.618.721a8.5 8.5 0 0 0-2.273-.942ZM22 13.592A6.408 6.408 0 1 1 15.592 20 6.41 6.41 0 0 1 22 13.592'/><path fill='#b3e5fc' d='M24.607 22.602a3.62 3.62 0 1 1-.006-5.118l.006.006-1.28 1.278a1.81 1.81 0 1 0-.004 2.56l.004-.004Z'/></svg>",
@@ -293,8 +298,8 @@
"folder-delta": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f8bbd0' d='M23 17.699 28.337 26H17.663zM23 14l-9 14h18z'/></svg>",
"folder-desktop-open": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M30 12H14a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6v2h-2v2h8v-2h-2v-2h6a2 2 0 0 0 2-2V14a2 2 0 0 0-2-2m0 12H14V14h16Z'/></svg>",
"folder-desktop": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M30 12H14a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6v2h-2v2h8v-2h-2v-2h6a2 2 0 0 0 2-2V14a2 2 0 0 0-2-2m0 12H14V14h16Z'/></svg>",
- "folder-development-open.clone": "<svg viewBox='0 0 32 32'><path fill='#0288D1' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#B3E5FC' d='M18.473 30a1 1 0 0 1-.238-.028 1.137 1.137 0 0 1-.828-1.323L20.5 12.905a1.13 1.13 0 0 1 .507-.744 1.06 1.06 0 0 1 .8-.134 1.14 1.14 0 0 1 .828 1.324l-3.101 15.744a1.12 1.12 0 0 1-.504.743 1.06 1.06 0 0 1-.557.162m6.2-2h-.077a1.08 1.08 0 0 1-.762-.412 1.164 1.164 0 0 1 .113-1.548l5.319-4.967-5.296-4.623a1.165 1.165 0 0 1-.162-1.544 1.08 1.08 0 0 1 .754-.437 1.06 1.06 0 0 1 .81.258l6.244 5.455a1.156 1.156 0 0 1 .003 1.723l-6.218 5.808a1.07 1.07 0 0 1-.729.289Zm-9.31 0a1.07 1.07 0 0 1-.728-.292l-6.226-5.811a1.16 1.16 0 0 1-.01-1.692l.02-.018 6.246-5.454a1.03 1.03 0 0 1 .8-.26 1.08 1.08 0 0 1 .76.436 1.165 1.165 0 0 1-.16 1.547l-5.294 4.62 5.32 4.964a1.156 1.156 0 0 1 .112 1.548 1.07 1.07 0 0 1-.762.412Z'/></svg>",
- "folder-development.clone": "<svg viewBox='0 0 32 32'><path fill='#0288D1' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#B3E5FC' d='M18.435 30a1 1 0 0 1-.238-.028 1.137 1.137 0 0 1-.828-1.323l3.093-15.744a1.13 1.13 0 0 1 .507-.744 1.06 1.06 0 0 1 .8-.134 1.14 1.14 0 0 1 .828 1.324l-3.1 15.744a1.12 1.12 0 0 1-.505.743 1.06 1.06 0 0 1-.557.162m6.2-2h-.077a1.08 1.08 0 0 1-.762-.412 1.164 1.164 0 0 1 .113-1.548l5.32-4.967-5.297-4.623a1.165 1.165 0 0 1-.162-1.544 1.08 1.08 0 0 1 .754-.437 1.06 1.06 0 0 1 .81.258l6.244 5.455a1.156 1.156 0 0 1 .004 1.723l-6.22 5.808a1.07 1.07 0 0 1-.728.289Zm-9.31 0a1.07 1.07 0 0 1-.728-.292l-6.225-5.811a1.16 1.16 0 0 1-.01-1.692l.02-.018 6.246-5.454a1.03 1.03 0 0 1 .8-.26 1.08 1.08 0 0 1 .758.436 1.165 1.165 0 0 1-.16 1.547l-5.293 4.62 5.32 4.964a1.156 1.156 0 0 1 .112 1.548 1.07 1.07 0 0 1-.762.412Z'/></svg>",
+ "folder-development-open.clone": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M18.473 30a1 1 0 0 1-.238-.028 1.137 1.137 0 0 1-.828-1.323L20.5 12.905a1.13 1.13 0 0 1 .507-.744 1.06 1.06 0 0 1 .8-.134 1.14 1.14 0 0 1 .828 1.324l-3.101 15.744a1.12 1.12 0 0 1-.504.743 1.06 1.06 0 0 1-.557.162m6.2-2h-.077a1.08 1.08 0 0 1-.762-.412 1.164 1.164 0 0 1 .113-1.548l5.319-4.967-5.296-4.623a1.165 1.165 0 0 1-.162-1.544 1.08 1.08 0 0 1 .754-.437 1.06 1.06 0 0 1 .81.258l6.244 5.455a1.156 1.156 0 0 1 .003 1.723l-6.218 5.808a1.07 1.07 0 0 1-.729.289Zm-9.31 0a1.07 1.07 0 0 1-.728-.292l-6.226-5.811a1.16 1.16 0 0 1-.01-1.692l.02-.018 6.246-5.454a1.03 1.03 0 0 1 .8-.26 1.08 1.08 0 0 1 .76.436 1.165 1.165 0 0 1-.16 1.547l-5.294 4.62 5.32 4.964a1.156 1.156 0 0 1 .112 1.548 1.07 1.07 0 0 1-.762.412Z'/></svg>",
+ "folder-development.clone": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M18.435 30a1 1 0 0 1-.238-.028 1.137 1.137 0 0 1-.828-1.323l3.093-15.744a1.13 1.13 0 0 1 .507-.744 1.06 1.06 0 0 1 .8-.134 1.14 1.14 0 0 1 .828 1.324l-3.1 15.744a1.12 1.12 0 0 1-.505.743 1.06 1.06 0 0 1-.557.162m6.2-2h-.077a1.08 1.08 0 0 1-.762-.412 1.164 1.164 0 0 1 .113-1.548l5.32-4.967-5.297-4.623a1.165 1.165 0 0 1-.162-1.544 1.08 1.08 0 0 1 .754-.437 1.06 1.06 0 0 1 .81.258l6.244 5.455a1.156 1.156 0 0 1 .004 1.723l-6.22 5.808a1.07 1.07 0 0 1-.728.289Zm-9.31 0a1.07 1.07 0 0 1-.728-.292l-6.225-5.811a1.16 1.16 0 0 1-.01-1.692l.02-.018 6.246-5.454a1.03 1.03 0 0 1 .8-.26 1.08 1.08 0 0 1 .758.436 1.165 1.165 0 0 1-.16 1.547l-5.293 4.62 5.32 4.964a1.156 1.156 0 0 1 .112 1.548 1.07 1.07 0 0 1-.762.412Z'/></svg>",
"folder-directive-open": "<svg viewBox='0 0 16 16'><path fill='#f44336' d='M14.484 6H4.72a1 1 0 0 0-.949.684L2 12V5h12a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232l-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h11l2.403-5.606A1 1 0 0 0 14.483 6'/><g fill='#ffcdd2'><path d='m11.5 6.001-1.5 2h3z'/><path d='M11 7v2h1V7zm-1 3H8v1.001h2z'/><path d='m9 9-2 1.5L9 12zm2 3h1v2h-1Z'/><path d='m10 13 1.5 2 1.5-2Zm2.715-3v1.001H15v-1Z'/><path d='m14 9 2 1.5-2 1.5Z'/></g></svg>",
"folder-directive": "<svg viewBox='0 0 16 16'><path fill='#f44336' d='m6.922 3.768-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232'/><g fill='#ffcdd2'><path d='m11.5 6.001-1.5 2h3z'/><path d='M11 7v2h1V7zm-1 3H8v1.001h2z'/><path d='m9 9-2 1.5L9 12zm2 3h1v2h-1Z'/><path d='m10 13 1.5 2 1.5-2Zm3-3v1.001h2v-1Z'/><path d='m14 9 2 1.5-2 1.5Z'/></g></svg>",
"folder-dist-open": "<svg viewBox='0 0 32 32'><path fill='#e57373' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M30 14h-4v-2l-2-2h-4l-2 2v2h-4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2m-10 0v-2h4v2Z'/></svg>",
@@ -323,13 +328,13 @@
"folder-examples": "<svg viewBox='0 0 32 32'><path fill='#009688' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b2dfdb' d='M16 14v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V14a2 2 0 0 0-2-2H18a2 2 0 0 0-2 2m2 0h2a2 2 0 0 1-2 2Zm0 4a4 4 0 0 0 4-4h2a6.005 6.005 0 0 1-6 6Zm0 8 4-4 1.6 1.6L26 20l4 6Z'/></svg>",
"folder-expo-open": "<svg viewBox='0 0 32 32'><path fill='#1976d2' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#bbdefb' d='M25.182 13.148c-.663-1.013-.82-1.148-2.17-1.148h-.032c-1.35 0-1.499.135-2.17 1.148C20.187 14.1 14 25.473 14 25.79a2.5 2.5 0 0 0 .545 1.513c.434.626 1.183.974 1.728.42.37-.373 4.34-7.24 6.257-9.837a.575.575 0 0 1 .94 0c1.916 2.597 5.887 9.464 6.257 9.837.545.554 1.294.204 1.728-.42A2.5 2.5 0 0 0 32 25.79c-.008-.317-6.195-11.699-6.818-12.642'/></svg>",
"folder-expo": "<svg viewBox='0 0 32 32'><path fill='#1976d2' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M25.182 13.148c-.663-1.013-.82-1.148-2.17-1.148h-.032c-1.35 0-1.499.135-2.17 1.148C20.187 14.1 14 25.473 14 25.79a2.5 2.5 0 0 0 .545 1.513c.434.626 1.183.974 1.728.42.37-.373 4.34-7.24 6.257-9.837a.575.575 0 0 1 .94 0c1.916 2.597 5.887 9.464 6.257 9.837.545.554 1.294.204 1.728-.42A2.5 2.5 0 0 0 32 25.79c-.008-.317-6.195-11.699-6.818-12.642'/></svg>",
- "folder-export-open": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#EFEBE9' fill-opacity='.949' d='M25 14a7 7 0 1 0 7 7 7 7 0 0 0-7-7m-5 8v-2h10v2Z'/></svg>",
- "folder-export": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#EFEBE9' fill-opacity='.949' d='M25 14a7 7 0 1 0 7 7 7 7 0 0 0-7-7m-5 8v-2h10v2Z'/></svg>",
+ "folder-export-open": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#efebe9' fill-opacity='.949' d='M25 14a7 7 0 1 0 7 7 7 7 0 0 0-7-7m-5 8v-2h10v2Z'/></svg>",
+ "folder-export": "<svg viewBox='0 0 32 32'><path fill='#ff8a65' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#efebe9' fill-opacity='.949' d='M25 14a7 7 0 1 0 7 7 7 7 0 0 0-7-7m-5 8v-2h10v2Z'/></svg>",
"folder-fastlane-open": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#e3f2fd' d='m15.508 21.618 1.272 3.936a1.456 1.456 0 0 0-.06 1.922 1.35 1.35 0 0 0 .937.486l.1.003a1.33 1.33 0 0 0 .894-.346 1.4 1.4 0 0 0 .207-.231 1.446 1.446 0 0 0-.307-1.972 1.36 1.36 0 0 0-.597-.256l-1.586-5.037a.16.16 0 0 0-.092-.101.15.15 0 0 0-.134.007 1.92 1.92 0 0 1-2.059-.113 1.995 1.995 0 0 1-.423-2.72 1.84 1.84 0 0 1 2.088-.697.16.16 0 0 0 .199-.102l.325-.95a.17.17 0 0 0-.007-.128.16.16 0 0 0-.093-.084 3 3 0 0 0-.978-.167h-.039A3.215 3.215 0 0 0 12 18.296a3.3 3.3 0 0 0 1.321 2.698 3.08 3.08 0 0 0 2.187.624'/><path fill='#e3f2fd' d='M15.802 17.146a1.35 1.35 0 0 0-1.787.535 1.45 1.45 0 0 0-.157 1.074 1.4 1.4 0 0 0 .622.875 1.33 1.33 0 0 0 .706.203 1.36 1.36 0 0 0 1.176-.685 1.47 1.47 0 0 0 .177-.971l4.14-3.149a.17.17 0 0 0 .065-.122.17.17 0 0 0-.05-.13 2.09 2.09 0 0 1-.556-2.063 1.883 1.883 0 0 1 2.383-1.262 1.95 1.95 0 0 1 1.31 1.823.16.16 0 0 0 .155.161l.983.02a.2.2 0 0 0 .114-.047.17.17 0 0 0 .048-.117 3.34 3.34 0 0 0-.945-2.333A3.12 3.12 0 0 0 21.939 10a4 4 0 0 0-.293.014 3.14 3.14 0 0 0-2.162 1.182 3.4 3.4 0 0 0-.457 3.457Zm10.842 10.755a1.4 1.4 0 0 0 .736-.77 1.45 1.45 0 0 0-.005-1.083 1.38 1.38 0 0 0-.744-.763 1.3 1.3 0 0 0-1.047.005 1.4 1.4 0 0 0-.693.672l-5.12.014a.16.16 0 0 0-.123.06.17.17 0 0 0-.033.135 2.08 2.08 0 0 1-.724 2 1.82 1.82 0 0 1-1.4.355 1.86 1.86 0 0 1-1.233-.773 2 2 0 0 1-.016-2.286.17.17 0 0 0-.033-.226l-.778-.613a.15.15 0 0 0-.12-.032.16.16 0 0 0-.105.065 3.36 3.36 0 0 0-.575 2.45 3.3 3.3 0 0 0 1.266 2.153 3.1 3.1 0 0 0 1.874.634 3.15 3.15 0 0 0 2.572-1.349 3.4 3.4 0 0 0 .545-1.264l4.01-.04a1.35 1.35 0 0 0 1.746.656'/><path fill='#e3f2fd' d='m27.718 23.882 1.228-3.945a1.34 1.34 0 0 0 .824-.473 1.44 1.44 0 0 0 .328-1.026 1.366 1.366 0 0 0-2.729-.013 1.5 1.5 0 0 0 .06.553 1.4 1.4 0 0 0 .336.564l-1.603 5.027a.17.17 0 0 0 .016.14.16.16 0 0 0 .115.075 1.952 1.952 0 0 1 .385 3.782 1.84 1.84 0 0 1-2.097-.692.156.156 0 0 0-.217-.037l-.811.572a.16.16 0 0 0-.067.108.17.17 0 0 0 .028.124A3.15 3.15 0 0 0 26.097 30a3.1 3.1 0 0 0 1.871-.63 3.36 3.36 0 0 0 1.165-3.667 3.23 3.23 0 0 0-1.415-1.82Z'/><path fill='#e3f2fd' d='M31.845 17.545a3.15 3.15 0 0 0-5.177-1.459l-3.28-2.432a1.46 1.46 0 0 0-.193-.969 1.37 1.37 0 0 0-.857-.637 1.3 1.3 0 0 0-.308-.038h-.004a1.425 1.425 0 0 0-.003 2.847h.005a1.3 1.3 0 0 0 .631-.16l4.165 3.137a.16.16 0 0 0 .133.027.16.16 0 0 0 .104-.089 1.98 1.98 0 0 1 1.735-1.178h.001a1.933 1.933 0 0 1 1.897 1.963 1.96 1.96 0 0 1-1.296 1.863.166.166 0 0 0-.102.203l.28.98a.16.16 0 0 0 .079.098.15.15 0 0 0 .123.011 3.2 3.2 0 0 0 1.867-1.64 3.4 3.4 0 0 0 .2-2.527'/></svg>",
"folder-fastlane": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e3f2fd' d='m15.508 21.618 1.272 3.936a1.456 1.456 0 0 0-.06 1.922 1.35 1.35 0 0 0 .937.486l.1.003a1.33 1.33 0 0 0 .894-.346 1.4 1.4 0 0 0 .207-.231 1.446 1.446 0 0 0-.307-1.972 1.36 1.36 0 0 0-.597-.256l-1.586-5.037a.16.16 0 0 0-.092-.101.15.15 0 0 0-.134.007 1.92 1.92 0 0 1-2.059-.113 1.995 1.995 0 0 1-.423-2.72 1.84 1.84 0 0 1 2.088-.697.16.16 0 0 0 .199-.102l.325-.95a.17.17 0 0 0-.007-.128.16.16 0 0 0-.093-.084 3 3 0 0 0-.978-.167h-.039A3.215 3.215 0 0 0 12 18.296a3.3 3.3 0 0 0 1.321 2.698 3.08 3.08 0 0 0 2.187.624'/><path fill='#e3f2fd' d='M15.802 17.146a1.35 1.35 0 0 0-1.787.535 1.45 1.45 0 0 0-.157 1.074 1.4 1.4 0 0 0 .622.875 1.33 1.33 0 0 0 .706.203 1.36 1.36 0 0 0 1.176-.685 1.47 1.47 0 0 0 .177-.971l4.14-3.149a.17.17 0 0 0 .065-.122.17.17 0 0 0-.05-.13 2.09 2.09 0 0 1-.556-2.063 1.883 1.883 0 0 1 2.383-1.262 1.95 1.95 0 0 1 1.31 1.823.16.16 0 0 0 .155.161l.983.02a.2.2 0 0 0 .114-.047.17.17 0 0 0 .048-.117 3.34 3.34 0 0 0-.945-2.333A3.12 3.12 0 0 0 21.939 10a4 4 0 0 0-.293.014 3.14 3.14 0 0 0-2.162 1.182 3.4 3.4 0 0 0-.457 3.457Zm10.842 10.755a1.4 1.4 0 0 0 .736-.77 1.45 1.45 0 0 0-.005-1.083 1.38 1.38 0 0 0-.744-.763 1.3 1.3 0 0 0-1.047.005 1.4 1.4 0 0 0-.693.672l-5.12.014a.16.16 0 0 0-.123.06.17.17 0 0 0-.033.135 2.08 2.08 0 0 1-.724 2 1.82 1.82 0 0 1-1.4.355 1.86 1.86 0 0 1-1.233-.773 2 2 0 0 1-.016-2.286.17.17 0 0 0-.033-.226l-.778-.613a.15.15 0 0 0-.12-.032.16.16 0 0 0-.105.065 3.36 3.36 0 0 0-.575 2.45 3.3 3.3 0 0 0 1.266 2.153 3.1 3.1 0 0 0 1.874.634 3.15 3.15 0 0 0 2.572-1.349 3.4 3.4 0 0 0 .545-1.264l4.01-.04a1.35 1.35 0 0 0 1.746.656'/><path fill='#e3f2fd' d='m27.718 23.882 1.228-3.945a1.34 1.34 0 0 0 .824-.473 1.44 1.44 0 0 0 .328-1.026 1.366 1.366 0 0 0-2.729-.013 1.5 1.5 0 0 0 .06.553 1.4 1.4 0 0 0 .336.564l-1.603 5.027a.17.17 0 0 0 .016.14.16.16 0 0 0 .115.075 1.952 1.952 0 0 1 .385 3.782 1.84 1.84 0 0 1-2.097-.692.156.156 0 0 0-.217-.037l-.811.572a.16.16 0 0 0-.067.108.17.17 0 0 0 .028.124A3.15 3.15 0 0 0 26.097 30a3.1 3.1 0 0 0 1.871-.63 3.36 3.36 0 0 0 1.165-3.667 3.23 3.23 0 0 0-1.415-1.82Z'/><path fill='#e3f2fd' d='M31.845 17.545a3.15 3.15 0 0 0-5.177-1.459l-3.28-2.432a1.46 1.46 0 0 0-.193-.969 1.37 1.37 0 0 0-.857-.637 1.3 1.3 0 0 0-.308-.038h-.004a1.425 1.425 0 0 0-.003 2.847h.005a1.3 1.3 0 0 0 .631-.16l4.165 3.137a.16.16 0 0 0 .133.027.16.16 0 0 0 .104-.089 1.98 1.98 0 0 1 1.735-1.178h.001a1.933 1.933 0 0 1 1.897 1.963 1.96 1.96 0 0 1-1.296 1.863.166.166 0 0 0-.102.203l.28.98a.16.16 0 0 0 .079.098.15.15 0 0 0 .123.011 3.2 3.2 0 0 0 1.867-1.64 3.4 3.4 0 0 0 .2-2.527'/></svg>",
"folder-favicon-open": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124c-.468 0-.921-.164-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fffde7' d='m24 24 6 4-2-6 4-4h-6l-1.999-6L22 18h-6l4 4-2 6z'/></svg>",
"folder-favicon": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fffde7' d='m24 24 6 4-2-6 4-4h-6l-1.999-6L22 18h-6l4 4-2 6z'/></svg>",
- "folder-firebase-open": "<svg viewBox='0 0 32 32'><path fill='#FF9100' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffe0b2' d='M24 28.014s-4.584.213-7-4.014c-.66-1.156-1.006-2.805-1-4 .012-2.264.962-3.881 1.038-4 .117-.181 2.954-.867 4.962 0s3.979 3.215 4 6-2.275 4.565-2.691 4.881.691 1.133.691 1.133M22 25.5c2.051-1.646 1.875-2.063 2-3.5s-1.007-3.071-2-4-2.934-.65-3.5-.5c-2.418 5.405 3.5 8 3.5 8'/><path fill='#ffe0b2' d='m24 28.014 2.527-.941s-1.988-1.265-2.909-2.168C21.381 22.71 20.021 20.085 20 16s4-8 4-8 8.063 6.276 8 12c-.644 8.183-8 8.014-8 8.014m4-3.023c1.044-1.135 1.95-2.042 2-4.991.075-4.381-6-9.5-6-9.5s-1.856 2.393-2 5.5c-.338 7.273 6 8.991 6 8.991'/><path fill='#ffe0b2' d='M22.34 25.64s3.453-.086 5.66-.649c3.451-.879-1.022 2.191-1.022 2.191L24 28.015l-1.313-.536z'/></svg>",
+ "folder-firebase-open": "<svg viewBox='0 0 32 32'><path fill='#ff9100' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffe0b2' d='M24 28.014s-4.584.213-7-4.014c-.66-1.156-1.006-2.805-1-4 .012-2.264.962-3.881 1.038-4 .117-.181 2.954-.867 4.962 0s3.979 3.215 4 6-2.275 4.565-2.691 4.881.691 1.133.691 1.133M22 25.5c2.051-1.646 1.875-2.063 2-3.5s-1.007-3.071-2-4-2.934-.65-3.5-.5c-2.418 5.405 3.5 8 3.5 8'/><path fill='#ffe0b2' d='m24 28.014 2.527-.941s-1.988-1.265-2.909-2.168C21.381 22.71 20.021 20.085 20 16s4-8 4-8 8.063 6.276 8 12c-.644 8.183-8 8.014-8 8.014m4-3.023c1.044-1.135 1.95-2.042 2-4.991.075-4.381-6-9.5-6-9.5s-1.856 2.393-2 5.5c-.338 7.273 6 8.991 6 8.991'/><path fill='#ffe0b2' d='M22.34 25.64s3.453-.086 5.66-.649c3.451-.879-1.022 2.191-1.022 2.191L24 28.015l-1.313-.536z'/></svg>",
"folder-firebase": "<svg fill-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='2' clip-rule='evenodd' viewBox='0 0 32 32'><path fill='#ff9100' fill-rule='nonzero' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4c-1.097 0-2 .903-2 2v16c0 1.097.903 2 2 2h24c1.097 0 2-.903 2-2V10c0-1.097-.903-2-2-2H15.124c-.468 0-.921-.164-1.28-.464'/><path fill='#ffe0b2' d='M24 28.014s-4.584.213-7-4.014c-.66-1.156-1.006-2.805-1-4 .012-2.264.962-3.881 1.038-4 .117-.181 2.954-.867 4.962 0s3.979 3.215 4 6-2.275 4.565-2.691 4.881.691 1.133.691 1.133M22 25.5c2.051-1.646 1.875-2.063 2-3.5s-1.007-3.071-2-4-2.934-.65-3.5-.5c-2.418 5.405 3.5 8 3.5 8'/><path fill='#ffe0b2' d='m24 28.014 2.527-.941s-1.988-1.265-2.909-2.168C21.381 22.71 20.021 20.085 20 16s4-8 4-8 8.063 6.276 8 12c-.644 8.183-8 8.014-8 8.014m4-3.023c1.044-1.135 1.95-2.042 2-4.991.075-4.381-6-9.5-6-9.5s-1.856 2.393-2 5.5c-.338 7.273 6 8.991 6 8.991'/><path fill='#ffe0b2' d='M22.34 25.64s3.453-.086 5.66-.649c3.451-.879-1.022 2.191-1.022 2.191L24 28.015l-1.313-.536z'/></svg>",
"folder-firestore-open": "<svg fill-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='2' clip-rule='evenodd' viewBox='0 0 32 32'><path fill='#2196f3' fill-rule='nonzero' d='M28.967 12H9.442c-.859 0-1.627.553-1.898 1.368L4 24V10h24c0-1.097-.903-2-2-2H15.124c-.468 0-.921-.164-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4c-1.097 0-2 .903-2 2v16c0 1.097.903 2 2 2h22l4.805-11.212c.107-.249.162-.517.162-.788 0-1.097-.903-2-2-2'/><path fill='#bbdefb' d='m24 10-8 4v4l8-4 8 4v-4z'/><path fill='#bbdefb' d='M16 20v4l8-4 8 4v-4l-8-4z'/><path fill='#bbdefb' d='m24 24 3-1 4 2-7 3z'/></svg>",
"folder-firestore": "<svg fill-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='2' clip-rule='evenodd' viewBox='0 0 32 32'><path fill='#2196f3' fill-rule='nonzero' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4c-1.097 0-2 .903-2 2v16c0 1.097.903 2 2 2h24c1.097 0 2-.903 2-2V10c0-1.097-.903-2-2-2H15.124c-.468 0-.921-.164-1.28-.464'/><path fill='#bbdefb' d='m24 10-8 4v4l8-4 8 4v-4z'/><path fill='#bbdefb' d='M16 20v4l8-4 8 4v-4l-8-4z'/><path fill='#bbdefb' d='m24 24 3-1 4 2-7 3z'/></svg>",
@@ -339,8 +344,8 @@
"folder-flutter": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='m13.8 7.5-1.2-1a2 2 0 0 0-1.3-.5H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.1a2 2 0 0 1-1.3-.5'/><path fill='#b3e5fc' d='m20 10-8 8 4 4 12-12zm4 8-6 6 6 6h8l-6-6 6-6z'/></svg>",
"folder-font-open": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M24.077 12h-2.154L16 28h2.423L20 24h6l1.577 4H30Zm-3.64 10L23 14.764 25.552 22Z'/></svg>",
"folder-font": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M24.077 12h-2.154L16 28h2.423L20 24h6l1.577 4H30Zm-3.64 10L23 14.764 25.552 22Z'/></svg>",
- "folder-forgejo-open": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><g fill='none' transform='translate(14.53 10.455)scale(.08531)'><path stroke='#FF6D00' stroke-width='25' d='M58 168V70a50 50 0 0 1 50-50h20' class='prefix__prefix__orange'/><path stroke='#D50000' stroke-width='25' d='M58 168v-30a50 50 0 0 1 50-50h20' class='prefix__prefix__red'/><circle cx='142' cy='20' r='18' stroke='#FF6D00' stroke-width='15' class='prefix__prefix__orange'/><circle cx='142' cy='88' r='18' stroke='#D50000' stroke-width='15' class='prefix__prefix__red'/><circle cx='58' cy='180' r='18' stroke='#D50000' stroke-width='15' class='prefix__prefix__red'/></g></svg>",
- "folder-forgejo": "<svg viewBox='0 0 32 32'><path fill='#757575' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g fill='none' transform='translate(14.53 10.455)scale(.08531)'><path stroke='#FF6D00' stroke-width='25' d='M58 168V70a50 50 0 0 1 50-50h20' class='prefix__prefix__orange'/><path stroke='#D50000' stroke-width='25' d='M58 168v-30a50 50 0 0 1 50-50h20' class='prefix__prefix__red'/><circle cx='142' cy='20' r='18' stroke='#FF6D00' stroke-width='15' class='prefix__prefix__orange'/><circle cx='142' cy='88' r='18' stroke='#D50000' stroke-width='15' class='prefix__prefix__red'/><circle cx='58' cy='180' r='18' stroke='#D50000' stroke-width='15' class='prefix__prefix__red'/></g></svg>",
+ "folder-forgejo-open": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><g fill='none' transform='translate(14.53 10.455)scale(.08531)'><path stroke='#ff6d00' stroke-width='25' d='M58 168V70a50 50 0 0 1 50-50h20' class='prefix__prefix__orange'/><path stroke='#d50000' stroke-width='25' d='M58 168v-30a50 50 0 0 1 50-50h20' class='prefix__prefix__red'/><circle cx='142' cy='20' r='18' stroke='#ff6d00' stroke-width='15' class='prefix__prefix__orange'/><circle cx='142' cy='88' r='18' stroke='#d50000' stroke-width='15' class='prefix__prefix__red'/><circle cx='58' cy='180' r='18' stroke='#d50000' stroke-width='15' class='prefix__prefix__red'/></g></svg>",
+ "folder-forgejo": "<svg viewBox='0 0 32 32'><path fill='#757575' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g fill='none' transform='translate(14.53 10.455)scale(.08531)'><path stroke='#ff6d00' stroke-width='25' d='M58 168V70a50 50 0 0 1 50-50h20' class='prefix__prefix__orange'/><path stroke='#d50000' stroke-width='25' d='M58 168v-30a50 50 0 0 1 50-50h20' class='prefix__prefix__red'/><circle cx='142' cy='20' r='18' stroke='#ff6d00' stroke-width='15' class='prefix__prefix__orange'/><circle cx='142' cy='88' r='18' stroke='#d50000' stroke-width='15' class='prefix__prefix__red'/><circle cx='58' cy='180' r='18' stroke='#d50000' stroke-width='15' class='prefix__prefix__red'/></g></svg>",
"folder-functions-open": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M24 16h-2.982l.14-1.676.002-.01a1.945 1.945 0 0 1 3.848-.485l1.475-1.687a3.9 3.9 0 0 0-3.01-2.126 4.143 4.143 0 0 0-4.263 4.105L19.048 16H16v2h2.87l-.529 5.874a2.05 2.05 0 0 1-1.348 1.776l-.026.009a1.92 1.92 0 0 1-2.451-1.465l-1.477 1.687a3.91 3.91 0 0 0 2.99 2.1 4.13 4.13 0 0 0 4.274-4.08L20.839 18H24Zm8 4.929-1.414-1.414-2.829 2.828-2.828-2.828-1.414 1.414 2.828 2.828-2.828 2.829L24.929 28l2.828-2.828L30.586 28 32 26.586l-2.828-2.829z'/></svg>",
"folder-functions": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M24 16h-2.982l.14-1.676.002-.01a1.945 1.945 0 0 1 3.848-.485l1.475-1.687a3.9 3.9 0 0 0-3.01-2.126 4.143 4.143 0 0 0-4.263 4.105L19.048 16H16v2h2.87l-.529 5.874a2.05 2.05 0 0 1-1.348 1.776l-.026.009a1.92 1.92 0 0 1-2.451-1.465l-1.477 1.687a3.91 3.91 0 0 0 2.99 2.1 4.13 4.13 0 0 0 4.274-4.08L20.839 18H24Zm8 4.929-1.414-1.414-2.829 2.828-2.828-2.828-1.414 1.414 2.828 2.828-2.828 2.829L24.929 28l2.828-2.828L30.586 28 32 26.586l-2.828-2.829z'/></svg>",
"folder-gamemaker-open": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b2dfdb' d='m32 20-9.03-9.03L13.942 20l9.03 9.03 3.765-3.766.007-5.264Zm-9.513 2.526L19.96 20l3.01-3.01L25.98 20h-3.494Z'/></svg>",
@@ -407,8 +412,8 @@
"folder-job": "<svg viewBox='0 0 32 32'><path fill='#ffa000' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffecb3' d='M30 14h-4v-2l-2-2h-4l-2 2v2h-4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2m-10-2h4v2h-4Zm6 10-7.023 4-1.014-1.725 5.558-3.27-5.558-3.269 1.014-1.724L26 20Z'/></svg>",
"folder-json-open": "<svg viewBox='0 0 32 32'><path fill='#f9a825' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fffde7' d='M24 28v-2h3q.425 0 .713-.288Q28 25.425 28 25v-2q0-.95.55-1.725t1.45-1.1v-.35q-.9-.325-1.45-1.1T28 17v-2q0-.425-.287-.712Q27.424 14 27 14h-3v-2h3q1.25 0 2.125.875T30 15v2q0 .425.287.712.288.288.713.288h1v4h-1q-.425 0-.713.288Q30 22.575 30 23v2q0 1.25-.875 2.125T27 28zm-7 0q-1.25 0-2.125-.875T14 25v-2q0-.425-.287-.712Q13.425 22 13 22h-1v-4h1q.425 0 .713-.288Q14 17.425 14 17v-2q0-1.25.875-2.125T17 12h3v2h-3q-.425 0-.713.288Q16 14.575 16 15v2q0 .95-.55 1.725t-1.45 1.1v.35q.9.325 1.45 1.1T16 23v2q0 .425.287.712.288.288.713.288h3v2z'/></svg>",
"folder-json": "<svg viewBox='0 0 32 32'><path fill='#f9a825' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fffde7' d='M24 28v-2h3q.425 0 .713-.287T28 25v-2q0-.95.55-1.725t1.45-1.1v-.35q-.9-.325-1.45-1.1T28 17v-2q0-.425-.287-.713T27 14h-3v-2h3q1.25 0 2.125.875T30 15v2q0 .425.287.712T31 18h1v4h-1q-.425 0-.713.287T30 23v2q0 1.25-.875 2.125T27 28zm-7 0q-1.25 0-2.125-.875T14 25v-2q0-.425-.287-.713T13 22h-1v-4h1q.425 0 .713-.287T14 17v-2q0-1.25.875-2.125T17 12h3v2h-3q-.425 0-.713.287T16 15v2q0 .95-.55 1.725t-1.45 1.1v.35q.9.325 1.45 1.1T16 23v2q0 .425.287.713T17 26h3v2z'/></svg>",
- "folder-jupyter-open": "<svg viewBox='0 0 32 32'><path fill='#FF9800' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><g fill='#FFE0B2' data-mit-no-recolor='true' transform='matrix(.7 0 0 .7 10 10)'><path d='M6.2 18a22.7 22.7 0 0 0 9.8 2 22.7 22.7 0 0 0 9.8-2 10.002 10.002 0 0 1-19.6 0m0-4a22.7 22.7 0 0 1 9.8-2 22.7 22.7 0 0 1 9.8 2 10.002 10.002 0 0 0-19.6 0'/><circle cx='27' cy='5' r='3'/><circle cx='5' cy='27' r='3'/><circle cx='5' cy='5' r='3'/></g></svg>",
- "folder-jupyter": "<svg viewBox='0 0 32 32'><path fill='#FF9800' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g fill='#FFE0B2' data-mit-no-recolor='true' transform='matrix(.7 0 0 .7 10 10)'><path d='M6.2 18a22.7 22.7 0 0 0 9.8 2 22.7 22.7 0 0 0 9.8-2 10.002 10.002 0 0 1-19.6 0m0-4a22.7 22.7 0 0 1 9.8-2 22.7 22.7 0 0 1 9.8 2 10.002 10.002 0 0 0-19.6 0'/><circle cx='27' cy='5' r='3'/><circle cx='5' cy='27' r='3'/><circle cx='5' cy='5' r='3'/></g></svg>",
+ "folder-jupyter-open": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><g fill='#ffe0b2' data-mit-no-recolor='true' transform='matrix(.7 0 0 .7 10 10)'><path d='M6.2 18a22.7 22.7 0 0 0 9.8 2 22.7 22.7 0 0 0 9.8-2 10.002 10.002 0 0 1-19.6 0m0-4a22.7 22.7 0 0 1 9.8-2 22.7 22.7 0 0 1 9.8 2 10.002 10.002 0 0 0-19.6 0'/><circle cx='27' cy='5' r='3'/><circle cx='5' cy='27' r='3'/><circle cx='5' cy='5' r='3'/></g></svg>",
+ "folder-jupyter": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g fill='#ffe0b2' data-mit-no-recolor='true' transform='matrix(.7 0 0 .7 10 10)'><path d='M6.2 18a22.7 22.7 0 0 0 9.8 2 22.7 22.7 0 0 0 9.8-2 10.002 10.002 0 0 1-19.6 0m0-4a22.7 22.7 0 0 1 9.8-2 22.7 22.7 0 0 1 9.8 2 10.002 10.002 0 0 0-19.6 0'/><circle cx='27' cy='5' r='3'/><circle cx='5' cy='27' r='3'/><circle cx='5' cy='5' r='3'/></g></svg>",
"folder-keys-open": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b2dfdb' d='M21.651 20a6 6 0 1 0 0 4H26v4h4v-4h2v-4ZM16 24a2 2 0 1 1 2-2 2 2 0 0 1-2 2'/></svg>",
"folder-keys": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b2dfdb' d='M21.651 20a6 6 0 1 0 0 4H26v4h4v-4h2v-4ZM16 24a2 2 0 1 1 2-2 2 2 0 0 1-2 2'/></svg>",
"folder-kubernetes-open": "<svg viewBox='0 0 32 32'><path fill='#448aff' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#bbdefb' d='M22.069 10.463a.6.6 0 0 0-.116.003.59.59 0 0 0-.517.635v.16a3.6 3.6 0 0 0 .08.543 5.2 5.2 0 0 1 .065 1.018.6.6 0 0 1-.186.305l-.013.238a7 7 0 0 0-1.031.157 7.27 7.27 0 0 0-3.706 2.117l-.196-.145a.52.52 0 0 1-.346-.039 5.4 5.4 0 0 1-.765-.69 5 5 0 0 0-.372-.395l-.12-.093a.75.75 0 0 0-.397-.158.6.6 0 0 0-.463.197.61.61 0 0 0 .148.834l.013.013c.026.027.079.067.106.093a4 4 0 0 0 .475.277 5.4 5.4 0 0 1 .848.597.6.6 0 0 1 .106.33l.183.158a7.22 7.22 0 0 0-1.137 5.121l-.25.065a.8.8 0 0 1-.254.253 4.4 4.4 0 0 1-1.018.158 2 2 0 0 0-.543.051l-.144.029h-.013v.013c-.04 0-.08.013-.106.013a.57.57 0 1 0 .339 1.086l.004-.001a1 1 0 0 0 .174-.041 2.7 2.7 0 0 0 .488-.197 7 7 0 0 1 1.018-.292.5.5 0 0 1 .305.119l.263-.039a7.43 7.43 0 0 0 3.27 4.088l-.094.238a.7.7 0 0 1 .042.33 4.2 4.2 0 0 1-.517.913c-.106.159-.199.304-.318.462a.17.17 0 0 1-.052.148c-.013.04-.04.066-.054.106a.57.57 0 0 0 1.072.382c.027-.04.051-.132.078-.132a5 5 0 0 0 .16-.53 5 5 0 0 1 .437-1.017.45.45 0 0 1 .25-.12l.132-.237a7.4 7.4 0 0 0 5.254.013l.105.212a.5.5 0 0 1 .277.183 6 6 0 0 1 .398.954c.04.172.094.344.16.542.027 0 .051.08.078.12.013.039.028.065.041.105a.568.568 0 0 0 .984-.569l-.007-.012a1 1 0 0 1-.052-.16c-.106-.146-.212-.305-.318-.45a7.4 7.4 0 0 1-.501-.9.44.44 0 0 1 .039-.343 1 1 0 0 1-.093-.225 7.44 7.44 0 0 0 3.268-4.113c.08.013.158.025.251.038a.33.33 0 0 1 .305-.106 4.7 4.7 0 0 1 1.018.28 2.6 2.6 0 0 0 .475.196.7.7 0 0 0 .187.028v.013c0 .013.053.013.093.026a.585.585 0 0 0 .635-.491.57.57 0 0 0-.483-.645l-.008-.001a.34.34 0 0 1-.157-.067h-.543a6.6 6.6 0 0 1-1.018-.186.55.55 0 0 1-.253-.238l-.251-.064a7.2 7.2 0 0 0-1.165-5.109l.211-.184a.4.4 0 0 1 .106-.317 5 5 0 0 1 .848-.597 3.3 3.3 0 0 0 .462-.277 1 1 0 0 0 .12-.093c.039-.026.08-.053.08-.08a.556.556 0 0 0-.78-.793c-.04 0-.093.054-.133.08a10 10 0 0 0-.372.395 4.8 4.8 0 0 1-.767.69.5.5 0 0 1-.344.039l-.212.158a7.37 7.37 0 0 0-4.708-2.274l-.013-.253a.45.45 0 0 1-.186-.29 5.2 5.2 0 0 1 .065-1.018 3.6 3.6 0 0 0 .08-.543v-.292a.57.57 0 0 0-.504-.506m-.778 4.408-.16 2.977h-.013a.5.5 0 0 1-.106.28.5.5 0 0 1-.687.118h-.013l-2.434-1.734a5.75 5.75 0 0 1 2.803-1.535c.212-.04.412-.08.61-.106m1.427 0a5.9 5.9 0 0 1 3.4 1.641l-2.421 1.734h-.013a.6.6 0 0 1-.277.067.494.494 0 0 1-.516-.47v-.008Zm-5.727 2.713 2.223 2.024v.013a.34.34 0 0 1 .144.253.483.483 0 0 1-.323.602l-.02.005v.013l-2.858.822a5.9 5.9 0 0 1 .834-3.732m10.013.04a6.18 6.18 0 0 1 .86 3.679l-2.87-.822-.013-.013a.47.47 0 0 1-.238-.157.49.49 0 0 1 .042-.694l.01-.01-.013-.038Zm-5.462 2.144h.912l.568.7-.199.886-.819.398-.819-.398-.212-.886Zm-2.132 2.447h.106a.55.55 0 0 1 .504.382.5.5 0 0 1-.052.28v.038l-1.127 2.74a5.84 5.84 0 0 1-2.366-2.952Zm4.87 0h.319l2.948.475a5.85 5.85 0 0 1-2.367 2.977l-1.14-2.79a.53.53 0 0 1 .24-.662m-2.327 1.199a.51.51 0 0 1 .488.256h.013l1.442 2.607a5 5 0 0 1-.569.157 5.9 5.9 0 0 1-3.214-.157l1.442-2.607h.012c.04-.093.107-.133.2-.2a.5.5 0 0 1 .186-.056'/></svg>",
@@ -423,8 +428,8 @@
"folder-less": "<svg viewBox='0 0 32 32'><path fill='#0277bd' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M20 21a1 1 0 0 0-1-1 1 1 0 0 0 1-1v-5h2v-2h-2a2 2 0 0 0-2 2v4a1 1 0 0 1-1 1h-1v2h1a1 1 0 0 1 1 1v4a2 2 0 0 0 2 2h2v-2h-2Zm11-2a1 1 0 0 1-1-1v-4a2 2 0 0 0-2-2h-2v2h2v5a1 1 0 0 0 1 1 1 1 0 0 0-1 1v5h-2v2h2a2 2 0 0 0 2-2v-4a1 1 0 0 1 1-1h1v-2Z'/></svg>",
"folder-lib-open": "<svg viewBox='0 0 32 32'><path fill='#c0ca33' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f0f4c3' d='M23 16a3 3 0 0 0 .003-6H23a3 3 0 0 0-3 2.999V13a3 3 0 0 0 2.999 3zm0 3.973c-2.225-2.078-5.955-3.978-9-3.973v10c3.19 0 6.85 2.004 9 4 2.225-2.078 5.955-4.005 9-4V16c-3.045-.005-6.775 1.895-9 3.973'/></svg>",
"folder-lib": "<svg viewBox='0 0 32 32'><path fill='#c0ca33' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f0f4c3' d='M22.931 16a3 3 0 0 0 .003-6h-.003a3 3 0 0 0-3 2.999V13a3 3 0 0 0 2.999 3zm0 3.973c-2.225-2.078-5.955-3.978-9-3.973v10c3.19 0 6.85 2.004 9 4 2.226-2.078 5.955-4.005 9-4V16c-3.044-.005-6.774 1.895-9 3.973'/></svg>",
- "folder-link-open": "<svg viewBox='0 0 1024 1024'><path fill='#7E57C2' d='M926.912 384H302.144a64 64 0 0 0-60.736 43.776L128 768V320h768a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848l-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h704l153.792-358.784A64 64 0 0 0 926.912 384'/><path fill='#d1c4e9' d='M736 320c-53.02 0-96 42.98-96 96 .104 40.593 25.729 76.733 64 90.264V576h-32c-17.673 0-32 14.327-32 32s14.327 32 32 32h32v190.547C595.489 821.653 512 768.467 512 704h64L448 576v128c0 106.039 128.942 192 288 192s288-85.961 288-192V576L896 704h64c0 64.467-83.489 117.653-192 126.547V640h32c17.673 0 32-14.327 32-32s-14.327-32-32-32h-32v-69.736c38.271-13.531 63.896-49.671 64-90.264 0-53.02-42.98-96-96-96m0 64c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32'/></svg>",
- "folder-link": "<svg viewBox='0 0 1024 1024'><path fill='#7E57C2' d='m443.008 241.152-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848'/><path fill='#d1c4e9' d='M736 320c-53.02 0-96 42.98-96 96 .104 40.593 25.729 76.733 64 90.264V576h-32c-17.673 0-32 14.327-32 32s14.327 32 32 32h32v190.547C595.489 821.653 512 768.467 512 704h64L448 576v128c0 106.039 128.942 192 288 192s288-85.961 288-192V576L896 704h64c0 64.467-83.489 117.653-192 126.547V640h32c17.673 0 32-14.327 32-32s-14.327-32-32-32h-32v-69.736c38.271-13.531 63.896-49.671 64-90.264 0-53.02-42.98-96-96-96m0 64c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32'/></svg>",
+ "folder-link-open": "<svg viewBox='0 0 1024 1024'><path fill='#7e57c2' d='M926.912 384H302.144a64 64 0 0 0-60.736 43.776L128 768V320h768a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848l-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h704l153.792-358.784A64 64 0 0 0 926.912 384'/><path fill='#d1c4e9' d='M736 320c-53.02 0-96 42.98-96 96 .104 40.593 25.729 76.733 64 90.264V576h-32c-17.673 0-32 14.327-32 32s14.327 32 32 32h32v190.547C595.489 821.653 512 768.467 512 704h64L448 576v128c0 106.039 128.942 192 288 192s288-85.961 288-192V576L896 704h64c0 64.467-83.489 117.653-192 126.547V640h32c17.673 0 32-14.327 32-32s-14.327-32-32-32h-32v-69.736c38.271-13.531 63.896-49.671 64-90.264 0-53.02-42.98-96-96-96m0 64c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32'/></svg>",
+ "folder-link": "<svg viewBox='0 0 1024 1024'><path fill='#7e57c2' d='m443.008 241.152-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848'/><path fill='#d1c4e9' d='M736 320c-53.02 0-96 42.98-96 96 .104 40.593 25.729 76.733 64 90.264V576h-32c-17.673 0-32 14.327-32 32s14.327 32 32 32h32v190.547C595.489 821.653 512 768.467 512 704h64L448 576v128c0 106.039 128.942 192 288 192s288-85.961 288-192V576L896 704h64c0 64.467-83.489 117.653-192 126.547V640h32c17.673 0 32-14.327 32-32s-14.327-32-32-32h-32v-69.736c38.271-13.531 63.896-49.671 64-90.264 0-53.02-42.98-96-96-96m0 64c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32'/></svg>",
"folder-linux-open": "<svg viewBox='0 0 32 32'><path fill='#f9a825' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffecb3' d='M24.62 16.35c-.42.28-1.75 1.04-1.95 1.19a.825.825 0 0 1-1.14-.01c-.2-.16-1.53-.92-1.95-1.19-.48-.309-.45-.699.08-.919a6.16 6.16 0 0 1 4.91.03c.49.21.51.6.05.9Zm7.218 7.279A19.1 19.1 0 0 0 28 17.971a4.3 4.3 0 0 1-1.06-1.88c-.1-.33-.17-.67-.24-1.01a11.3 11.3 0 0 0-.7-2.609 4.06 4.06 0 0 0-3.839-2.47 4.2 4.2 0 0 0-3.95 2.4 6 6 0 0 0-.46 1.34c-.17.76-.32 1.55-.5 2.319a3.4 3.4 0 0 1-.959 1.71 19.5 19.5 0 0 0-3.88 5.348 6 6 0 0 0-.37.88c-.19.66.29 1.12.99.96.44-.09.88-.18 1.3-.31.41-.15.57-.05.67.35a6.73 6.73 0 0 0 4.24 4.498c4.119 1.56 8.928-.66 9.968-4.578.07-.27.17-.37.47-.27.46.14.93.24 1.4.35a.724.724 0 0 0 .92-.64 1.44 1.44 0 0 0-.16-.73Z'/></svg>",
"folder-linux": "<svg viewBox='0 0 32 32'><path fill='#f9a825' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffecb3' d='M24.62 16.35c-.42.28-1.75 1.04-1.95 1.19a.825.825 0 0 1-1.14-.01c-.2-.16-1.53-.92-1.95-1.19-.48-.309-.45-.699.08-.919a6.16 6.16 0 0 1 4.91.03c.49.21.51.6.05.9Zm7.218 7.279A19.1 19.1 0 0 0 28 17.971a4.3 4.3 0 0 1-1.06-1.88c-.1-.33-.17-.67-.24-1.01a11.3 11.3 0 0 0-.7-2.609 4.06 4.06 0 0 0-3.839-2.47 4.2 4.2 0 0 0-3.95 2.4 6 6 0 0 0-.46 1.34c-.17.76-.32 1.55-.5 2.319a3.4 3.4 0 0 1-.959 1.71 19.5 19.5 0 0 0-3.88 5.348 6 6 0 0 0-.37.88c-.19.66.29 1.12.99.96.44-.09.88-.18 1.3-.31.41-.15.57-.05.67.35a6.73 6.73 0 0 0 4.24 4.498c4.119 1.56 8.928-.66 9.968-4.578.07-.27.17-.37.47-.27.46.14.93.24 1.4.35a.724.724 0 0 0 .92-.64 1.44 1.44 0 0 0-.16-.73Z'/></svg>",
"folder-liquibase-open": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M23 12c-3.865 0-6.998 1.343-6.998 3s3.133 3 6.999 3S30 16.657 30 15s-3.134-3-7-3m7 5c-.222.2-.438.417-.703.582-.84.466-1.767.724-2.7.957-1.64.41-3.07.673-5.645 1.363-1.232.33-3.29 1.07-4.304 2.494-.686.961-.644 2.116-.646 2.604.438-.29.91-.82 3.26-1.51a61 61 0 0 1 3.145-.777c.785-.175 1.57-.329 2.354-.516 2.853-.697 3.631-1.07 4.325-1.539.608-.413.916-.91.914-1.658 0 0-.024-1.342 0-2m0 4.453c-1.39.78-2.246 1.215-4.325 1.682-1.767.4-3.53.81-5.295 1.22-1.188.282-3.555.975-4.18 2.145-.286.536-.295 1.164.169 1.643.05.05.56.524.738.586 0 0 .218-.106.836-.393 1.16-.536 2.396-.858 3.64-1.162 1.824-.425 3.659-.818 5.448-1.383.82-.252 1.605-.582 2.26-1.13C29.755 24.271 30 24 30 23Zm0 4.147s-.505.507-2.014 1.076C24.213 27.979 22.03 28 19.027 29.15c-.15.058-.388.147-.532.211.124.05.717.236.711.229.96.243 2.365.416 3.795.41.534-.013 4.286.128 6.163-1.39.384-.34.833-.61.836-1.61Z'/></svg>",
@@ -435,8 +440,8 @@
"folder-lottie": "<svg viewBox='0 0 32 32'><path fill='#00bfa5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#a7ffeb' d='M28 10H18a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4V14a4 4 0 0 0-4-4m0 5.563a.48.48 0 0 1-.437.464c-1.541.201-2.457 1.49-3.715 3.503-1.233 1.971-2.619 4.19-5.323 4.446a.495.495 0 0 1-.525-.501v-1.038a.48.48 0 0 1 .437-.465c1.541-.2 2.457-1.489 3.715-3.502 1.233-1.971 2.619-4.19 5.323-4.446a.495.495 0 0 1 .525.501Z'/></svg>",
"folder-lua-open": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><circle cx='29' cy='13' r='3' fill='#b3e5fc'/><path fill='#b3e5fc' d='M19.703 14.594a7.703 7.703 0 1 0 7.703 7.703 7.703 7.703 0 0 0-7.703-7.703M21 24a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/></svg>",
"folder-lua": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><circle cx='29' cy='13' r='3' fill='#b3e5fc'/><path fill='#b3e5fc' d='M19.703 14.594a7.703 7.703 0 1 0 7.703 7.703 7.703 7.703 0 0 0-7.703-7.703M21 24a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/></svg>",
- "folder-luau-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#42A5F5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#B3E5FC' fill-rule='evenodd' d='M18.381 12 31 15.381 27.619 28 15 24.619zm8.095 3.86 2.524.676-.676 2.524-2.524-.677z' clip-rule='evenodd'/></svg>",
- "folder-luau": "<svg fill='none' viewBox='0 0 32 32'><path fill='#42A5F5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#B3E5FC' fill-rule='evenodd' d='M18.381 12 31 15.381 27.619 28 15 24.619zm8.095 3.86 2.524.676-.676 2.524-2.524-.677z' clip-rule='evenodd'/></svg>",
+ "folder-luau-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#42a5f5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' fill-rule='evenodd' d='M18.381 12 31 15.381 27.619 28 15 24.619zm8.095 3.86 2.524.676-.676 2.524-2.524-.677z' clip-rule='evenodd'/></svg>",
+ "folder-luau": "<svg fill='none' viewBox='0 0 32 32'><path fill='#42a5f5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' fill-rule='evenodd' d='M18.381 12 31 15.381 27.619 28 15 24.619zm8.095 3.86 2.524.676-.676 2.524-2.524-.677z' clip-rule='evenodd'/></svg>",
"folder-macos-open": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#cfd8dc' d='M30.64 27.499c-.82 1.24-1.687 2.45-3.008 2.47-1.322.03-1.746-.79-3.245-.79-1.508 0-1.972.77-3.224.82-1.292.05-2.268-1.32-3.096-2.53-1.687-2.47-2.979-7.02-1.243-10.08a4.81 4.81 0 0 1 4.063-2.51c1.262-.02 2.465.87 3.244.87.77 0 2.229-1.07 3.757-.91a4.56 4.56 0 0 1 3.59 1.98 4.57 4.57 0 0 0-2.12 3.81A4.41 4.41 0 0 0 32 24.67a11.1 11.1 0 0 1-1.36 2.83Zm-5.632-16a4.46 4.46 0 0 1 2.9-1.499 4.42 4.42 0 0 1-1.026 3.19 3.58 3.58 0 0 1-2.91 1.42 4.3 4.3 0 0 1 1.036-3.11Z'/></svg>",
"folder-macos": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#cfd8dc' d='M30.64 27.499c-.82 1.24-1.687 2.45-3.008 2.47-1.322.03-1.746-.79-3.245-.79-1.508 0-1.972.77-3.224.82-1.292.05-2.268-1.32-3.096-2.53-1.687-2.47-2.979-7.02-1.243-10.08a4.81 4.81 0 0 1 4.063-2.51c1.262-.02 2.465.87 3.244.87.77 0 2.229-1.07 3.757-.91a4.56 4.56 0 0 1 3.59 1.98 4.57 4.57 0 0 0-2.12 3.81A4.41 4.41 0 0 0 32 24.67a11.1 11.1 0 0 1-1.36 2.83Zm-5.632-16a4.46 4.46 0 0 1 2.9-1.499 4.42 4.42 0 0 1-1.026 3.19 3.58 3.58 0 0 1-2.91 1.42 4.3 4.3 0 0 1 1.036-3.11Z'/></svg>",
"folder-mail-open": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M14 16v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2H16a2 2 0 0 0-2 2m16 2-7 4-7-4v-2l7 4 7-4Z'/></svg>",
@@ -467,26 +472,26 @@
"folder-netlify": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#a7ffeb' d='M22 16h-4v6h2v-4h1.5a.5.5 0 0 1 .5.5V22h2v-4a2 2 0 0 0-2-2'/><rect width='6' height='2' x='26' y='18' fill='#a7ffeb' rx='.5'/><rect width='2' height='6' x='20' y='8' fill='#a7ffeb' rx='.5'/><rect width='6' height='2' x='10' y='18' fill='#a7ffeb' rx='.5'/><rect width='2' height='6' x='20' y='24' fill='#a7ffeb' rx='.5'/><path fill='#a7ffeb' d='m13 12.172 1.414-1.414 2.828 2.828L15.828 15zM15.828 23l1.414 1.414-2.828 2.828L13 25.828z'/></svg>",
"folder-next-open": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#cfd8dc' d='M24 12a8 8 0 1 0 3.969 14.94L22 19v4.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .5-.5h1.232a.5.5 0 0 1 .416.223l6.736 10.103A7.993 7.993 0 0 0 24 12m4 8h-2v-3.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5Z'/></svg>",
"folder-next": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#cfd8dc' d='M24 12a8 8 0 1 0 3.969 14.94L22 19v4.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .5-.5h1.232a.5.5 0 0 1 .416.223l6.736 10.103A7.993 7.993 0 0 0 24 12m4 8h-2v-3.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5Z'/></svg>",
- "folder-ngrx-actions-open.clone": "<svg viewBox='0 0 32 32'><path fill='#AB47BC' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#E1BEE7' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-actions.clone": "<svg viewBox='0 0 32 32'><path fill='#AB47BC' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#E1BEE7' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-effects-open.clone": "<svg viewBox='0 0 32 32'><path fill='#00BCD4' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#B2EBF2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-effects.clone": "<svg viewBox='0 0 32 32'><path fill='#00BCD4' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#B2EBF2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-entities-open.clone": "<svg viewBox='0 0 32 32'><path fill='#FBC02D' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#FFECB3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-entities.clone": "<svg viewBox='0 0 32 32'><path fill='#FBC02D' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#FFECB3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-reducer-open.clone": "<svg viewBox='0 0 32 32'><path fill='#EF5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#FFCDD2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-reducer.clone": "<svg viewBox='0 0 32 32'><path fill='#EF5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#FFCDD2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-selectors-open.clone": "<svg viewBox='0 0 32 32'><path fill='#FF6E40' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#FFCCBC' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-selectors.clone": "<svg viewBox='0 0 32 32'><path fill='#FF6E40' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#FFCCBC' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-state-open.clone": "<svg viewBox='0 0 32 32'><path fill='#9E9D24' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#F0F4C3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
- "folder-ngrx-state.clone": "<svg viewBox='0 0 32 32'><path fill='#9E9D24' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#F0F4C3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-actions-open.clone": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#e1bee7' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-actions.clone": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1bee7' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-effects-open.clone": "<svg viewBox='0 0 32 32'><path fill='#00bcd4' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b2ebf2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-effects.clone": "<svg viewBox='0 0 32 32'><path fill='#00bcd4' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b2ebf2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-entities-open.clone": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffecb3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-entities.clone": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffecb3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-reducer-open.clone": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-reducer.clone": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-selectors-open.clone": "<svg viewBox='0 0 32 32'><path fill='#ff6e40' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffccbc' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-selectors.clone": "<svg viewBox='0 0 32 32'><path fill='#ff6e40' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffccbc' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-state-open.clone": "<svg viewBox='0 0 32 32'><path fill='#9e9d24' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f0f4c3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
+ "folder-ngrx-state.clone": "<svg viewBox='0 0 32 32'><path fill='#9e9d24' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f0f4c3' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
"folder-ngrx-store-open": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#dcedc8' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
"folder-ngrx-store": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#dcedc8' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
"folder-node-open": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#dcedc8' d='m25 12-7 4.072v7.854L25 28l7-4.074v-7.854Z'/></svg>",
"folder-node": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#dcedc8' d='m25 12-7 4.072v7.854L25 28l7-4.074v-7.854Z'/></svg>",
"folder-nuxt-open": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#00e676' d='M22.498 27.998h6.927a1.56 1.56 0 0 0 1.127-.617 1.3 1.3 0 0 0 .188-.631 1.26 1.26 0 0 0-.188-.618l-4.685-8.053a1.14 1.14 0 0 0-.443-.443 1.5 1.5 0 0 0-.67-.188 1.29 1.29 0 0 0-1.074.63l-1.182 2.054-2.376-3.986a1.3 1.3 0 0 0-.43-.497 1.52 1.52 0 0 0-1.247 0 1.5 1.5 0 0 0-.51.497l-5.799 9.986a1.2 1.2 0 0 0-.134.618 1.24 1.24 0 0 0 .134.63 1.3 1.3 0 0 0 .497.43 1.3 1.3 0 0 0 .63.188h4.363a4.26 4.26 0 0 0 3.88-2.241l2.12-3.692 1.114-1.933 3.436 5.866h-4.564Zm-4.9-2h-3.04l4.533-7.8 2.28 3.893-1.52 2.667a2.34 2.34 0 0 1-2.267 1.24Z'/></svg>",
"folder-nuxt": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#00e676' d='M22.498 27.998h6.927a1.56 1.56 0 0 0 1.127-.617 1.3 1.3 0 0 0 .188-.631 1.26 1.26 0 0 0-.188-.618l-4.685-8.053a1.14 1.14 0 0 0-.443-.443 1.5 1.5 0 0 0-.67-.188 1.29 1.29 0 0 0-1.074.63l-1.182 2.054-2.376-3.986a1.3 1.3 0 0 0-.43-.497 1.52 1.52 0 0 0-1.247 0 1.5 1.5 0 0 0-.51.497l-5.799 9.986a1.2 1.2 0 0 0-.134.618 1.24 1.24 0 0 0 .134.63 1.3 1.3 0 0 0 .497.43 1.3 1.3 0 0 0 .63.188h4.363a4.26 4.26 0 0 0 3.88-2.241l2.12-3.692 1.114-1.933 3.436 5.866h-4.564Zm-4.9-2h-3.04l4.533-7.8 2.28 3.893-1.52 2.667a2.34 2.34 0 0 1-2.267 1.24Z'/></svg>",
- "folder-obsidian-open": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#673AB7' d='M463.47 192H151.06c-13.77 0-26 8.82-30.35 21.89L64 384V160h384c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42l-20.6-17.15c-5.75-4.8-13-7.43-20.48-7.43H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h352l76.88-179.39c1.7-3.98 2.59-8.28 2.59-12.61 0-17.67-14.33-32-32-32'/><g fill='#D1C4E9'><path d='M336.2 318.24c8.07-1.51 12.6-2.02 21.66-2.02-34.18-89.72 48.95-139.27 18.63-155.11-17-8.88-52.32 37.77-72.93 56.26l-10.67 37.41c19.77 16.2 36.25 39.63 43.31 63.46m75.04 128.91c13.05 3.85 26.66-5.92 28.52-19.42 1.35-9.81 3.51-20.65 8.24-30.94-2.66-7.51-25.72-71.18-104.74-56.39 7.6 31.4-4.15 64.54-22.83 91.02 33.14.31 59.29 6.45 90.81 15.73'/><path d='M478.76 346.86c7.02-12.43-16.61-22.28-28.74-50.78-10.52-24.69 4.93-53.82-8.18-66.23l-40.17-38.02c-14.09 38.27-40.29 56.91-17.12 123.38 37.13 6.98 67.48 27.2 77.55 58.42 0 0 13.67-21.49 16.66-26.77m-221.26 5.78c-8.21 18.67 17.96 36.81 43.46 63.29 29-40.73 24.17-88.95-15.12-127.91z'/></g></svg>",
- "folder-obsidian": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#673AB7' d='m221.5 120.58-20.6-17.16A32 32 0 0 0 180.42 96H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32V160c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42'/><g fill='#D1C4E9'><path d='M336.2 318.24c8.07-1.51 12.6-2.02 21.66-2.02-34.18-89.72 48.95-139.27 18.63-155.11-17-8.88-52.32 37.77-72.93 56.26l-10.67 37.41c19.77 16.2 36.25 39.63 43.31 63.46m75.04 128.91c13.05 3.85 26.66-5.92 28.52-19.42 1.35-9.81 3.51-20.65 8.24-30.94-2.66-7.51-25.72-71.18-104.74-56.39 7.6 31.4-4.15 64.54-22.83 91.02 33.14.31 59.29 6.45 90.81 15.73'/><path d='M478.76 346.86c7.02-12.43-16.61-22.28-28.74-50.78-10.52-24.69 4.93-53.82-8.18-66.23l-40.17-38.02c-14.09 38.27-40.29 56.91-17.12 123.38 37.13 6.98 67.48 27.2 77.55 58.42 0 0 13.67-21.49 16.66-26.77m-221.26 5.78c-8.21 18.67 17.96 36.81 43.46 63.29 29-40.73 24.17-88.95-15.12-127.91z'/></g></svg>",
+ "folder-obsidian-open": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#673ab7' d='M463.47 192H151.06c-13.77 0-26 8.82-30.35 21.89L64 384V160h384c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42l-20.6-17.15c-5.75-4.8-13-7.43-20.48-7.43H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h352l76.88-179.39c1.7-3.98 2.59-8.28 2.59-12.61 0-17.67-14.33-32-32-32'/><g fill='#d1c4e9'><path d='M336.2 318.24c8.07-1.51 12.6-2.02 21.66-2.02-34.18-89.72 48.95-139.27 18.63-155.11-17-8.88-52.32 37.77-72.93 56.26l-10.67 37.41c19.77 16.2 36.25 39.63 43.31 63.46m75.04 128.91c13.05 3.85 26.66-5.92 28.52-19.42 1.35-9.81 3.51-20.65 8.24-30.94-2.66-7.51-25.72-71.18-104.74-56.39 7.6 31.4-4.15 64.54-22.83 91.02 33.14.31 59.29 6.45 90.81 15.73'/><path d='M478.76 346.86c7.02-12.43-16.61-22.28-28.74-50.78-10.52-24.69 4.93-53.82-8.18-66.23l-40.17-38.02c-14.09 38.27-40.29 56.91-17.12 123.38 37.13 6.98 67.48 27.2 77.55 58.42 0 0 13.67-21.49 16.66-26.77m-221.26 5.78c-8.21 18.67 17.96 36.81 43.46 63.29 29-40.73 24.17-88.95-15.12-127.91z'/></g></svg>",
+ "folder-obsidian": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#673ab7' d='m221.5 120.58-20.6-17.16A32 32 0 0 0 180.42 96H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32V160c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42'/><g fill='#d1c4e9'><path d='M336.2 318.24c8.07-1.51 12.6-2.02 21.66-2.02-34.18-89.72 48.95-139.27 18.63-155.11-17-8.88-52.32 37.77-72.93 56.26l-10.67 37.41c19.77 16.2 36.25 39.63 43.31 63.46m75.04 128.91c13.05 3.85 26.66-5.92 28.52-19.42 1.35-9.81 3.51-20.65 8.24-30.94-2.66-7.51-25.72-71.18-104.74-56.39 7.6 31.4-4.15 64.54-22.83 91.02 33.14.31 59.29 6.45 90.81 15.73'/><path d='M478.76 346.86c7.02-12.43-16.61-22.28-28.74-50.78-10.52-24.69 4.93-53.82-8.18-66.23l-40.17-38.02c-14.09 38.27-40.29 56.91-17.12 123.38 37.13 6.98 67.48 27.2 77.55 58.42 0 0 13.67-21.49 16.66-26.77m-221.26 5.78c-8.21 18.67 17.96 36.81 43.46 63.29 29-40.73 24.17-88.95-15.12-127.91z'/></g></svg>",
"folder-open": "<svg viewBox='0 0 16 16'><path fill='#90a4ae' d='M14.483 6H4.721a1 1 0 0 0-.949.684L2 12V5h12a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232l-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h11l2.403-5.606A1 1 0 0 0 14.483 6'/></svg>",
"folder-other-open": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffccbc' d='M22 10a10 10 0 1 0 10 10 10 10 0 0 0-10-10m-6 12.125a2 2 0 1 1 2-2 2 2 0 0 1-2 2m6 0a2 2 0 1 1 2-2 2 2 0 0 1-2 2m6 0a2 2 0 1 1 2-2 2 2 0 0 1-2 2'/></svg>",
"folder-other": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffccbc' d='M22 10a10 10 0 1 0 10 10 10 10 0 0 0-10-10m-6 12.125a2 2 0 1 1 2-2 2 2 0 0 1-2 2m6 0a2 2 0 1 1 2-2 2 2 0 0 1-2 2m6 0a2 2 0 1 1 2-2 2 2 0 0 1-2 2'/></svg>",
@@ -522,20 +527,22 @@
"folder-public": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M22 10a10 10 0 1 0 10 10 10 10 0 0 0-10-10m6.918 6H25.96a15.8 15.8 0 0 0-1.342-3.54 8.04 8.04 0 0 1 4.3 3.54M22 12a14.1 14.1 0 0 1 1.89 4h-3.78A14.1 14.1 0 0 1 22 12m-2.618.46A15.8 15.8 0 0 0 18.04 16h-2.958a8.04 8.04 0 0 1 4.3-3.54M14.263 22a7.7 7.7 0 0 1 0-4h3.407a15.5 15.5 0 0 0 0 4Zm.82 2h2.957a15.8 15.8 0 0 0 1.342 3.54 8.04 8.04 0 0 1-4.3-3.54ZM22 28a14.1 14.1 0 0 1-1.89-4h3.78A14.1 14.1 0 0 1 22 28m2.31-6h-4.62a13.4 13.4 0 0 1 0-4h4.62a13.4 13.4 0 0 1 0 4m.308 5.54A15.8 15.8 0 0 0 25.96 24h2.958a8.04 8.04 0 0 1-4.3 3.54M29.737 22H26.33a15.5 15.5 0 0 0 0-4h3.407a7.7 7.7 0 0 1 0 4'/></svg>",
"folder-python-open": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#0277bd' d='M21.123 10a2.574 2.574 0 0 0-2.574 2.574v1.512h3.86c.352 0 .64.513.64.864h-6.426a2.574 2.574 0 0 0-2.574 2.574v3.404A2.57 2.57 0 0 0 16.62 23.5h1.065v-2.412a2.565 2.565 0 0 1 2.556-2.574h4.734a2.565 2.565 0 0 0 2.574-2.556v-3.384A2.574 2.574 0 0 0 24.975 10zm-.648 1.449c.36 0 .648.109.648.64s-.288.8-.648.8c-.351 0-.64-.27-.64-.8s.289-.64.64-.64'/><path fill='#fdd835' d='M28.412 14.5v2.412a2.565 2.565 0 0 1-2.556 2.574h-4.733a2.565 2.565 0 0 0-2.574 2.556v3.382A2.574 2.574 0 0 0 21.12 28h3.854a2.574 2.574 0 0 0 2.574-2.574v-1.513h-3.862c-.351 0-.638-.512-.638-.863h6.426a2.574 2.574 0 0 0 2.574-2.574v-3.403a2.574 2.574 0 0 0-2.574-2.573Zm-8.675 4.063-.004.003q.017-.003.034-.003Zm5.886 6.547c.35 0 .639.27.639.801a.64.64 0 0 1-.64.64c-.36 0-.647-.109-.647-.64s.288-.8.648-.8Z'/></svg>",
"folder-python": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#0277bd' d='M21.123 10a2.574 2.574 0 0 0-2.574 2.574v1.512h3.86c.352 0 .64.513.64.864h-6.426a2.574 2.574 0 0 0-2.574 2.574v3.404A2.57 2.57 0 0 0 16.62 23.5h1.065v-2.412a2.565 2.565 0 0 1 2.556-2.574h4.734a2.565 2.565 0 0 0 2.574-2.556v-3.384A2.574 2.574 0 0 0 24.975 10zm-.648 1.449c.36 0 .648.109.648.64s-.288.8-.648.8c-.351 0-.64-.27-.64-.8s.289-.64.64-.64'/><path fill='#fdd835' d='M28.412 14.5v2.412a2.565 2.565 0 0 1-2.556 2.574h-4.733a2.565 2.565 0 0 0-2.574 2.556v3.382A2.574 2.574 0 0 0 21.12 28h3.854a2.574 2.574 0 0 0 2.574-2.574v-1.513h-3.862c-.351 0-.638-.512-.638-.863h6.426a2.574 2.574 0 0 0 2.574-2.574v-3.403a2.574 2.574 0 0 0-2.574-2.573Zm-8.675 4.063-.004.003q.017-.003.034-.003Zm5.886 6.547c.35 0 .639.27.639.801a.64.64 0 0 1-.64.64c-.36 0-.647-.109-.647-.64s.288-.8.648-.8Z'/></svg>",
+ "folder-pytorch-open": "<svg viewBox='0 0 32 32'><path fill='#f4511e' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffab91' d='M23.334 14.011a1.334 1.334 0 0 1 1.333-1.335 1.335 1.335 0 1 1-1.333 1.335m6.497 5.29a7.5 7.5 0 0 0-1.449-3.066 8 8 0 0 0-.734-.814l-1.883 1.892a6 6 0 0 1 .498.543 4.9 4.9 0 0 1 .961 2.01 5 5 0 0 1 .11.822 5.334 5.334 0 0 1-10.667.016v-.016a3.86 3.86 0 0 1 .463-1.845 5 5 0 0 1 .852-1.267c.12-.12 1.209-1.166 2.493-2.394l1.537-1.473L22 10l-3.372 3.255a337 337 0 0 0-2.584 2.486 7.7 7.7 0 0 0-1.327 1.965A6.54 6.54 0 0 0 14 20.688a8 8 0 1 0 16-.02 8 8 0 0 0-.17-1.366'/></svg>",
+ "folder-pytorch": "<svg viewBox='0 0 32 32'><path fill='#f4511e' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffab91' d='M23.334 14.011a1.334 1.334 0 0 1 1.333-1.335 1.335 1.335 0 1 1-1.333 1.335m6.497 5.29a7.5 7.5 0 0 0-1.449-3.066 8 8 0 0 0-.734-.814l-1.883 1.892a6 6 0 0 1 .498.543 4.9 4.9 0 0 1 .961 2.01 5 5 0 0 1 .11.822 5.334 5.334 0 0 1-10.667.016v-.016a3.86 3.86 0 0 1 .463-1.845 5 5 0 0 1 .852-1.267c.12-.12 1.209-1.166 2.493-2.394l1.537-1.473L22 10l-3.372 3.255a337 337 0 0 0-2.584 2.486 7.7 7.7 0 0 0-1.327 1.965A6.54 6.54 0 0 0 14 20.688a8 8 0 1 0 16-.02 8 8 0 0 0-.17-1.366'/></svg>",
"folder-quasar-open": "<svg viewBox='0 0 32 32'><path fill='#1976d2' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#bbdefb' d='M24.026 20A2.028 2.028 0 1 1 22 18.048 1.99 1.99 0 0 1 24.026 20m6.967-5.002a10 10 0 0 0-1.59-2.002L27.06 14.3a7.9 7.9 0 0 0-2.445-1.365 9.3 9.3 0 0 0-1.893 2.6 11.74 11.74 0 0 1 7.8 2.618l1.473-.819A9.8 9.8 0 0 0 30.993 15Zm0 10.002A9.8 9.8 0 0 0 32 22.67l-2.342-1.303a7.2 7.2 0 0 0 .005-2.72 10 10 0 0 0-3.285-.278 10.7 10.7 0 0 1 1.545 7.812l1.473.82A10 10 0 0 0 30.993 25m-8.992 5a10.8 10.8 0 0 0 2.597-.326v-2.603a7.9 7.9 0 0 0 2.451-1.357 9.1 9.1 0 0 0-1.392-2.88 11.36 11.36 0 0 1-6.255 5.196v1.64a10.8 10.8 0 0 0 2.599.33m-8.994-5a10 10 0 0 0 1.592 2.004L16.94 25.7a7.8 7.8 0 0 0 2.447 1.365 9.3 9.3 0 0 0 1.891-2.6 11.75 11.75 0 0 1-7.8-2.618l-1.471.819a9.8 9.8 0 0 0 1 2.333Zm0-10A9.8 9.8 0 0 0 12 17.33l2.343 1.303a7.2 7.2 0 0 0-.005 2.72 10 10 0 0 0 3.286.278 10.7 10.7 0 0 1-1.545-7.814l-1.475-.82a10 10 0 0 0-1.597 2.005Zm8.992-5a10.8 10.8 0 0 0-2.597.326v2.603a7.9 7.9 0 0 0-2.45 1.357 9.1 9.1 0 0 0 1.393 2.88A11.35 11.35 0 0 1 24.6 11.97v-1.64A10.8 10.8 0 0 0 22 10Z'/></svg>",
"folder-quasar": "<svg viewBox='0 0 32 32'><path fill='#1976d2' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M24.026 20A2.028 2.028 0 1 1 22 18.048 1.99 1.99 0 0 1 24.026 20m6.967-5.002a10 10 0 0 0-1.59-2.002L27.06 14.3a7.9 7.9 0 0 0-2.445-1.365 9.3 9.3 0 0 0-1.893 2.6 11.74 11.74 0 0 1 7.8 2.618l1.473-.819A9.8 9.8 0 0 0 30.993 15Zm0 10.002A9.8 9.8 0 0 0 32 22.67l-2.342-1.303a7.2 7.2 0 0 0 .005-2.72 10 10 0 0 0-3.285-.278 10.7 10.7 0 0 1 1.545 7.812l1.473.82A10 10 0 0 0 30.993 25m-8.992 5a10.8 10.8 0 0 0 2.597-.326v-2.603a7.9 7.9 0 0 0 2.451-1.357 9.1 9.1 0 0 0-1.392-2.88 11.36 11.36 0 0 1-6.255 5.196v1.64a10.8 10.8 0 0 0 2.599.33m-8.994-5a10 10 0 0 0 1.592 2.004L16.94 25.7a7.8 7.8 0 0 0 2.447 1.365 9.3 9.3 0 0 0 1.891-2.6 11.75 11.75 0 0 1-7.8-2.618l-1.471.819a9.8 9.8 0 0 0 1 2.333Zm0-10A9.8 9.8 0 0 0 12 17.33l2.343 1.303a7.2 7.2 0 0 0-.005 2.72 10 10 0 0 0 3.286.278 10.7 10.7 0 0 1-1.545-7.814l-1.475-.82a10 10 0 0 0-1.597 2.005Zm8.992-5a10.8 10.8 0 0 0-2.597.326v2.603a7.9 7.9 0 0 0-2.45 1.357 9.1 9.1 0 0 0 1.393 2.88A11.35 11.35 0 0 1 24.6 11.97v-1.64A10.8 10.8 0 0 0 22 10Z'/></svg>",
"folder-queue-open": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M24 16v-2h-3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3v-2h-2v-8Zm8-2v-2h-5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h5v-2h-4V14Zm-16 2h2v8h-2z'/></svg>",
"folder-queue": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M24 16v-2h-3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3v-2h-2v-8Zm8-2v-2h-5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h5v-2h-4V14Zm-16 2h2v8h-2z'/></svg>",
"folder-react-components-open": "<svg viewBox='0 0 16 16'><path fill='#00bcd4' d='M14.484 6H4.72a1 1 0 0 0-.949.684L2 12V5h13a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232l-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h11l2.403-5.606A1 1 0 0 0 14.483 6'/><g fill='#b2ebf2'><path d='M10.5 8.399c2.924 0 4.714 1.037 4.714 1.6s-1.79 1.602-4.714 1.602S5.785 10.564 5.785 10s1.79-1.601 4.715-1.601m0-.8c-3.038 0-5.5 1.075-5.5 2.4s2.462 2.402 5.5 2.402S16 11.326 16 10s-2.463-2.401-5.5-2.401'/><path d='M10.5 9.2a.786.8 0 1 0 .785.8.786.8 0 0 0-.785-.8'/><path d='M8.322 5.8c.793 0 2.333 1.272 3.538 3.4 1.463 2.58 1.476 4.677.997 4.959a.354.36 0 0 1-.18.04c-.792 0-2.333-1.271-3.538-3.399-1.463-2.58-1.476-4.677-.997-4.96a.354.36 0 0 1 .18-.04m0-.8a1.128 1.149 0 0 0-.572.147c-1.128.663-.81 3.374.708 6.054C9.748 13.478 11.491 15 12.678 15a1.128 1.149 0 0 0 .572-.148c1.127-.663.81-3.373-.71-6.053C11.25 6.522 9.509 5 8.323 5Z'/><path d='M12.677 5.8a.354.36 0 0 1 .18.04c.48.283.466 2.38-.997 4.96-1.206 2.128-2.746 3.4-3.538 3.4a.354.36 0 0 1-.18-.04c-.48-.284-.466-2.38.997-4.96 1.206-2.128 2.746-3.4 3.538-3.4m0-.8c-1.186 0-2.929 1.522-4.22 3.8-1.517 2.68-1.835 5.39-.707 6.052a1.128 1.149 0 0 0 .572.148c1.186 0 2.929-1.523 4.22-3.8 1.517-2.68 1.835-5.39.708-6.052A1.128 1.149 0 0 0 12.677 5'/></g></svg>",
"folder-react-components": "<svg viewBox='0 0 16 16'><path fill='#00bcd4' d='m6.922 3.768-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232'/><g fill='#b2ebf2'><path d='M10.5 8.399c2.924 0 4.714 1.037 4.714 1.6s-1.79 1.602-4.714 1.602S5.785 10.564 5.785 10s1.79-1.601 4.715-1.601m0-.8c-3.038 0-5.5 1.075-5.5 2.4s2.462 2.402 5.5 2.402S16 11.326 16 10s-2.463-2.401-5.5-2.401'/><path d='M10.5 9.2a.786.8 0 1 0 .785.8.786.8 0 0 0-.785-.8'/><path d='M8.322 5.8c.793 0 2.333 1.272 3.538 3.4 1.463 2.58 1.476 4.677.997 4.959a.354.36 0 0 1-.18.04c-.792 0-2.333-1.271-3.538-3.399-1.463-2.58-1.476-4.677-.997-4.96a.354.36 0 0 1 .18-.04m0-.8a1.128 1.149 0 0 0-.572.147c-1.128.663-.81 3.374.708 6.054C9.748 13.478 11.491 15 12.678 15a1.128 1.149 0 0 0 .572-.148c1.127-.663.81-3.373-.71-6.053C11.25 6.522 9.509 5 8.323 5Z'/><path d='M12.677 5.8a.354.36 0 0 1 .18.04c.48.283.466 2.38-.997 4.96-1.206 2.128-2.746 3.4-3.538 3.4a.354.36 0 0 1-.18-.04c-.48-.284-.466-2.38.997-4.96 1.206-2.128 2.746-3.4 3.538-3.4m0-.8c-1.186 0-2.929 1.522-4.22 3.8-1.517 2.68-1.835 5.39-.707 6.052a1.128 1.149 0 0 0 .572.148c1.186 0 2.929-1.523 4.22-3.8 1.517-2.68 1.835-5.39.708-6.052A1.128 1.149 0 0 0 12.677 5'/></g></svg>",
- "folder-redux-actions-open.clone": "<svg viewBox='0 0 32 32'><path fill='#AB47BC' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#F3E5F5' stroke='#F3E5F5' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#F3E5F5' stroke='#F3E5F5' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#F3E5F5' stroke='#F3E5F5' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
- "folder-redux-actions.clone": "<svg viewBox='0 0 32 32'><path fill='#AB47BC' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#F3E5F5' stroke='#F3E5F5' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#F3E5F5' stroke='#F3E5F5' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#F3E5F5' stroke='#F3E5F5' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
+ "folder-redux-actions-open.clone": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
+ "folder-redux-actions.clone": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
"folder-redux-reducer-open": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' stroke='#ffcdd2' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#ffcdd2' stroke='#ffcdd2' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#ffcdd2' stroke='#ffcdd2' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
"folder-redux-reducer": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' stroke='#ffcdd2' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#ffcdd2' stroke='#ffcdd2' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#ffcdd2' stroke='#ffcdd2' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
- "folder-redux-selector-open.clone": "<svg viewBox='0 0 32 32'><path fill='#FF6E40' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#FFCCBC' stroke='#FFCCBC' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#FFCCBC' stroke='#FFCCBC' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#FFCCBC' stroke='#FFCCBC' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
- "folder-redux-selector.clone": "<svg viewBox='0 0 32 32'><path fill='#FF6E40' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#FFCCBC' stroke='#FFCCBC' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#FFCCBC' stroke='#FFCCBC' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#FFCCBC' stroke='#FFCCBC' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
- "folder-redux-store-open.clone": "<svg viewBox='0 0 32 32'><path fill='#8BC34A' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#DCEDC8' stroke='#DCEDC8' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#DCEDC8' stroke='#DCEDC8' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#DCEDC8' stroke='#DCEDC8' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
- "folder-redux-store.clone": "<svg viewBox='0 0 32 32'><path fill='#8BC34A' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#DCEDC8' stroke='#DCEDC8' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#DCEDC8' stroke='#DCEDC8' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#DCEDC8' stroke='#DCEDC8' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
+ "folder-redux-selector-open.clone": "<svg viewBox='0 0 32 32'><path fill='#ff6e40' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffccbc' stroke='#ffccbc' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#ffccbc' stroke='#ffccbc' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#ffccbc' stroke='#ffccbc' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
+ "folder-redux-selector.clone": "<svg viewBox='0 0 32 32'><path fill='#ff6e40' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffccbc' stroke='#ffccbc' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#ffccbc' stroke='#ffccbc' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#ffccbc' stroke='#ffccbc' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
+ "folder-redux-store-open.clone": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#dcedc8' stroke='#dcedc8' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#dcedc8' stroke='#dcedc8' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#dcedc8' stroke='#dcedc8' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
+ "folder-redux-store.clone": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#dcedc8' stroke='#dcedc8' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#dcedc8' stroke='#dcedc8' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#dcedc8' stroke='#dcedc8' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
"folder-repository-open": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#c8e6c9' d='M20 10a2 2 0 0 0-1.6.8l-1.6 2.134a4 4 0 0 0-.8 2.398V26a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V15.332a4 4 0 0 0-.8-2.398L29.6 10.8A2 2 0 0 0 28 10zm0 2h8l1.5 2h-11zm2 4h4v4h4l-6 6-6-6h4z'/></svg>",
"folder-repository": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#c8e6c9' d='M20 10a2 2 0 0 0-1.6.8l-1.6 2.134a4 4 0 0 0-.8 2.398V26a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V15.332a4 4 0 0 0-.8-2.398L29.6 10.8A2 2 0 0 0 28 10zm0 2h8l1.5 2h-11zm2 4h4v4h4l-6 6-6-6h4z'/></svg>",
"folder-resolver-open": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#c8e6c9' d='M29.216 14 20.6 22.159l-3.816-3.614L14 21.183 20.6 28 32 17.205Z'/></svg>",
@@ -552,8 +559,8 @@
"folder-routes": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#c8e6c9' d='M17.414 14.586 20 12h-8v8l2.586-2.586 4.91 4.91A1.7 1.7 0 0 1 20 23.541V28h4v-4.459a5.68 5.68 0 0 0-1.676-4.045ZM29.36 12l-5.61 4.93.57.57a5.6 5.6 0 0 1 1.56 2.89L32 15.01Z'/></svg>",
"folder-rules-open": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M30 14h-2a3 3 0 0 0-6 0h-2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2m-5-1a1 1 0 1 1-1 1 1.003 1.003 0 0 1 1-1m-1.547 11.597-3.093-3.093 1.09-1.09 2.003 1.995 5.096-5.096 1.091 1.099Z'/></svg>",
"folder-rules": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M30 14h-2a3 3 0 0 0-6 0h-2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V16a2 2 0 0 0-2-2m-5-1a1 1 0 1 1-1 1 1.003 1.003 0 0 1 1-1m-1.547 11.597-3.093-3.093 1.09-1.09 2.003 1.995 5.096-5.096 1.091 1.099Z'/></svg>",
- "folder-rust-open": "<svg viewBox='0 0 32 32'><path fill='#FF7043' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>",
- "folder-rust": "<svg viewBox='0 0 32 32'><path fill='#FF7043' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>",
+ "folder-rust-open": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>",
+ "folder-rust": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffccbc' d='M30 24v1a1 1 0 0 1-2 0v-1a2 2 0 0 0-2-2 3 3 0 0 0 2.996-3.16A3.115 3.114 0 0 0 25.83 16H14v2h2v8h-2v2h8v-2h-2v-2h4c.82.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63H32v-4zm-6-4h-4v-2h4a1 1 0 0 1 0 2'/></svg>",
"folder-sandbox-open": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='M28.965 12.001H9.44a2 2 0 0 0-1.898 1.368L3.998 24.001v-14h24a2 2 0 0 0-2-2H15.122a2 2 0 0 1-1.28-.464l-1.288-1.072a2 2 0 0 0-1.28-.464H3.998a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212a2 2 0 0 0-1.838-2.788'/><path fill='#bbdefb' d='M21 20h-6a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1m7-8a4 4 0 1 0 4 4 4 4 0 0 0-4-4m.707 17.707 3-3a1 1 0 0 0 0-1.414l-3-3a1 1 0 0 0-1.414 0l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 0 0 1.414 0m-11.581-7.193-2.999 6A1 1 0 0 0 15.001 30H21a1 1 0 0 0 .874-1.486l-2.999-6a1 1 0 0 0-1.748 0'/></svg>",
"folder-sandbox": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M21 20h-6a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1m7-8a4 4 0 1 0 4 4 4 4 0 0 0-4-4m.707 17.707 3-3a1 1 0 0 0 0-1.414l-3-3a1 1 0 0 0-1.414 0l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 0 0 1.414 0m-11.581-7.193-2.999 6A1 1 0 0 0 15.001 30H21a1 1 0 0 0 .874-1.486l-2.999-6a1 1 0 0 0-1.748 0'/></svg>",
"folder-sass-open": "<svg viewBox='0 0 32 32'><path fill='#f06292' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fce4ec' d='M31.897 12.592a3 3 0 0 0-1.53-1.912 7.95 7.95 0 0 0-6.348-.05 17.4 17.4 0 0 0-5.864 3.557c-1.83 1.81-2.288 3.496-2.124 4.39.346 1.89 2.181 3.227 3.658 4.3.314.23.618.45.876.657-.92.513-2.916 1.749-3.483 3.074a2.9 2.9 0 0 0-.074 2.347 1.57 1.57 0 0 0 .874.903 3.5 3.5 0 0 0 .986.142 4.14 4.14 0 0 0 3.438-2.025 5.03 5.03 0 0 0 .55-3.886 4.5 4.5 0 0 1 1.46-.034 2.64 2.64 0 0 1 1.927.96 1.44 1.44 0 0 1 .304.968 1.2 1.2 0 0 1-.55.805c-.159.104-.356.233-.31.504.028.151.13.393.532.313a1.99 1.99 0 0 0 1.392-1.841 2.9 2.9 0 0 0-.801-2.051 3.9 3.9 0 0 0-2.897-1.135 6.5 6.5 0 0 0-1.813.226 13 13 0 0 0-1.498-1.346c-1.165-.947-2.265-1.842-2.2-3.125.085-1.654 1.672-3.306 4.716-4.909 2.7-1.422 4.894-1.47 6.04-1.041a1.44 1.44 0 0 1 .858.674 2.23 2.23 0 0 1-.257 1.866 6.57 6.57 0 0 1-5.023 3.105 2.56 2.56 0 0 1-2.225-.565c-.189-.219-.37-.423-.65-.263-.332.196-.175.625-.123.768a2.6 2.6 0 0 0 1.578 1.342 7.32 7.32 0 0 0 4.752-.482c2.631-1.078 4.384-3.933 3.83-6.236ZM21.301 26.118a3 3 0 0 1-.13.345 3.4 3.4 0 0 1-.517.795c-.648.743-1.499.978-1.776.808a.27.27 0 0 1-.088-.187 2.5 2.5 0 0 1 .742-1.704 7.8 7.8 0 0 1 1.865-1.445 3.05 3.05 0 0 1-.096 1.388'/></svg>",
@@ -567,7 +574,7 @@
"folder-secure-open": "<svg viewBox='0 0 32 32'><path fill='#f9a825' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fff9c4' d='M28 16v-3.828a4.116 4.116 0 0 0-3.607-4.153A4 4 0 0 0 20 12v4h-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2Zm-4 8a2 2 0 1 1 2-2 2 2 0 0 1-2 2m2-8h-4v-4a2 2 0 0 1 4 0Z'/></svg>",
"folder-secure": "<svg viewBox='0 0 32 32'><path fill='#f9a825' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fff9c4' d='M28 16v-3.828a4.116 4.116 0 0 0-3.607-4.153A4 4 0 0 0 20 12v4h-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2Zm-4 8a2 2 0 1 1 2-2 2 2 0 0 1-2 2m2-8h-4v-4a2 2 0 0 1 4 0Z'/></svg>",
"folder-seeders-open": "<svg clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 3200 3200'><path fill='#43a047' d='M2340.5 1199.5c-18.05 5.55-36.72 11.21-56 17a565 565 0 0 0-62 21q-441.75 165.75-603 609c-2.04 4.44-3.7 9.11-5 14-1.4 3.2-2.4 6.53-3 10-3.64 7.59-6.31 15.59-8 24-2.11 4-3.44 8.34-4 13-.61.89-.94 1.89-1 3-1.78 3-2.78 6.34-3 10-.67 2.67-1.33 5.33-2 8-1.57 1.6-2.24 3.6-2 6-.92 2.92-1.58 5.92-2 9-1.9 2.2-2.9 4.86-3 8-.33 2-.67 4-1 6-1.42 2.01-2.09 4.35-2 7v1c-.62.11-1.12.44-1.5 1a103 103 0 0 0-3.5 17c-21.96 92.4-35.63 186.07-41 281a821 821 0 0 0-4 73v2c-1.16 46.16-1.33 92.5-.5 139 .89 16.16 1.73 32.16 2.5 48v11c.18 7.19.85 14.19 2 21 .47 13.68 1.13 27.34 2 41-386.33.17-772.67 0-1159-.5q-122.657-16.17-169.5-130.5a230.2 230.2 0 0 1-11-49c-.667-546.67-.667-1093.3 0-1640 9.834-76.145 49.334-130.64 118.5-163.5q29.688-12.892 62-16 387.03-1.49 774 1c40.68 5.845 76.34 22.178 107 49 45.37 38.377 91.04 76.377 137 114 33.84 22.572 71.17 34.238 112 35l1108 1q101.4 12.037 154.5 98.5c17.74 31.305 26.58 64.972 26.5 101h-2400c-.167 465.67 0 931.33.5 1397l357-1071c28.15-65.48 76.984-106.31 146.5-122.5a307 307 0 0 1 20-3c472.33-.5 944.67-.67 1417-.5' opacity='.999'/><path fill='#a5d6a7' d='M3071.5 1301.5c-1.15 5.48-1.32 11.15-.5 17 .28.92.78 1.58 1.5 2v4c-.17 6.34 0 12.68.5 19q.345 1.86 1.5 3v5c-.32 9.22.34 18.22 2 27v10c.15 12.67.49 25.34 1 38-.17 19 0 38 .5 57 .28-.92.78-1.58 1.5-2q.585 196.035-43.5 387c-27.87 114.37-71.2 222.37-130 324-21.76 34.53-44.76 68.2-69 101a1351 1351 0 0 1-82.5 92.5c-16.67 15-33.33 30-50 45a2628 2628 0 0 1-49 38.5c-73.84 51.95-153.18 95.28-238 130-60.65 24.38-122.99 43.88-187 58.5-113.01 24.48-227.35 38.15-343 41-68.76 1.79-137.42.13-206-5-45.06-3.26-90.06-7.1-135-11.5-3.11-27.6-5.45-55.26-7-83-.87-13.66-1.53-27.32-2-41 .24-7.23-.43-14.23-2-21v-11c-1.58-62.32-2.25-124.66-2-187v-2c.82-2.47 1.32-5.14 1.5-8 1.01-21.66 1.84-43.33 2.5-65 5.37-94.93 19.04-188.6 41-281 1.96-5.81 3.62-11.81 5-18v-1c1.42-2.01 2.09-4.35 2-7 .33-2 .67-4 1-6 1.6-2.35 2.6-5.01 3-8 .42-3.08 1.08-6.08 2-9 1.57-1.6 2.24-3.6 2-6 .67-2.67 1.33-5.33 2-8 1.78-3 2.78-6.34 3-10 .06-1.11.39-2.11 1-3 2.11-4 3.44-8.34 4-13a169 169 0 0 0 8-24c.6-3.47 1.6-6.8 3-10 2.82-4.13 4.49-8.8 5-14q161.25-443.25 603-609c20.86-6.62 41.53-13.62 62-21 19.28-5.79 37.95-11.45 56-17 29.89-8.48 60.22-15.65 91-21.5 191.75-33.99 384.08-37.66 577-11 14.64 2.55 29.3 4.88 44 7 1.2.9 2.03 2.07 2.5 3.5 6.67 41.19 12.17 82.52 16.5 124'/><path fill='#43a047' d='M3071.5 1303.5c19.44 34.7 27.61 72.03 24.5 112-1.77 22.43-7.27 43.77-16.5 64 .23-17.69-.1-35.35-1-53 .06-12.86-.61-25.53-2-38v-10c-.22-9.18-.88-18.18-2-27v-5c-.13-7.53-.79-14.86-2-22v-4c-.26-5.67-.59-11.34-1-17m-398 192h1c17.51-.33 34.84 0 52 1-1.33.67-2.67 1.33-4 2-3.65.77-6.98 2.1-10 4a950 950 0 0 0-105 50.5c-121.59 68.07-232.26 150.91-332 248.5a178.3 178.3 0 0 0-23 22c-96.64 96.09-181.81 201.09-255.5 315a2166 2166 0 0 0-119.5 210c-1.41.47-2.07 1.47-2 3v1q-1.17-19.245 0-39v-2c1.45-5.77 2.11-11.77 2-18 .24-7.06.91-14.06 2-21 1.75-6.43 2.75-13.09 3-20v-1c2.07-8.76 3.4-17.76 4-27v-2c1.72-3.34 2.38-7.01 2-11 2.57-6.3 3.9-12.97 4-20 .03-4.14.7-8.14 2-12 1.42-2.01 2.09-4.35 2-7v-2c2.54-3.85 3.54-8.19 3-13 17.15-76.63 42.32-150.3 75.5-221 51.76-109.33 124.93-201.83 219.5-277.5 91.03-69.67 193.03-116.5 306-140.5 4.81.54 9.15-.46 13-3 9.03-.41 17.7-2.07 26-5 8.58-.59 16.91-1.92 25-4 6.91-.25 13.57-1.25 20-3 6.26-1.24 12.59-1.91 19-2 14.8-1.38 29.47-2.72 44-4h2c8.18-.28 16.18-.94 24-2'/></svg>",
- "folder-seeders": "<svg clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 3200 3200'><path fill='#43a047' d='M2999.5 1165.5c-30.43-3.18-61.1-6.35-92-9.5-167.75-14.9-334.08-5.57-499 28a1455 1455 0 0 0-207 61c-79.51 31.74-154.18 71.74-224 120a896 896 0 0 0-55 43c-1 .33-2 .67-3 1-46.92 39.92-90.09 83.42-129.5 130.5a1986 1986 0 0 1-45 60c-61.77 91.89-109.44 190.89-143 297a1660.6 1660.6 0 0 0-53 246c-14.98 118.89-19.98 238.22-15 358 1.34 33.03 3.17 66.03 5.5 99-386.33.17-772.67 0-1159-.5q-122.657-16.17-169.5-130.5a230.2 230.2 0 0 1-11-49c-.667-546.67-.667-1093.3 0-1640 9.834-76.145 49.334-130.64 118.5-163.5q29.688-12.892 62-16c255.33-.667 510.67-.667 766 0 43.68 4.875 82.01 21.541 115 50 45.37 38.377 91.04 76.377 137 114 28.54 18.847 59.87 30.181 94 34q662.985 1.986 1326 2c77.31 10.044 132.48 50.211 165.5 120.5a220.4 220.4 0 0 1 15 60c.5 61.67.67 123.33.5 185'/><path fill='#A5D6A7' d='M2999.5 1165.5c17.69 2.74 35.36 5.91 53 9.5 1.08.67 1.75 1.67 2 3 28.18 168.19 32.68 337.02 13.5 506.5-11.97 104.19-34.8 205.86-68.5 305-33.03 92.41-76.87 179.07-131.5 260a1694 1694 0 0 1-77 97 2240 2240 0 0 1-80.5 78.5c-46.48 38.13-95.48 72.8-147 104a1457 1457 0 0 1-145 70.5c-24.7 9.85-49.7 19.01-75 27.5-149 44.89-301.33 68.89-457 72-64.06 1.52-128.06.19-192-4-49.39-3.53-98.73-7.7-148-12.5a1745 1745 0 0 1-7-83 3304 3304 0 0 1-5.5-99c-4.98-119.78.02-239.11 15-358a1660.6 1660.6 0 0 1 53-246c33.56-106.11 81.23-205.11 143-297q23.07-29.58 45-60c39.41-47.08 82.58-90.58 129.5-130.5 1-.33 2-.67 3-1a896 896 0 0 1 55-43c69.82-48.26 144.49-88.26 224-120a1455 1455 0 0 1 207-61c164.92-33.57 331.25-42.9 499-28 30.9 3.15 61.57 6.32 92 9.5' opacity='.999'/><path fill='#43a047' d='M2709.5 1494.5c7.67-.17 15.34 0 23 .5a825 825 0 0 0-130 61c-97.27 54.78-187.6 119.12-271 193-15.7 14.7-31.7 29.04-48 43a4541 4541 0 0 0-111.5 115.5 2730 2730 0 0 1-57 68c-93.1 116.73-172.93 242.4-239.5 377-.5 1.02-1.17 1.19-2 .5 6.63-168.23 50.47-326.06 131.5-473.5 116.36-197.54 287.53-319.37 513.5-365.5 24.5-4.98 49.17-8.98 74-12q58.635-5.25 117-7.5'/><path fill='#43a047' d='M2999.5 1989.5c.17 143 0 286-.5 429-8.62 70.79-43.78 123.62-105.5 158.5q-31.365 15.615-66 21c-136.31 1.48-272.65 1.98-409 1.5 49.63-20.82 97.96-44.32 145-70.5 51.52-31.2 100.52-65.87 147-104a2240 2240 0 0 0 80.5-78.5c26.87-31.42 52.54-63.76 77-97 54.63-80.93 98.47-167.59 131.5-260' opacity='.999'/></svg>",
+ "folder-seeders": "<svg clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 3200 3200'><path fill='#43a047' d='M2999.5 1165.5c-30.43-3.18-61.1-6.35-92-9.5-167.75-14.9-334.08-5.57-499 28a1455 1455 0 0 0-207 61c-79.51 31.74-154.18 71.74-224 120a896 896 0 0 0-55 43c-1 .33-2 .67-3 1-46.92 39.92-90.09 83.42-129.5 130.5a1986 1986 0 0 1-45 60c-61.77 91.89-109.44 190.89-143 297a1660.6 1660.6 0 0 0-53 246c-14.98 118.89-19.98 238.22-15 358 1.34 33.03 3.17 66.03 5.5 99-386.33.17-772.67 0-1159-.5q-122.657-16.17-169.5-130.5a230.2 230.2 0 0 1-11-49c-.667-546.67-.667-1093.3 0-1640 9.834-76.145 49.334-130.64 118.5-163.5q29.688-12.892 62-16c255.33-.667 510.67-.667 766 0 43.68 4.875 82.01 21.541 115 50 45.37 38.377 91.04 76.377 137 114 28.54 18.847 59.87 30.181 94 34q662.985 1.986 1326 2c77.31 10.044 132.48 50.211 165.5 120.5a220.4 220.4 0 0 1 15 60c.5 61.67.67 123.33.5 185'/><path fill='#a5d6a7' d='M2999.5 1165.5c17.69 2.74 35.36 5.91 53 9.5 1.08.67 1.75 1.67 2 3 28.18 168.19 32.68 337.02 13.5 506.5-11.97 104.19-34.8 205.86-68.5 305-33.03 92.41-76.87 179.07-131.5 260a1694 1694 0 0 1-77 97 2240 2240 0 0 1-80.5 78.5c-46.48 38.13-95.48 72.8-147 104a1457 1457 0 0 1-145 70.5c-24.7 9.85-49.7 19.01-75 27.5-149 44.89-301.33 68.89-457 72-64.06 1.52-128.06.19-192-4-49.39-3.53-98.73-7.7-148-12.5a1745 1745 0 0 1-7-83 3304 3304 0 0 1-5.5-99c-4.98-119.78.02-239.11 15-358a1660.6 1660.6 0 0 1 53-246c33.56-106.11 81.23-205.11 143-297q23.07-29.58 45-60c39.41-47.08 82.58-90.58 129.5-130.5 1-.33 2-.67 3-1a896 896 0 0 1 55-43c69.82-48.26 144.49-88.26 224-120a1455 1455 0 0 1 207-61c164.92-33.57 331.25-42.9 499-28 30.9 3.15 61.57 6.32 92 9.5' opacity='.999'/><path fill='#43a047' d='M2709.5 1494.5c7.67-.17 15.34 0 23 .5a825 825 0 0 0-130 61c-97.27 54.78-187.6 119.12-271 193-15.7 14.7-31.7 29.04-48 43a4541 4541 0 0 0-111.5 115.5 2730 2730 0 0 1-57 68c-93.1 116.73-172.93 242.4-239.5 377-.5 1.02-1.17 1.19-2 .5 6.63-168.23 50.47-326.06 131.5-473.5 116.36-197.54 287.53-319.37 513.5-365.5 24.5-4.98 49.17-8.98 74-12q58.635-5.25 117-7.5'/><path fill='#43a047' d='M2999.5 1989.5c.17 143 0 286-.5 429-8.62 70.79-43.78 123.62-105.5 158.5q-31.365 15.615-66 21c-136.31 1.48-272.65 1.98-409 1.5 49.63-20.82 97.96-44.32 145-70.5 51.52-31.2 100.52-65.87 147-104a2240 2240 0 0 0 80.5-78.5c26.87-31.42 52.54-63.76 77-97 54.63-80.93 98.47-167.59 131.5-260' opacity='.999'/></svg>",
"folder-server-open": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fffde7' d='M14 15v4a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H15a1 1 0 0 0-1 1m6 3h-4v-2h4Zm4 0h-2v-2h2Zm-10 5v4a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H15a1 1 0 0 0-1 1m6 3h-4v-2h4Zm4 0h-2v-2h2Z'/></svg>",
"folder-server": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fffde7' d='M14 15v4a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H15a1 1 0 0 0-1 1m6 3h-4v-2h4Zm4 0h-2v-2h2Zm-10 5v4a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H15a1 1 0 0 0-1 1m6 3h-4v-2h4Zm4 0h-2v-2h2Z'/></svg>",
"folder-serverless-open": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M14 14h6l-.8 3H14zm18 0v3h-9.8l.8-3zm-18 6h4.4l-.8 3H14zm18 0v3H20.6l.8-3zm-18 6h2.8l-.8 3h-2zm18 0v3H19l.8-3z'/></svg>",
@@ -576,8 +583,8 @@
"folder-shader": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1bee7' d='M21.58 25.999A3.9 3.9 0 0 1 17.79 30a4.007 4.007 0 0 1 0-8.003A3.9 3.9 0 0 1 21.58 26Zm4.704-9.47a3.966 3.966 0 0 0-3.257-4.494 3.833 3.833 0 0 0-4.255 3.439 4.026 4.026 0 0 0 2.457 4.285c2.905 1.205 3.59 2.423 3.226 5.71a3.967 3.967 0 0 0 3.254 4.495 3.83 3.83 0 0 0 4.257-3.435 4.03 4.03 0 0 0-2.456-4.288c-2.905-1.207-3.59-2.427-3.225-5.713Z'/></svg>",
"folder-shared-open": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#e1bee7' d='M28 26a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2H18a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h-4v2h18v-2Zm-5-6v-2l4 2.798L23 24v-2a4.12 4.12 0 0 0-4 2c.448-2.003.888-3.595 4-4'/></svg>",
"folder-shared": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1bee7' d='M28 26a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2H18a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h-4v2h18v-2Zm-5-6v-2l4 2.798L23 24v-2a4.12 4.12 0 0 0-4 2c.448-2.003.888-3.595 4-4'/></svg>",
- "folder-snapcraft-open": "<svg viewBox='0 0 16 16'><path fill='#66bb6a' d='M14.033 6H4.597a.97.97 0 0 0-.918.684L2 12V5h11.566c0-.552-.433-1-.967-1H7.343a.95.95 0 0 1-.619-.232l-.622-.536A.95.95 0 0 0 5.483 3H1.967C1.433 3 1 3.448 1 4v8c0 .552.433 1 .967 1h10.632l2.323-5.606c.273-.66-.195-1.394-.889-1.394'/><path fill='#DCEDC8' d='m12.538 7.077 2.077 1.038-2.077 2.077zM8.385 14l3.807-3.462-1.73-1.73zM7 5l5.192 5.192V7.077zm8.654 2.077-3.116-.346L16 8.46z'/></svg>",
- "folder-snapcraft": "<svg viewBox='0 0 16 16'><path fill='#66BB6A' d='m6.922 3.768-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232'/><path fill='#DCEDC8' d='m12.538 7.077 2.077 1.038-2.077 2.077zM8.385 14l3.807-3.462-1.73-1.73zM7 5l5.192 5.192V7.077zm8.654 2.077-3.116-.346L16 8.46z'/></svg>",
+ "folder-snapcraft-open": "<svg viewBox='0 0 16 16'><path fill='#66bb6a' d='M14.033 6H4.597a.97.97 0 0 0-.918.684L2 12V5h11.566c0-.552-.433-1-.967-1H7.343a.95.95 0 0 1-.619-.232l-.622-.536A.95.95 0 0 0 5.483 3H1.967C1.433 3 1 3.448 1 4v8c0 .552.433 1 .967 1h10.632l2.323-5.606c.273-.66-.195-1.394-.889-1.394'/><path fill='#dcedc8' d='m12.538 7.077 2.077 1.038-2.077 2.077zM8.385 14l3.807-3.462-1.73-1.73zM7 5l5.192 5.192V7.077zm8.654 2.077-3.116-.346L16 8.46z'/></svg>",
+ "folder-snapcraft": "<svg viewBox='0 0 16 16'><path fill='#66bb6a' d='m6.922 3.768-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232'/><path fill='#dcedc8' d='m12.538 7.077 2.077 1.038-2.077 2.077zM8.385 14l3.807-3.462-1.73-1.73zM7 5l5.192 5.192V7.077zm8.654 2.077-3.116-.346L16 8.46z'/></svg>",
"folder-snippet-open": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='M29 12H9.4a2 2 0 0 0-1.9 1.4L4 24V10h24a2 2 0 0 0-2-2H15.1a2 2 0 0 1-1.3-.5l-1.2-1a2 2 0 0 0-1.3-.5H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.8-11.2A2 2 0 0 0 29 12'/><path fill='#ffe0b2' d='M20 10s-2 1.86-2 4 6 8 6 8l-.89.88a3 3 0 0 0-5.1 2A3.01 3.01 0 0 0 20.87 28a3.02 3.02 0 0 0 3.11-3.24L26 23.5l.25.31A3.02 3.02 0 0 0 28.88 28 3.01 3.01 0 0 0 32 25.12a3.01 3.01 0 0 0-4.38-2.78zm10 0-4 8 2 2s4-3.94 4-6-2-4-2-4m-9.06 14h.1c.51.02.9.4.95.89v.2a.98.98 0 0 1-1.03.91.99.99 0 0 1-.96-1.04.98.98 0 0 1 .94-.96m8 0h.1c.56.02.98.48.96 1.04a.98.98 0 0 1-1.04.96.98.98 0 0 1-.96-1.04.98.98 0 0 1 .94-.96' style='-inkscape-stroke:none'/></svg>",
"folder-snippet": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='m13.84 7.54-1.28-1.08A2 2 0 0 0 11.28 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.12a2 2 0 0 1-1.28-.46'/><path fill='#ffe0b2' d='M20 10s-2 1.86-2 4 6 8 6 8l-.89.88a3 3 0 0 0-5.1 2A3.01 3.01 0 0 0 20.87 28a3.02 3.02 0 0 0 3.11-3.24L26 23.5l.25.31A3.02 3.02 0 0 0 28.88 28 3.01 3.01 0 0 0 32 25.12a3.01 3.01 0 0 0-4.38-2.78zm10 0-4 8 2 2s4-3.94 4-6-2-4-2-4m-9.06 14h.1c.51.02.9.4.95.89v.2a.98.98 0 0 1-1.03.91.99.99 0 0 1-.96-1.04.98.98 0 0 1 .94-.96m8 0h.1c.56.02.98.48.96 1.04a.98.98 0 0 1-1.04.96.98.98 0 0 1-.96-1.04.98.98 0 0 1 .94-.96' style='-inkscape-stroke:none'/></svg>",
"folder-src-open": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#c8e6c9' d='M18.473 30a1 1 0 0 1-.238-.028 1.137 1.137 0 0 1-.828-1.323L20.5 12.905a1.13 1.13 0 0 1 .507-.744 1.06 1.06 0 0 1 .8-.134 1.14 1.14 0 0 1 .828 1.324l-3.101 15.744a1.12 1.12 0 0 1-.504.743 1.06 1.06 0 0 1-.557.162m6.2-2h-.077a1.08 1.08 0 0 1-.762-.412 1.164 1.164 0 0 1 .113-1.548l5.319-4.967-5.296-4.623a1.165 1.165 0 0 1-.162-1.544 1.08 1.08 0 0 1 .754-.437 1.06 1.06 0 0 1 .81.258l6.244 5.455a1.156 1.156 0 0 1 .003 1.723l-6.218 5.808a1.07 1.07 0 0 1-.729.289Zm-9.31 0a1.07 1.07 0 0 1-.728-.292l-6.226-5.811a1.16 1.16 0 0 1-.01-1.692l.02-.018 6.246-5.454a1.03 1.03 0 0 1 .8-.26 1.08 1.08 0 0 1 .76.436 1.165 1.165 0 0 1-.16 1.547l-5.294 4.62 5.32 4.964a1.156 1.156 0 0 1 .112 1.548 1.07 1.07 0 0 1-.762.412Z'/></svg>",
@@ -624,8 +631,8 @@
"folder-theme": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M21.998 10C16 10 12 14 12 20a10 10 0 0 0 10 10c.92 0 2 0 2-2 0-.436-.569-.785-.964-1.18A2.37 2.37 0 0 1 22 25c0-1 1-1 2-1h4c4 0 4-4 4-6 0-4-4-8-10.002-8M16 20a2 2 0 1 1 2-2 2 2 0 0 1-2 2m6-4a2 2 0 1 1 2-2 2 2 0 0 1-2 2m6 4a2 2 0 1 1 2-2 2 2 0 0 1-2 2'/></svg>",
"folder-tools-open": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#bbdefb' d='M24.363 19.012 13.364 30 12 28.638l10.988-11zm4.365-2.815.574-.576-.77-.77.624-.623-1.384-1.383-.623.624-.77-.77-.574.575A20.5 20.5 0 0 0 20.155 10l-.81 1.744a24.5 24.5 0 0 1 4.736 3.253l-.488.488 2.923 2.923.488-.488a24.5 24.5 0 0 1 3.252 4.736L32 21.848a20.5 20.5 0 0 0-3.272-5.651'/></svg>",
"folder-tools": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M24.363 19.012 13.364 30 12 28.638l10.988-11zm4.365-2.815.574-.576-.77-.77.624-.623-1.384-1.383-.623.624-.77-.77-.574.575A20.5 20.5 0 0 0 20.155 10l-.81 1.744a24.5 24.5 0 0 1 4.736 3.253l-.488.488 2.923 2.923.488-.488a24.5 24.5 0 0 1 3.252 4.736L32 21.848a20.5 20.5 0 0 0-3.272-5.651'/></svg>",
- "folder-trash-open": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#F44336' d='M463.47 192H151.06c-13.77 0-26 8.82-30.35 21.89L64 384V160h384c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42l-20.6-17.15c-5.75-4.8-13-7.43-20.48-7.43H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h352l76.88-179.39c1.7-3.98 2.59-8.28 2.59-12.61 0-17.67-14.33-32-32-32'/><path fill='#FFCDD2' d='M320 160v32h-96v32h32v192c0 17.63 14.38 32 32 32h160c17.63 0 32-14.37 32-32V224h32v-32h-96v-32zm0 96v128h32V256zm64 0v128h32V256z'/></svg>",
- "folder-trash": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#F44336' d='m221.5 120.58-20.6-17.16A32 32 0 0 0 180.42 96H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32V160c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42'/><path fill='#FFCDD2' d='M320 160v32h-96v32h32v192c0 17.63 14.38 32 32 32h160c17.63 0 32-14.37 32-32V224h32v-32h-96v-32zm0 96v128h32V256zm64 0v128h32V256z'/></svg>",
+ "folder-trash-open": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#f44336' d='M463.47 192H151.06c-13.77 0-26 8.82-30.35 21.89L64 384V160h384c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42l-20.6-17.15c-5.75-4.8-13-7.43-20.48-7.43H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h352l76.88-179.39c1.7-3.98 2.59-8.28 2.59-12.61 0-17.67-14.33-32-32-32'/><path fill='#ffcdd2' d='M320 160v32h-96v32h32v192c0 17.63 14.38 32 32 32h160c17.63 0 32-14.37 32-32V224h32v-32h-96v-32zm0 96v128h32V256zm64 0v128h32V256z'/></svg>",
+ "folder-trash": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#f44336' d='m221.5 120.58-20.6-17.16A32 32 0 0 0 180.42 96H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32V160c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42'/><path fill='#ffcdd2' d='M320 160v32h-96v32h32v192c0 17.63 14.38 32 32 32h160c17.63 0 32-14.37 32-32V224h32v-32h-96v-32zm0 96v128h32V256zm64 0v128h32V256z'/></svg>",
"folder-trigger-open": "<svg viewBox='0 0 1024 1024'><path fill='#ffc107' d='M926.944 384h-624.8a64 64 0 0 0-60.736 43.776L128 768V320h768a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848l-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h704l153.76-358.784A64 64 0 0 0 926.944 384'/><path fill='#ffecb3' d='m640 896 64-256h-64a28.252 32.205 0 0 1-20.876-53.906L832 320l-64 256h64a28.252 32.205 0 0 1 20.876 53.906z'/></svg>",
"folder-trigger": "<svg viewBox='0 0 1024 1024'><path fill='#ffc107' d='m443.008 241.152-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848'/><path fill='#ffecb3' d='m640 896 64-256h-64a28.252 32.205 0 0 1-20.876-53.906L832 320l-64 256h64a28.252 32.205 0 0 1 20.876 53.906z'/></svg>",
"folder-turborepo-open": "<svg viewBox='0 0 32 32'><defs><linearGradient id='a' x1='30.58' x2='17.816' y1='13.808' y2='26.573' gradientUnits='userSpaceOnUse'><stop offset='.15' stop-color='#2196f3'/><stop offset='.85' stop-color='#f50057'/></linearGradient></defs><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#cfd8dc' d='M23 16a3 3 0 1 1-3 3 3 3 0 0 1 3-3m0-2a5 5 0 1 0 5 5 5 5 0 0 0-5-5'/><path fill='url(#a)' d='M16 20a7.1 7.1 0 0 0 1.5 3.41l-1.421 1.426A9.05 9.05 0 0 1 14 20Zm15.944-2.003A9.015 9.015 0 0 0 24 10v2a7.085 7.085 0 0 1 0 14v2a9.03 9.03 0 0 0 7.944-10.003m-14.414 8.23A9.07 9.07 0 0 0 22 28v-2a7.1 7.1 0 0 1-3.03-1.218Z'/></svg>",
@@ -664,30 +671,30 @@
"folder-vuex-store": "<svg viewBox='0 0 32 32'><path fill='#009688' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><g data-mit-no-recolor='true'><path fill='#41b883' d='m14 29.989 7.2-14.38 1.8 3.508v3.688l-3.577 7.184ZM32 30l-7.2-14.38-1.8 3.508v3.688L26.566 30Z'/><path fill='#35495e' d='m14 12 4.5 9 2.7-5.391L19.4 12Zm18 0-4.5 9-2.7-5.391L26.6 12Z'/></g></svg>",
"folder-wakatime-open": "<svg viewBox='0 0 32 32'><path fill='#455a64' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f5f5f5' d='M31.578 14.516A1.62 1.62 0 0 0 30.442 14a1.5 1.5 0 0 0-1.273.728l-6.255 8.823-.82-1.324a1.54 1.54 0 0 0-1.31-.756 1.52 1.52 0 0 0-1.331.827l-.783 1.253-3.795-5.52a1.54 1.54 0 0 0-1.352-.8 1.6 1.6 0 0 0-1.521 1.644 1.67 1.67 0 0 0 .366 1.066l5.026 7.205a1.506 1.506 0 0 0 2.686.058l.717-1.136.698 1.103a2 2 0 0 0 .178.266 2 2 0 0 0 .13.141l.106.092a2 2 0 0 0 .227.15l.1.05a1.4 1.4 0 0 0 .455.122l.123.008h.029a1.53 1.53 0 0 0 1.204-.617l7.61-10.702a1.7 1.7 0 0 0 .341-1.007 1.6 1.6 0 0 0-.42-1.158m-9.212 12.82'/></svg>",
"folder-wakatime": "<svg viewBox='0 0 32 32'><path fill='#455a64' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f5f5f5' d='M31.578 14.516A1.62 1.62 0 0 0 30.442 14a1.5 1.5 0 0 0-1.273.728l-6.255 8.823-.82-1.324a1.54 1.54 0 0 0-1.31-.756 1.52 1.52 0 0 0-1.331.827l-.783 1.253-3.795-5.52a1.54 1.54 0 0 0-1.352-.8 1.6 1.6 0 0 0-1.521 1.644 1.67 1.67 0 0 0 .366 1.066l5.026 7.205a1.506 1.506 0 0 0 2.686.058l.717-1.136.698 1.103a2 2 0 0 0 .178.266 2 2 0 0 0 .13.141l.106.092a2 2 0 0 0 .227.15l.1.05a1.4 1.4 0 0 0 .455.122l.123.008h.029a1.53 1.53 0 0 0 1.204-.617l7.61-10.702a1.7 1.7 0 0 0 .341-1.007 1.6 1.6 0 0 0-.42-1.158m-9.212 12.82'/></svg>",
- "folder-webpack-open": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#FAFAFA' d='m30.992 14.263-7-4a2 2 0 0 0-1.984 0l-7 4A2 2 0 0 0 14 16v8.65a2 2 0 0 0 1.025 1.746l6 3.35A2 2 0 0 0 23 29.73a2 2 0 0 0 1.975.016l6-3.35A2 2 0 0 0 32 24.65V16a2 2 0 0 0-1.008-1.737'/><path fill='#0277bd' d='M30 24.65 24 28v-6l6-3.35zM23 12l-7 4 7 4 7-4zm-7 12.65L22 28v-6l-6-3.35z'/></svg>",
- "folder-webpack": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#FAFAFA' d='m30.992 14.263-7-4a2 2 0 0 0-1.984 0l-7 4A2 2 0 0 0 14 16v8.65a2 2 0 0 0 1.025 1.746l6 3.35A2 2 0 0 0 23 29.73a2 2 0 0 0 1.975.016l6-3.35A2 2 0 0 0 32 24.65V16a2 2 0 0 0-1.008-1.737'/><path fill='#0277bd' d='M30 24.65 24 28v-6l6-3.35zM23 12l-7 4 7 4 7-4zm-7 12.65L22 28v-6l-6-3.35z'/></svg>",
+ "folder-webpack-open": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fafafa' d='m30.992 14.263-7-4a2 2 0 0 0-1.984 0l-7 4A2 2 0 0 0 14 16v8.65a2 2 0 0 0 1.025 1.746l6 3.35A2 2 0 0 0 23 29.73a2 2 0 0 0 1.975.016l6-3.35A2 2 0 0 0 32 24.65V16a2 2 0 0 0-1.008-1.737'/><path fill='#0277bd' d='M30 24.65 24 28v-6l6-3.35zM23 12l-7 4 7 4 7-4zm-7 12.65L22 28v-6l-6-3.35z'/></svg>",
+ "folder-webpack": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#fafafa' d='m30.992 14.263-7-4a2 2 0 0 0-1.984 0l-7 4A2 2 0 0 0 14 16v8.65a2 2 0 0 0 1.025 1.746l6 3.35A2 2 0 0 0 23 29.73a2 2 0 0 0 1.975.016l6-3.35A2 2 0 0 0 32 24.65V16a2 2 0 0 0-1.008-1.737'/><path fill='#0277bd' d='M30 24.65 24 28v-6l6-3.35zM23 12l-7 4 7 4 7-4zm-7 12.65L22 28v-6l-6-3.35z'/></svg>",
"folder-windows-open": "<svg viewBox='0 0 32 32'><path fill='#2196f3' d='M24.667 27.333h-20A2.667 2.667 0 0 1 2 24.667v-16A2.657 2.657 0 0 1 4.648 6h8.019l2.666 2.667h9.334a2.68 2.68 0 0 1 2.666 2.666H4.667v13.334L7.52 14h22.76l-3.04 11.333a2.67 2.67 0 0 1-2.573 2'/><path fill='#bbdefb' d='M14 12h8v8h-8zm10 0h8v8h-8zm0 10h8v8h-8zm-10 0h8v8h-8z'/></svg>",
"folder-windows": "<svg viewBox='0 0 32 32'><path fill='#2196f3' d='M12.667 6h-8A2.657 2.657 0 0 0 2 8.648v16.019a2.68 2.68 0 0 0 2.667 2.666H26a2.68 2.68 0 0 0 2.667-2.666V11.333A2.667 2.667 0 0 0 26 8.667H15.333z'/><path fill='#bbdefb' d='M14 12h8v8h-8zm10 0h8v8h-8zm0 10h8v8h-8zm-10 0h8v8h-8z'/></svg>",
- "folder-wordpress-open": "<svg viewBox='0 0 32 32'><path fill='#0277BD' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#e1f5fe' d='M22 8a10 10 0 0 0-8.356 4.51l.642.013c1.049 0 2.669-.125 2.669-.125a.413.413 0 0 1 .07.824l-1.155.119 3.648 10.803 2.188-6.56-1.559-4.243-1.061-.12a.414.414 0 0 1 .07-.823l2.632.125c1.049 0 2.67-.125 2.67-.125a.413.413 0 0 1 .062.824l-1.143.119 3.612 10.72 1.002-3.332a12.7 12.7 0 0 0 .757-3.228 5.2 5.2 0 0 0-.83-2.764 4.67 4.67 0 0 1-.978-2.34 1.73 1.73 0 0 1 1.681-1.771h.127A10 10 0 0 0 22.001 8Zm8.777 5.201.07 1.037a9.5 9.5 0 0 1-.771 3.576l-3.053 8.822a10 10 0 0 0 3.754-13.435m-17.916.724A10.2 10.2 0 0 0 12 18.003 9.98 9.98 0 0 0 17.64 27Zm9.315 4.952-2.996 8.72a10.06 10.06 0 0 0 6.144-.164l-.073-.142Z'/></svg>",
- "folder-wordpress": "<svg viewBox='0 0 32 32'><path fill='#0277BD' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1f5fe' d='M22 8a10 10 0 0 0-8.356 4.51l.642.013c1.049 0 2.669-.125 2.669-.125a.413.413 0 0 1 .07.824l-1.155.119 3.648 10.803 2.188-6.56-1.559-4.243-1.061-.12a.414.414 0 0 1 .07-.823l2.632.125c1.049 0 2.67-.125 2.67-.125a.413.413 0 0 1 .062.824l-1.143.119 3.612 10.72 1.002-3.332a12.7 12.7 0 0 0 .757-3.228 5.2 5.2 0 0 0-.83-2.764 4.67 4.67 0 0 1-.978-2.34 1.73 1.73 0 0 1 1.681-1.771h.127A10 10 0 0 0 22.001 8Zm8.777 5.201.07 1.037a9.5 9.5 0 0 1-.771 3.576l-3.053 8.822a10 10 0 0 0 3.754-13.435m-17.916.724A10.2 10.2 0 0 0 12 18.003 9.98 9.98 0 0 0 17.64 27Zm9.315 4.952-2.996 8.72a10.06 10.06 0 0 0 6.144-.164l-.073-.142Z'/></svg>",
+ "folder-wordpress-open": "<svg viewBox='0 0 32 32'><path fill='#0277bd' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#e1f5fe' d='M22 8a10 10 0 0 0-8.356 4.51l.642.013c1.049 0 2.669-.125 2.669-.125a.413.413 0 0 1 .07.824l-1.155.119 3.648 10.803 2.188-6.56-1.559-4.243-1.061-.12a.414.414 0 0 1 .07-.823l2.632.125c1.049 0 2.67-.125 2.67-.125a.413.413 0 0 1 .062.824l-1.143.119 3.612 10.72 1.002-3.332a12.7 12.7 0 0 0 .757-3.228 5.2 5.2 0 0 0-.83-2.764 4.67 4.67 0 0 1-.978-2.34 1.73 1.73 0 0 1 1.681-1.771h.127A10 10 0 0 0 22.001 8Zm8.777 5.201.07 1.037a9.5 9.5 0 0 1-.771 3.576l-3.053 8.822a10 10 0 0 0 3.754-13.435m-17.916.724A10.2 10.2 0 0 0 12 18.003 9.98 9.98 0 0 0 17.64 27Zm9.315 4.952-2.996 8.72a10.06 10.06 0 0 0 6.144-.164l-.073-.142Z'/></svg>",
+ "folder-wordpress": "<svg viewBox='0 0 32 32'><path fill='#0277bd' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1f5fe' d='M22 8a10 10 0 0 0-8.356 4.51l.642.013c1.049 0 2.669-.125 2.669-.125a.413.413 0 0 1 .07.824l-1.155.119 3.648 10.803 2.188-6.56-1.559-4.243-1.061-.12a.414.414 0 0 1 .07-.823l2.632.125c1.049 0 2.67-.125 2.67-.125a.413.413 0 0 1 .062.824l-1.143.119 3.612 10.72 1.002-3.332a12.7 12.7 0 0 0 .757-3.228 5.2 5.2 0 0 0-.83-2.764 4.67 4.67 0 0 1-.978-2.34 1.73 1.73 0 0 1 1.681-1.771h.127A10 10 0 0 0 22.001 8Zm8.777 5.201.07 1.037a9.5 9.5 0 0 1-.771 3.576l-3.053 8.822a10 10 0 0 0 3.754-13.435m-17.916.724A10.2 10.2 0 0 0 12 18.003 9.98 9.98 0 0 0 17.64 27Zm9.315 4.952-2.996 8.72a10.06 10.06 0 0 0 6.144-.164l-.073-.142Z'/></svg>",
"folder-yarn-open": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M31.445 24.006a7.2 7.2 0 0 0-2.736 1.301 16.2 16.2 0 0 1-4.038 1.886 1.1 1.1 0 0 1-.68.394 42 42 0 0 1-4.455.413c-.805.006-1.296-.212-1.434-.554a1.14 1.14 0 0 1 .58-1.474l.02-.008a2.5 2.5 0 0 1-.357-.27c-.118-.122-.243-.368-.28-.277-.156.392-.237 1.352-.654 1.784a2.04 2.04 0 0 1-2.3.052c-.704-.386.05-1.295.05-1.295a.497.497 0 0 1-.679-.23l-.007-.015a3.56 3.56 0 0 1-.46-2.106 3.92 3.92 0 0 1 1.221-2.08 6.85 6.85 0 0 1 .455-3.144 7.4 7.4 0 0 1 2.187-2.614s-1.34-1.527-.84-2.912c.322-.903.453-.895.56-.935a2.5 2.5 0 0 0 1.003-.61 3.58 3.58 0 0 1 3.046-1.213s.8-2.53 1.546-2.035a13.3 13.3 0 0 1 1.06 2.062s.885-.535.985-.336a8.35 8.35 0 0 1 .361 4.382 10.1 10.1 0 0 1-1.795 3.863 7.9 7.9 0 0 1 1.808 2.778 8.4 8.4 0 0 1 .181 3.722l.024.044a4.44 4.44 0 0 0 2.343-.934 5.77 5.77 0 0 1 2.954-1.147.75.75 0 0 1 .873.62.775.775 0 0 1-.542.888'/></svg>",
"folder-yarn": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M31.445 24.006a7.2 7.2 0 0 0-2.736 1.301 16.2 16.2 0 0 1-4.038 1.886 1.1 1.1 0 0 1-.68.394 42 42 0 0 1-4.455.413c-.805.006-1.296-.212-1.434-.554a1.14 1.14 0 0 1 .58-1.474l.02-.008a2.5 2.5 0 0 1-.357-.27c-.118-.122-.243-.368-.28-.277-.156.392-.237 1.352-.654 1.784a2.04 2.04 0 0 1-2.3.052c-.704-.386.05-1.295.05-1.295a.497.497 0 0 1-.679-.23l-.007-.015a3.56 3.56 0 0 1-.46-2.106 3.92 3.92 0 0 1 1.221-2.08 6.85 6.85 0 0 1 .455-3.144 7.4 7.4 0 0 1 2.187-2.614s-1.34-1.527-.84-2.912c.322-.903.453-.895.56-.935a2.5 2.5 0 0 0 1.003-.61 3.58 3.58 0 0 1 3.046-1.213s.8-2.53 1.546-2.035a13.3 13.3 0 0 1 1.06 2.062s.885-.535.985-.336a8.35 8.35 0 0 1 .361 4.382 10.1 10.1 0 0 1-1.795 3.863 7.9 7.9 0 0 1 1.808 2.778 8.4 8.4 0 0 1 .181 3.722l.024.044a4.44 4.44 0 0 0 2.343-.934 5.77 5.77 0 0 1 2.954-1.147.75.75 0 0 1 .873.62.775.775 0 0 1-.542.888'/></svg>",
- "folder-zeabur-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#7E57C2' d='M29 12H9.4c-.9 0-1.6.6-1.9 1.4L4 24V10h24c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.2-1.3-.5l-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h22l4.8-11.2c.4-1 0-2.2-1-2.6-.3-.1-.6-.2-.8-.2'/><g fill='#D1C4E9' clip-path='url(#a)'><path d='M20 24h12v6H12v-6h6l8-4H12v-6h20v6z'/><path d='M26 14H12v6h14zm6 10H20v6h12z'/></g><defs><clipPath id='a'><path d='M12 14h20v16H12z'/></clipPath></defs></svg>",
- "folder-zeabur": "<svg fill='none' viewBox='0 0 32 32'><path fill='#7E57C2' d='m13.8 7.5-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h24c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.1-1.3-.5'/><g fill='#D1C4E9' clip-path='url(#a)'><path d='M20 24h12v6H12v-6h6l8-4H12v-6h20v6z'/><path d='M26 14H12v6h14zm6 10H20v6h12z'/></g><defs><clipPath id='a'><path d='M12 14h20v16H12z'/></clipPath></defs></svg>",
+ "folder-zeabur-open": "<svg fill='none' viewBox='0 0 32 32'><path fill='#7e57c2' d='M29 12H9.4c-.9 0-1.6.6-1.9 1.4L4 24V10h24c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.2-1.3-.5l-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h22l4.8-11.2c.4-1 0-2.2-1-2.6-.3-.1-.6-.2-.8-.2'/><g fill='#d1c4e9' clip-path='url(#a)'><path d='M20 24h12v6H12v-6h6l8-4H12v-6h20v6z'/><path d='M26 14H12v6h14zm6 10H20v6h12z'/></g><defs><clipPath id='a'><path d='M12 14h20v16H12z'/></clipPath></defs></svg>",
+ "folder-zeabur": "<svg fill='none' viewBox='0 0 32 32'><path fill='#7e57c2' d='m13.8 7.5-1.3-1.1c-.3-.2-.8-.4-1.2-.4H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h24c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2H15.1c-.5 0-.9-.1-1.3-.5'/><g fill='#d1c4e9' clip-path='url(#a)'><path d='M20 24h12v6H12v-6h6l8-4H12v-6h20v6z'/><path d='M26 14H12v6h14zm6 10H20v6h12z'/></g><defs><clipPath id='a'><path d='M12 14h20v16H12z'/></clipPath></defs></svg>",
"folder": "<svg viewBox='0 0 16 16'><path fill='#90a4ae' d='m6.922 3.768-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232'/></svg>",
"font": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M24 28h4L18 4h-4L4 28h4l8-19.422'/><path fill='#f44336' d='M8 20h16v4H8z'/></svg>",
"forth": "<svg viewBox='0 0 67.733 67.733'><path fill='#ef5350' d='M10.321 12.006c-.21 0-.38.173-.38.39v12.63c0 .215.17.389.38.389h16.925c.21 0 .38-.174.38-.39v-12.63a.384.384 0 0 0-.38-.389zm30.167 0c-.21 0-.38.173-.38.39v12.63c0 .215.17.389.38.389h16.925c.21 0 .38-.174.38-.39v-12.63a.384.384 0 0 0-.38-.389zM10.321 34.328c-.21 0-.38.173-.38.39v12.63c0 .215.17.389.38.389h16.925c.21 0 .38-.174.38-.39v-12.63a.384.384 0 0 0-.38-.389zm30.167 0c-.21 0-.38.173-.38.39v12.63c0 .215.17.389.38.389h4.053v4.351H40.51a.374.374 0 0 0-.375.375v2.89c0 .207.167.374.375.374h8.303a.373.373 0 0 0 .374-.374v-4.135h3.798a.374.374 0 0 0 .374-.375v-3.106h4.054c.21 0 .38-.174.38-.39v-12.63a.384.384 0 0 0-.38-.389z'/></svg>",
"fortran": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M6 4v2h3a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6v2h12v-2h-3a1 1 0 0 1-1-1v-9h4a2 2 0 0 1 2 2v2h2V10h-2v2a2 2 0 0 1-2 2h-4V6h6a4 4 0 0 1 4 4h2V4Z'/></svg>",
"foxpro": "<svg viewBox='0 0 300 300'><path fill='#fbc02d' d='M110.978 292.583c1.71-.48 4.858-.93 6.994-.996l3.886-.123v-5.072c0-9.684-6.355-24.885-13.844-33.115-1.22-1.34-5.476-4.083-9.459-6.096-7.06-3.568-7.46-3.643-16.061-3.032-4.85.345-11.618 1.392-15.038 2.325-21.15 5.777-53.825-10.127-53.871-26.223-.017-5.957 2.584-8.327 11.094-10.108 10.248-2.144 17.288-5.479 18.06-8.554 1.18-4.697-.304-19.367-2.339-23.14-1.434-2.66-1.925-5.792-1.94-12.369-.018-8.404.16-9.092 3.878-15.025 2.143-3.42 6.093-8.94 8.779-12.265 4.553-5.639 4.908-6.515 5.266-12.953.414-7.455-2.387-20.66-6.188-29.184-4.29-9.62-3.664-35.68 1.334-55.392C56.631 21.138 66.772 4.993 73.058 6.99c2.083.66 2.215 1.013 9.388 24.968 2.121 7.084 2.404 7.46 8.731 11.62 10.456 6.875 20.048 18.022 29.738 34.563l2.684 4.582 10.203 1.289c17.3 2.184 37.568 10.032 48.636 18.83 2.26 1.796 4.824 3.043 5.7 2.771.875-.271 6.923-2.912 13.438-5.867 14.079-6.386 28.26-11.15 47.56-15.972 20.388-5.097 31.764-7.167 34.968-6.363 2.781.699 2.796.75 2.153 7.662-.73 7.857-5.042 21.461-9.758 30.793-7.233 14.308-25.004 34-41.889 46.417-7.607 5.594-7.995 6.08-8.621 10.787-5.233 39.421-15.87 61.176-43.496 88.956-12.947 13.02-13.966 13.797-25.405 19.401-15.592 7.64-23.663 9.762-44.04 11.572-4.036.36-4.494.268-2.072-.414zm-80.394-56.332a167 167 0 0 1-2.505-1.814c-1.917-1.406-1.965-1.384-.65.294.783.997 1.91 1.813 2.504 1.813s.888-.132.65-.293z'/><path fill='#ff9800' d='M110.978 292.583c1.71-.48 4.858-.93 6.994-.996l3.886-.123-.012-4.922c-.015-5.735-1.855-13.217-5.005-20.336-1.63-3.684-1.805-4.689-.653-3.733 2.945 2.445 2.772.137-.327-4.355-3.607-5.228-8.862-9.639-16.88-14.163-3.157-1.782-6.76-4.412-8.006-5.844-1.367-1.574-3.365-2.604-5.048-2.604-2.704 0-2.657.1 1.547 3.33 2.383 1.832 4.333 3.63 4.333 3.992s-4.08.951-9.067 1.305c-4.987.353-11.864 1.407-15.284 2.34-21.15 5.777-53.825-10.127-53.871-26.223-.018-5.957 2.584-8.327 11.094-10.107 10.282-2.151 17.288-5.477 18.066-8.578 1.24-4.94-.43-20.227-2.787-25.52-1.773-3.981-2.17-6.518-1.862-11.917.432-7.593 3.474-13.514 13.02-25.34 4.553-5.639 4.91-6.514 5.267-12.953.413-7.454-2.387-20.66-6.188-29.184-4.29-9.618-3.664-35.68 1.334-55.392C56.631 21.138 66.772 4.992 73.058 6.988c2.083.661 2.215 1.014 9.388 24.968 2.121 7.085 2.404 7.46 8.731 11.62 10.456 6.876 20.048 18.022 29.738 34.563l2.684 4.583 10.203 1.289c17.3 2.184 37.568 10.032 48.636 18.83 2.26 1.796 4.824 3.043 5.7 2.771.875-.271 6.922-2.912 13.438-5.867 14.079-6.386 28.26-11.15 47.56-15.972 20.388-5.097 31.764-7.167 34.968-6.363 2.781.699 2.795.75 2.153 7.662-.73 7.857-5.042 21.461-9.758 30.793-7.233 14.308-25.004 34-41.889 46.417-7.608 5.594-7.995 6.08-8.621 10.787-5.233 39.421-15.87 61.176-43.496 88.956-12.947 13.02-13.966 13.797-25.405 19.401-15.592 7.64-23.663 9.762-44.04 11.572-4.036.36-4.494.267-2.072-.414zm28.487-17.757c15.214-5.868 29.597-17.34 38.77-30.924 10.817-16.02 18.632-40.542 21.438-67.272l1.34-12.765 7.065-6.263c7.63-6.765 16.248-18.903 22.119-31.154 4.509-9.41 4.622-12.622.517-14.743-6.472-3.347-27.43-1.059-46.262 5.05-12.977 4.211-14.76 4.4-19.647 2.08l-3.479-1.65-7.558 5.011c-4.158 2.756-12.657 10.028-18.889 16.158-11.187 11.007-14.057 12.906-14.057 9.305 0-2.563 14.333-18.344 24.633-27.122 6.74-5.744 9.286-7.307 11.917-7.32 2.425-.011 2.932-.277 1.846-.964-.823-.52-3.643-.694-6.265-.383-4.02.477-5.899.003-11.973-3.008-3.964-1.966-12.103-5.63-18.087-8.143-9.855-4.14-11.67-5.35-19.27-12.867C99.007 83.29 92.3 75.125 88.715 69.711c-7.619-11.512-9.882-14.176-13.738-16.182-6.709-3.49-11.336 2.036-14.805 17.687-2.129 9.603-1.636 29.433 1.083 43.55 2.508 13.023 1.12 18.641-7.308 29.553-9.286 12.025-10.76 19.67-6.29 32.617 3.345 9.682 4.876 19.774 3.542 23.34-2.335 6.24-12.264 13.471-18.5 13.471-3.035 0-3.19.184-2.564 3.04.794 3.61-2.412 13.54-4.373 13.54-1.97 0-1.581 1.74.91 4.06 3.543 3.301 11.06 6.389 16.814 6.907 4.561.412 4.949.314 2.725-.687a979 979 0 0 1-5.699-2.599l-3.109-1.433 4.145.762c6.893 1.266 21.308.708 29.22-1.132 4.101-.953 10.03-1.733 13.172-1.733 10.801 0 24.919 7.327 33.195 17.229 4.183 5.005 10.903 18.998 10.926 22.751.026 4.412.858 4.44 11.402.374zm-5.69-42.91c-34.094-5.987-38.188-6.821-39.716-8.089-2.84-2.356 2.65-2.362 31.777-.034 15.761 1.26 28.936 1.963 29.277 1.563.34-.401 1.119-5.78 1.728-11.953.61-6.174 1.39-11.504 1.733-11.847 1.709-1.709 2.777 2.714 3.803 15.74 1.267 16.085 1.015 16.916-5.357 17.66-1.957.229-12.417-1.14-23.245-3.04m-52.977-29.777c-.839-1.356 2.065-3.717 11.39-9.264 9.722-5.78 21.588-9.929 32.597-11.395 13.411-1.786 17.117.44 6.12 3.676-4.205 1.238-6 2.454-8.009 5.428-3.307 4.9-9.59 9.453-14.248 10.327-2.002.375-6.759.093-10.572-.626-6.389-1.205-7.31-1.14-11.762.83-3.246 1.436-5.054 1.771-5.515 1.025zM61.8 181.891c-4.124-5.852-5.081-11.666-2.813-17.094 1.092-2.615 1.411-5.572 1.014-9.413-.62-6.007.614-8.12 3.393-5.814.85.706 3.211 4.26 5.244 7.896 4.35 7.777 4.959 15.03 2.008 23.912-2.29 6.894-4.267 7.008-8.846.513'/><path fill='#263238' d='M110.978 292.583c1.71-.48 4.858-.93 6.994-.996 2.347-.075 3.89-.637 3.897-1.418.004-.73 5.324-3.067 12.175-5.35 15.388-5.127 25.225-9.652 34.145-15.702 29.01-19.678 47.927-56.35 47.958-92.981.01-10.453.32-10.95 10.844-17.31 8.456-5.11 30.613-27.397 30.613-30.793 0-.536 1.457-3.175 3.238-5.863 3.99-6.023 10.38-23.325 10.995-29.775.435-4.56.323-4.8-2.485-5.336-10.334-1.974-40.272 5.589-66.668 16.842-19.344 8.248-18.662 8.18-28.102 2.792-15.56-8.878-31.568-15.114-45.666-17.789-4.657-.883-8.536-1.716-8.618-1.852-1.217-1.964-13.342-18.003-16.245-21.488-2.138-2.565-7.377-7.672-11.643-11.352-8.3-7.155-11.524-12.045-15.023-22.776-2.646-8.118-4.738-11.482-7.106-11.42-.985.026-3.746 2.288-6.138 5.024-4.88 5.587-7.616 14.008-9.749 30-1.675 12.57-.592 40.068 2.189 55.6 3.13 17.474 1.72 23.614-7.368 32.123l-4.426 4.145 4.343-5.33c6.243-7.661 7.43-10.45 7.434-17.466.004-6.504-3.948-23.123-6.818-28.673-2.286-4.42-3.133-24.55-1.54-36.61 3.76-28.47 16.268-54.564 24.849-51.842 2.083.662 2.216 1.014 9.388 24.969 2.122 7.084 2.404 7.46 8.732 11.62 10.456 6.875 20.048 18.022 29.738 34.563l2.684 4.582 10.161 1.284c17.57 2.218 38.61 10.584 50.486 20.072l2.808 2.243 9.398-4.367c5.169-2.402 13.464-5.935 18.435-7.852 18.978-7.321 63.593-18.103 69.142-16.71 2.872.72 2.875.729 2.228 7.681-.73 7.856-5.041 21.46-9.758 30.793-7.232 14.308-25.004 34-41.889 46.417-7.607 5.593-7.995 6.08-8.62 10.787-5.233 39.421-15.871 61.176-43.497 88.956-12.947 13.02-13.966 13.797-25.405 19.401-15.592 7.64-23.663 9.76-44.04 11.572-4.036.358-4.494.267-2.072-.415zm-65.8-46.306c-8.26-2.214-20.64-8.61-24.89-12.861-7.241-7.241-9.021-15.757-4.205-20.116 1.073-.972 5.017-2.408 8.763-3.191 8.78-1.837 14.8-4.553 19.146-8.637 1.913-1.798 4.127-3.27 4.922-3.27 5.032 0-6.224 11.61-13.063 13.474-2.565.699-5.241 1.434-5.947 1.634-.94.266-1.012 1.077-.266 3.039 1.193 3.138-.806 8.86-4.458 12.76l-2.282 2.437 3.109 2.521c11.045 8.96 23.647 10.678 44.586 6.077 12.895-2.833 13.257-2.856 16.62-1.046 6.163 3.32 5.233 4.346-4.562 5.039-4.937.349-11.542 1.342-14.677 2.207-7.077 1.954-15.352 1.93-22.797-.067zm103.622-13.43c-.855-.19-11.94-2.02-24.634-4.066-24.527-3.953-26.442-4.385-25.583-5.776.577-.932 1.04-.91 34.537 1.674 11.609.897 21.688 1.405 22.398 1.133.84-.321 1.55-4.145 2.031-10.932.721-10.171 2.027-14.003 3.137-9.206.302 1.305.987 7.236 1.523 13.18 1.229 13.66.8 14.62-6.452 14.461-2.973-.066-6.103-.277-6.958-.468zm-62.842-35.035c9.795-8.936 32.919-17.021 46.583-16.29 2.632.141 1.297 1.805-2.125 2.647-1.572.387-5.686 3.662-9.142 7.277-7.841 8.202-11.972 9.648-21.914 7.672-5.395-1.073-7.267-1.07-9.948.007-6.85 2.753-7.6 2.468-3.454-1.313m-23.513-17.967c-4.163-6.559-4.619-9.794-2.197-15.588 1.148-2.75 1.403-5.397.904-9.395-.626-5.014-.51-5.53 1.183-5.204 1.033.2 3.52 3.16 5.527 6.578 4.576 7.796 5.448 15.96 2.535 23.746-1.048 2.802-2.411 5.265-3.03 5.47-.62.207-2.834-2.317-4.922-5.607m59.413-32.9c0-2.015 12.835-16.484 20.322-22.909 9.177-7.875 12.004-9.768 14.593-9.768 4.217 0 2.596 2.78-4.605 7.892-3.848 2.73-11.858 9.766-17.803 15.634-5.944 5.869-11.19 10.67-11.657 10.67s-.85-.684-.85-1.52z'/></svg>",
"freemarker": "<svg viewBox='0 0 16 16'><path fill='#2196f3' d='m12.5 11 .75.5L15 8l-1.75-3.5-.75.5L14 8zM6 4h1v2h2V4h1v2h1.5v1H10v2h1.5v1H10v2H9v-2H7v2H6v-2H4.5V9H6V7H4.5V6H6zm1 5h2V7H7zM3.5 5l-.75-.5L1 8l1.75 3.5.75-.5L2 8z'/></svg>",
- "fsharp": "<svg viewBox='0 0 500 500'><path fill='#0288D1' d='m236.249 36.066-213.94 213.94 213.94 213.94v-84.36l-129.7-129.7 129.7-129.7z'/><path fill='#0288D1' d='m236.249 156.017-93.622 93.62 93.622 93.622z'/><path fill='#00B8D4' d='m263.759 36.047 213.94 213.94-213.94 213.94v-84.36l129.7-129.7-129.7-129.7z'/></svg>",
- "fusebox": "<svg viewBox='0 0 152.99 160.01'><g data-name='Layer 2'><g data-name='Fuse Box'><path fill='#FAFAFA' d='m76.995 12.087 64.783 21.76-6.72 76.61-57.094 37.236-60.916-38.345-4.975-75.178z' data-mit-no-recolor='true'/><path fill='#424242' d='m77.982 149.831-62.688-39.444-5.124-77.518 66.817-22.694 66.729 22.406-6.93 78.906zM18.794 108.31l59.153 37.235 55.382-36.127 6.52-74.306-62.845-21.105-63.028 21.437z' data-mit-no-recolor='true'/><path fill='#0277BD' d='M76.856 140.055V64.012l58.42-26.028-5.229 68.754z'/><path fill='#424242' d='M76.856 140.055 24.179 107.42l-5.603-69.487 58.28 26.08z' data-mit-no-recolor='true'/><path fill='#FAFAFA' d='m32.498 56.2.47 13.905L65.57 85.973l.105 12.822-32.296-15.894 1.432 32.383-12.71-7.856-4.407-70.308 59.17 26.901v13.494z' data-mit-no-recolor='true'/><path fill='#FAFAFA' d='M128.799 89.404c-.07 11.451-5.525 23.209-14.638 28.908l-37.358 24.38V64.038L113.7 47.594c4.504-2.103 8.3-1.248 10.823 2.715 1.658 2.618 2.523 6.31 2.496 10.657a31.15 31.15 0 0 1-3.954 15.546c3.745 1.745 5.814 5.717 5.735 12.892zm-11.268-23.576c0-4.05-1.746-6.049-4.295-4.757L87.923 73.71l-.07 15.022 25.697-13.608c2.182-1.152 3.98-5.159 3.98-9.296zm1.876 28.14c0-4.983-2.112-7.192-5.717-5.175l-25.836 14.463v17.09l25.61-15.24c3.866-2.304 5.926-6.345 5.943-11.137z' data-mit-no-recolor='true'/><path fill='#424242' d='m76.856 17.874-58.21 20.031 95.812 2.078-73.337 8.039 35.735 15.99 18.12-8.073-24.204-1.196 43.45-7.385 21.054-9.374z' data-mit-no-recolor='true'/></g></g></svg>",
+ "fsharp": "<svg viewBox='0 0 500 500'><path fill='#0288d1' d='m236.249 36.066-213.94 213.94 213.94 213.94v-84.36l-129.7-129.7 129.7-129.7z'/><path fill='#0288d1' d='m236.249 156.017-93.622 93.62 93.622 93.622z'/><path fill='#00b8d4' d='m263.759 36.047 213.94 213.94-213.94 213.94v-84.36l129.7-129.7-129.7-129.7z'/></svg>",
+ "fusebox": "<svg viewBox='0 0 152.99 160.01'><g data-name='Layer 2'><g data-name='Fuse Box'><path fill='#fafafa' d='m76.995 12.087 64.783 21.76-6.72 76.61-57.094 37.236-60.916-38.345-4.975-75.178z' data-mit-no-recolor='true'/><path fill='#424242' d='m77.982 149.831-62.688-39.444-5.124-77.518 66.817-22.694 66.729 22.406-6.93 78.906zM18.794 108.31l59.153 37.235 55.382-36.127 6.52-74.306-62.845-21.105-63.028 21.437z' data-mit-no-recolor='true'/><path fill='#0277bd' d='M76.856 140.055V64.012l58.42-26.028-5.229 68.754z'/><path fill='#424242' d='M76.856 140.055 24.179 107.42l-5.603-69.487 58.28 26.08z' data-mit-no-recolor='true'/><path fill='#fafafa' d='m32.498 56.2.47 13.905L65.57 85.973l.105 12.822-32.296-15.894 1.432 32.383-12.71-7.856-4.407-70.308 59.17 26.901v13.494z' data-mit-no-recolor='true'/><path fill='#fafafa' d='M128.799 89.404c-.07 11.451-5.525 23.209-14.638 28.908l-37.358 24.38V64.038L113.7 47.594c4.504-2.103 8.3-1.248 10.823 2.715 1.658 2.618 2.523 6.31 2.496 10.657a31.15 31.15 0 0 1-3.954 15.546c3.745 1.745 5.814 5.717 5.735 12.892zm-11.268-23.576c0-4.05-1.746-6.049-4.295-4.757L87.923 73.71l-.07 15.022 25.697-13.608c2.182-1.152 3.98-5.159 3.98-9.296zm1.876 28.14c0-4.983-2.112-7.192-5.717-5.175l-25.836 14.463v17.09l25.61-15.24c3.866-2.304 5.926-6.345 5.943-11.137z' data-mit-no-recolor='true'/><path fill='#424242' d='m76.856 17.874-58.21 20.031 95.812 2.078-73.337 8.039 35.735 15.99 18.12-8.073-24.204-1.196 43.45-7.385 21.054-9.374z' data-mit-no-recolor='true'/></g></g></svg>",
"gamemaker": "<svg viewBox='0 0 16 16'><path fill='#26a69a' d='M8 1.422 14.578 8h-3.759v3.853c-.94.846-1.88 1.785-2.819 2.725L1.422 8zM5.275 8 8 10.725V8h2.725A37 37 0 0 0 8 5.275 37 37 0 0 0 5.275 8'/></svg>",
"garden": "<svg viewBox='0 0 16 16'><g stroke-width='.752'><path fill='#80cbc4' d='M14 14V2h-1.716v12z'/><path fill='#80deea' d='M10.58 14V2H8.865v12z'/><path fill='#26a69a' d='M10.58 8.98V2H8.865v6.98z'/><path fill='#ff80ab' d='M10.608 10.034v-1.65H8.844v1.65z'/><path fill='#1565c0' d='M7.145 14V6.377H5.452V14z'/><path fill='#43a047' d='M5.451 14V2H3.716v12z'/><path fill='#4db6ac' d='M3.716 14V2H2v12z'/><path fill='#0288d1' d='M7.148 6.477V2H5.45v4.477z'/><path fill='#ff80ab' d='M7.145 8.162V6.504H5.452v1.658z'/><path fill='#4caf50' d='M12.296 14V2h-1.715v12z'/><path fill='#00bfa5' d='M8.864 14V2H7.148v12z'/></g></svg>",
- "gatsby": "<svg viewBox='0 0 28 28'><path fill='#FAFAFA' d='M23.835 14h-6.259v1.788h4.292c-.626 2.682-2.593 4.918-5.186 5.812L6.4 11.318c1.073-3.13 4.113-5.365 7.6-5.365 2.682 0 5.096 1.342 6.616 3.398l1.341-1.162C20.17 5.775 17.308 4.165 14 4.165c-4.65 0-8.583 3.308-9.566 7.69l11.801 11.801c4.292-1.073 7.6-5.007 7.6-9.656m-19.67.09c0 2.503.984 4.917 2.861 6.794s4.381 2.861 6.795 2.861z'/><path fill='#6A1B9A' d='M14 1.483C7.116 1.483 1.483 7.116 1.483 14S7.116 26.517 14 26.517 26.517 20.884 26.517 14 20.884 1.483 14 1.483m-6.974 19.49c-1.877-1.877-2.86-4.38-2.86-6.794l9.745 9.656c-2.504-.09-5.007-.984-6.885-2.861zm9.12 2.594L4.433 11.854C5.417 7.474 9.351 4.165 14 4.165c3.308 0 6.17 1.61 7.957 4.024l-1.34 1.162C19.096 7.294 16.681 5.953 14 5.953c-3.487 0-6.437 2.236-7.6 5.365L16.682 21.6c2.593-.894 4.56-3.13 5.186-5.812h-4.292V14h6.259c0 4.65-3.308 8.583-7.69 9.567z'/></svg>",
+ "gatsby": "<svg viewBox='0 0 28 28'><path fill='#fafafa' d='M23.835 14h-6.259v1.788h4.292c-.626 2.682-2.593 4.918-5.186 5.812L6.4 11.318c1.073-3.13 4.113-5.365 7.6-5.365 2.682 0 5.096 1.342 6.616 3.398l1.341-1.162C20.17 5.775 17.308 4.165 14 4.165c-4.65 0-8.583 3.308-9.566 7.69l11.801 11.801c4.292-1.073 7.6-5.007 7.6-9.656m-19.67.09c0 2.503.984 4.917 2.861 6.794s4.381 2.861 6.795 2.861z'/><path fill='#6a1b9a' d='M14 1.483C7.116 1.483 1.483 7.116 1.483 14S7.116 26.517 14 26.517 26.517 20.884 26.517 14 20.884 1.483 14 1.483m-6.974 19.49c-1.877-1.877-2.86-4.38-2.86-6.794l9.745 9.656c-2.504-.09-5.007-.984-6.885-2.861zm9.12 2.594L4.433 11.854C5.417 7.474 9.351 4.165 14 4.165c3.308 0 6.17 1.61 7.957 4.024l-1.34 1.162C19.096 7.294 16.681 5.953 14 5.953c-3.487 0-6.437 2.236-7.6 5.365L16.682 21.6c2.593-.894 4.56-3.13 5.186-5.812h-4.292V14h6.259c0 4.65-3.308 8.583-7.69 9.567z'/></svg>",
"gcp": "<svg viewBox='0 0 300 300'><path fill='#f44336' d='M184.351 103.816h7.786l22.191-22.191 1.09-9.421a99.743 99.743 0 0 0-162.266 48.664 12.07 12.07 0 0 1 7.786-.467l44.382-7.32s2.258-3.737 3.426-3.503a55.36 55.36 0 0 1 75.76-5.762z'/><path fill='#448aff' d='M245.94 120.868a100 100 0 0 0-30.132-48.587l-31.146 31.146a55.36 55.36 0 0 1 20.323 43.914v5.529a27.72 27.72 0 1 1 0 55.438h-55.439l-5.528 5.606v33.248l5.528 5.528h55.439a72.101 72.101 0 0 0 40.956-131.822z'/><path fill='#43a047' d='M94.03 252.379h55.438v-44.382H94.03a27.6 27.6 0 0 1-11.446-2.492l-7.786 2.414-22.347 22.19-1.947 7.787a71.7 71.7 0 0 0 43.526 14.483'/><path fill='#ffc107' d='M94.03 108.41a72.101 72.101 0 0 0-43.526 129.252l32.158-32.157a27.72 27.72 0 1 1 36.673-36.673l32.158-32.158A72.02 72.02 0 0 0 94.03 108.41'/></svg>",
"gemfile": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M21.184 10.016V10H10.881l.016.033-.016-.017L8 14l8.032 10L24 14z'/><path fill='#e53935' d='m16 3.455 11 6.286v12.518l-11 6.286-11-6.286V9.741zM16 0 2 8v16l14 8 14-8V8z'/></svg>",
- "gemini-ai": "<svg viewBox='0 0 16 16'><path fill='#448AFF' d='M15 8.014A7.457 7.457 0 0 0 8.014 15h-.028A7.456 7.456 0 0 0 1 8.014v-.028A7.456 7.456 0 0 0 7.986 1h.028A7.457 7.457 0 0 0 15 7.986z'/></svg>",
+ "gemini-ai": "<svg viewBox='0 0 16 16'><path fill='#448aff' d='M15 8.014A7.457 7.457 0 0 0 8.014 15h-.028A7.456 7.456 0 0 0 1 8.014v-.028A7.456 7.456 0 0 0 7.986 1h.028A7.457 7.457 0 0 0 15 7.986z'/></svg>",
"gemini": "<svg viewBox='0 0 32 32'><path fill='#81c784' d='M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2m11.3 10h-5.64a22.5 22.5 0 0 0-2.705-7.616A12.03 12.03 0 0 1 27.3 12M20 16c0 .693-.037 1.357-.094 2h-7.811A22 22 0 0 1 12 16c0-.693.037-1.357.094-2h7.811c.058.643.095 1.307.095 2m-4 12c-.115 0-.226-.014-.34-.017A20.4 20.4 0 0 1 12.368 20h7.264a20.4 20.4 0 0 1-3.292 7.983c-.114.003-.225.017-.34.017m-3.632-16a20.4 20.4 0 0 1 3.292-7.983c.114-.003.225-.017.34-.017s.226.014.34.017A20.4 20.4 0 0 1 19.632 12Zm.683-7.618A22.4 22.4 0 0 0 10.339 12H4.7a12.03 12.03 0 0 1 8.35-7.618ZM4.18 14h5.91c-.052.647-.091 1.307-.091 2s.039 1.353.091 2H4.18a11.2 11.2 0 0 1 0-4m.52 6h5.638a22.4 22.4 0 0 0 2.712 7.618A12.03 12.03 0 0 1 4.7 20m14.255 7.616A22.5 22.5 0 0 0 21.661 20H27.3a12.03 12.03 0 0 1-8.344 7.616ZM27.819 18h-5.91c.052-.647.091-1.307.091-2s-.039-1.353-.091-2h5.91a11.2 11.2 0 0 1 0 4'/></svg>",
"git": "<svg viewBox='0 0 32 32'><path fill='#e64a19' d='M13.172 2.828 11.78 4.22l1.91 1.91 2 2A2.986 2.986 0 0 1 20 10.81a3.25 3.25 0 0 1-.31 1.31l2.06 2a2.68 2.68 0 0 1 3.37.57 2.86 2.86 0 0 1 .88 2.117 3.02 3.02 0 0 1-.856 2.109A2.9 2.9 0 0 1 23 19.81a2.93 2.93 0 0 1-2.13-.87 2.694 2.694 0 0 1-.56-3.38l-2-2.06a3 3 0 0 1-.31.12V20a3 3 0 0 1 1.44 1.09 2.92 2.92 0 0 1 .56 1.72 2.88 2.88 0 0 1-.878 2.128 2.98 2.98 0 0 1-2.048.871 2.981 2.981 0 0 1-2.514-4.719A3 3 0 0 1 16 20v-6.38a2.96 2.96 0 0 1-1.44-1.09 2.9 2.9 0 0 1-.56-1.72 2.9 2.9 0 0 1 .31-1.31l-3.9-3.9-7.579 7.572a4 4 0 0 0-.001 5.658l10.342 10.342a4 4 0 0 0 5.656 0l10.344-10.344a4 4 0 0 0 0-5.656L18.828 2.828a4 4 0 0 0-5.656 0'/></svg>",
"github-actions-workflow": "<svg viewBox='0 0 32 32'><path fill='#78909c' d='M26 18h-6a2 2 0 0 0-2 2v2h-6a2 2 0 0 1-2-2v-6h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2v6a4 4 0 0 0 4 4h6v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2M6.5 12a.5.5 0 0 1-.5-.5v-5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5ZM26 25.5a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5Z'/></svg>",
@@ -695,34 +702,34 @@
"gitlab": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='m29.532 13.083-.037-.105-3.811-10.322a1 1 0 0 0-.392-.49.985.985 0 0 0-1.39.316 1 1 0 0 0-.122.28L21.208 10H10.792L8.22 2.762a1.004 1.004 0 0 0-1.246-.721 1 1 0 0 0-.266.124 1 1 0 0 0-.392.491L2.507 12.98l-.04.103a7.52 7.52 0 0 0 2.348 8.491l.015.012.032.026 5.797 4.511 2.876 2.257 1.747 1.372a1.146 1.146 0 0 0 1.424 0l1.747-1.372 2.876-2.257 5.838-4.537.016-.013a7.52 7.52 0 0 0 2.35-8.49Z'/><path fill='#ef6c00' d='m29.532 13.083-.037-.105a12.6 12.6 0 0 0-5.123 2.394l-8.367 6.57 5.327 4.181 5.839-4.537.016-.013a7.52 7.52 0 0 0 2.345-8.49'/><path fill='#f9a825' d='m10.659 26.123 2.876 2.257 1.747 1.372a1.146 1.146 0 0 0 1.424 0l1.747-1.372 2.876-2.257L16 21.943Z'/><path fill='#ef6c00' d='M7.628 15.371a12.6 12.6 0 0 0-5.12-2.39l-.04.102a7.52 7.52 0 0 0 2.347 8.491l.015.012.032.026 5.797 4.511 5.331-4.18Z'/></svg>",
"gitpod": "<svg viewBox='0 0 32 32'><path fill='#ffa726' d='M18.258 4.156a2.6 2.6 0 0 1-.951 3.538l-.016.01-7.757 4.428a.66.66 0 0 0-.331.57v6.953a.66.66 0 0 0 .331.57l6.14 3.507a.66.66 0 0 0 .652 0l6.14-3.506a.66.66 0 0 0 .331-.571v-4.323l-5.52 3.11a2.6 2.6 0 0 1-2.57-4.518l.014-.009 7.898-4.452A3.61 3.61 0 0 1 28 12.603v7.58a4.95 4.95 0 0 1-2.495 4.295l-7.048 4.024a4.95 4.95 0 0 1-4.914 0l-7.048-4.024A4.95 4.95 0 0 1 4 20.182v-8.007A4.95 4.95 0 0 1 6.495 7.88l8.214-4.692a2.603 2.603 0 0 1 3.55.968Z'/></svg>",
"gleam": "<svg viewBox='0 0 24 24'><path fill='#ea80fc' d='m12.717 2.53-.027.005-.438.092-.22.334.04.41-.187.307.041.052s.063.077.031.196c-.016.06-.114.318-.205.543s-.176.43-.176.43l-.017.042.021.038a.55.55 0 0 0 .026.517c.057.1.035.2-.006.287a.6.6 0 0 1-.1.147l-.013.015-.147.432-.023.182.029.066-.057.045-.304.945-.036.283-.097.399-.196.56-.05.293-.082.342-.118.27-.084.156-.152.46-.111.49.492.349.85-.123.584-.766.28-.771.182-.852.112-.63.066-.339.174-.806.053-.473.127-.39.021-.329.082-.576.037-.476.084-.274.055-.625-.031-.146.119-.368-.078-.093.03-.17-.144-.31zM2.059 5.442l-.04.018-.575.227-.258.283.893 1.142.326.032.267.453.457.172.069.16.357.285.932.678.664.433 1.312 1.055.112.209.365.201.467.399.588.517.101.223.625.043.656-.85-.006-.605-1.738-1.395-1.27-.922-1.245-.838-.954-.515-.056-.14-1.065-.628-.406-.324zm17.912 5.528-.152.134-.233-.062-.959.14-.47-.109.02.131c-.116-.065-.24-.049-.343-.035-.105.014-.187.019-.232-.006-.124-.069-.261-.034-.361.004-.07.026-.09.043-.12.06l-.263-.15-.373.166-.4-.04-.42-.018-.45.08-.252-.063-.617.067-.326-.084-.04.048s-.027.036-.062.063a.2.2 0 0 1-.047.025c-.01.003-.004.002-.004.002-.087-.05-.178-.04-.253-.031-.075.008-.135.021-.135.021l-.008.004-.248.118-.795.132-.021.028s-.12.143-.227.304a1.5 1.5 0 0 0-.133.246.4.4 0 0 0-.031.124.17.17 0 0 0 .053.132c-.002-.001.01.02.011.065a1 1 0 0 1-.021.164 2 2 0 0 1-.068.226l-.026.063.461.367.47.063.29-.094.95-.174.185-.076.107.037.588-.053.19.106.124-.108.116.037.918-.138.402.125-.027-.147c.04.018.076.027.12.03q.08.004.182-.002c.134-.008.293-.028.448-.05.295-.04.548-.089.57-.093l.19.066.853-.127.527-.156.363.117.407-.058.224-.215.54.135-.018-.135s-.003-.062.027-.106c.015-.021.034-.04.084-.054a.6.6 0 0 1 .25.01c.276.058.45.023.557-.043a.3.3 0 0 0 .107-.106.3.3 0 0 0 .03-.066l.01-.043-.122-.158.053-.19-.338-.45-.576-.11-.358.005-.039-.062-.047-.002s-.247-.022-.449.045c-.107.036-.234-.072-.234-.072l-.139.127zM9.03 13.117l-.033.018-.658.35-.23.22-.173.356.203.572.319.295.306.465-3.802 3.158-.12.17-.798.615-.328.334-.559.422-.09.135-.152.445.385.531.459-.31.207.093.166-.199.101.047.729-.572.238-.133.287-.068.344-.266-.04-.05.206-.07.416-.359.273-.076.328-.224.141-.268.031-.021.112.048.52-.546.54-.292 1.102-.923.457 1.283.252.375.146.629.213.082-.012.033.112.75.193.18-.08.326.316.164a.25.25 0 0 0 .016.156c.026.067.072.147.148.254.164.23.369.29.522.277a.6.6 0 0 0 .262-.09l.023-.015.186-.445-.256-1.391-.225-1.092-.275-.592-.194-.552v-.002c0-.002-.051-.155-.105-.336a4 4 0 0 1-.12-.487 1.1 1.1 0 0 0-.103-.373l-.002-.003-.142-.301.19-.178.558-.648.08-.729-.688-.762-.484.196-.379.406-.582-.867z'/></svg>",
- "gnuplot": "<svg viewBox='0 -16 16 16'><path fill='#1E88E5' d='M2-1c-.5 0-1-.5-1-1v-12c0-.5.5-1 1-1h12c.5 0 1 .5 1 1v12c0 .5-.5 1-1 1zm0-2v1h12v-7L9-4 6-7zm0-2 4-4 3 3 5-5v-3H2z'/></svg>",
+ "gnuplot": "<svg viewBox='0 -16 16 16'><path fill='#1e88e5' d='M2-1c-.5 0-1-.5-1-1v-12c0-.5.5-1 1-1h12c.5 0 1 .5 1 1v12c0 .5-.5 1-1 1zm0-2v1h12v-7L9-4 6-7zm0-2 4-4 3 3 5-5v-3H2z'/></svg>",
"go-mod": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M2 12h4v2H2zm-2 4h6v2H0zm4 4h2v2H4zm16.954-5H14v3h3.239a4.42 4.42 0 0 1-3.531 2 2.65 2.65 0 0 1-2.053-.858 2.86 2.86 0 0 1-.628-2.28A4.515 4.515 0 0 1 15.292 13a2.73 2.73 0 0 1 1.749.584l2.962-1.185A5.6 5.6 0 0 0 15.292 10a7.526 7.526 0 0 0-7.243 6.5 5.614 5.614 0 0 0 5.659 6.5 7.526 7.526 0 0 0 7.243-6.5 6.4 6.4 0 0 0 .003-1.5'/><path fill='#ec407a' d='M26.292 10a7.526 7.526 0 0 0-7.243 6.5 5.614 5.614 0 0 0 5.659 6.5 7.526 7.526 0 0 0 7.243-6.5 5.614 5.614 0 0 0-5.659-6.5m2.681 6.137A4.515 4.515 0 0 1 24.708 20a2.65 2.65 0 0 1-2.053-.858 2.86 2.86 0 0 1-.628-2.28A4.515 4.515 0 0 1 26.292 13a2.65 2.65 0 0 1 2.053.858 2.86 2.86 0 0 1 .628 2.28Z'/></svg>",
"go": "<svg viewBox='0 0 32 32'><path fill='#00acc1' d='M2 12h4v2H2zm-2 4h6v2H0zm4 4h2v2H4zm16.954-5H14v3h3.239a4.42 4.42 0 0 1-3.531 2 2.65 2.65 0 0 1-2.053-.858 2.86 2.86 0 0 1-.628-2.28A4.515 4.515 0 0 1 15.292 13a2.73 2.73 0 0 1 1.749.584l2.962-1.185A5.6 5.6 0 0 0 15.292 10a7.526 7.526 0 0 0-7.243 6.5 5.614 5.614 0 0 0 5.659 6.5 7.526 7.526 0 0 0 7.243-6.5 6.4 6.4 0 0 0 .003-1.5'/><path fill='#00acc1' d='M26.292 10a7.526 7.526 0 0 0-7.243 6.5 5.614 5.614 0 0 0 5.659 6.5 7.526 7.526 0 0 0 7.243-6.5 5.614 5.614 0 0 0-5.659-6.5m2.681 6.137A4.515 4.515 0 0 1 24.708 20a2.65 2.65 0 0 1-2.053-.858 2.86 2.86 0 0 1-.628-2.28A4.515 4.515 0 0 1 26.292 13a2.65 2.65 0 0 1 2.053.858 2.86 2.86 0 0 1 .628 2.28Z'/></svg>",
- "go_gopher": "<svg viewBox='0 0 24 24'><g fill='#4DD0E1'><path d='M10.575 1.695c-2.634 0-4.756 2.453-4.756 5.502v4.6l-.027-.003v4.71c0 3.05 2.122 5.502 4.756 5.502h2.287c2.634 0 4.756-2.453 4.756-5.502v-4.6l.027.003v-4.71c0-3.049-2.122-5.502-4.756-5.502z'/><rect width='2.289' height='3.335' x='-1.177' y='6.093' ry='1.125' transform='matrix(.48489 -.87457 .85979 .51065 0 0)'/><rect width='2.297' height='3.39' x='10.261' y='-15.076' ry='1.143' transform='matrix(.44646 .8948 -.89204 .45195 0 0)'/></g><g data-mit-no-recolor='true' transform='translate(.282 -.134)'><circle cx='9.267' cy='5.13' r='2.054' fill='#FAFAFA' stroke='#616161' stroke-width='.1'/><circle cx='14.214' cy='5.116' r='2.054' fill='#FAFAFA' stroke='#616161' stroke-width='.1'/><ellipse cx='8.039' cy='5.051' fill='#212121' rx='.792' ry='.901'/><path fill='#FAFAFA' stroke='#FAFAFA' stroke-width='.155' d='m11.792 9.556.763.138a.403.689 0 0 1 .008.138.403.689 0 0 1-.403.69.403.689 0 0 1-.403-.69.403.689 0 0 1 .035-.276z'/><ellipse cx='8.51' cy='5.365' fill='#FAFAFA' rx='.138' ry='.166'/><ellipse cx='12.945' cy='5.189' fill='#212121' rx='.792' ry='.901'/><ellipse cx='13.414' cy='5.446' fill='#FAFAFA' rx='.138' ry='.166'/><ellipse cx='-12.982' cy='-3.409' fill='#FFE0B2' rx='.708' ry='1.026' transform='rotate(-129.403)'/><path fill='#FAFAFA' stroke='#FAFAFA' stroke-width='.153' d='m11.772 9.553-.757.135a.4.672 0 0 0-.008.134.4.672 0 0 0 .4.673.4.672 0 0 0 .4-.672.4.672 0 0 0-.035-.27z'/><g fill='#FFE0B2'><ellipse cx='1.841' cy='-21.563' rx='.707' ry='1.026' transform='scale(1 -1)rotate(50.597)'/><ellipse cx='-17.281' cy='-21.784' rx='.864' ry='1.27' transform='matrix(.3054 -.95222 -.97065 -.24051 0 0)'/><ellipse cx='22.885' cy='2.587' rx='.864' ry='1.27' transform='matrix(.22652 .97401 .95652 -.29167 0 0)'/><path stroke='#546E7A' stroke-width='.1' d='M10.708 8.392a.594.594 0 0 0-.594.597v.115c0 .331.265.598.594.598h.386a.973.772 0 0 1 .697-.235.973.772 0 0 1 .698.235h.334c.33 0 .595-.267.595-.598V8.99a.595.595 0 0 0-.595-.597h-2.115z'/></g><ellipse cx='11.734' cy='8.203' fill='#212121' stroke='#FAFAFA' stroke-width='.162' rx='1.208' ry='.68'/></g></svg>",
+ "go_gopher": "<svg viewBox='0 0 24 24'><g fill='#4dd0e1'><path d='M10.575 1.695c-2.634 0-4.756 2.453-4.756 5.502v4.6l-.027-.003v4.71c0 3.05 2.122 5.502 4.756 5.502h2.287c2.634 0 4.756-2.453 4.756-5.502v-4.6l.027.003v-4.71c0-3.049-2.122-5.502-4.756-5.502z'/><rect width='2.289' height='3.335' x='-1.177' y='6.093' ry='1.125' transform='matrix(.48489 -.87457 .85979 .51065 0 0)'/><rect width='2.297' height='3.39' x='10.261' y='-15.076' ry='1.143' transform='matrix(.44646 .8948 -.89204 .45195 0 0)'/></g><g data-mit-no-recolor='true' transform='translate(.282 -.134)'><circle cx='9.267' cy='5.13' r='2.054' fill='#fafafa' stroke='#616161' stroke-width='.1'/><circle cx='14.214' cy='5.116' r='2.054' fill='#fafafa' stroke='#616161' stroke-width='.1'/><ellipse cx='8.039' cy='5.051' fill='#212121' rx='.792' ry='.901'/><path fill='#fafafa' stroke='#fafafa' stroke-width='.155' d='m11.792 9.556.763.138a.403.689 0 0 1 .008.138.403.689 0 0 1-.403.69.403.689 0 0 1-.403-.69.403.689 0 0 1 .035-.276z'/><ellipse cx='8.51' cy='5.365' fill='#fafafa' rx='.138' ry='.166'/><ellipse cx='12.945' cy='5.189' fill='#212121' rx='.792' ry='.901'/><ellipse cx='13.414' cy='5.446' fill='#fafafa' rx='.138' ry='.166'/><ellipse cx='-12.982' cy='-3.409' fill='#ffe0b2' rx='.708' ry='1.026' transform='rotate(-129.403)'/><path fill='#fafafa' stroke='#fafafa' stroke-width='.153' d='m11.772 9.553-.757.135a.4.672 0 0 0-.008.134.4.672 0 0 0 .4.673.4.672 0 0 0 .4-.672.4.672 0 0 0-.035-.27z'/><g fill='#ffe0b2'><ellipse cx='1.841' cy='-21.563' rx='.707' ry='1.026' transform='rotate(-50.597)scale(1 -1)'/><ellipse cx='-17.281' cy='-21.784' rx='.864' ry='1.27' transform='matrix(.3054 -.95222 -.97065 -.24051 0 0)'/><ellipse cx='22.885' cy='2.587' rx='.864' ry='1.27' transform='matrix(.22652 .97401 .95652 -.29167 0 0)'/><path stroke='#546e7a' stroke-width='.1' d='M10.708 8.392a.594.594 0 0 0-.594.597v.115c0 .331.265.598.594.598h.386a.973.772 0 0 1 .697-.235.973.772 0 0 1 .698.235h.334c.33 0 .595-.267.595-.598V8.99a.595.595 0 0 0-.595-.597h-2.115z'/></g><ellipse cx='11.734' cy='8.203' fill='#212121' stroke='#fafafa' stroke-width='.162' rx='1.208' ry='.68'/></g></svg>",
"godot-assets": "<svg viewBox='0 0 32 32'><path fill='#66bb6a' d='m31.75 10.745-2.5-4.33a.5.5 0 0 0-.683-.183l-2.793 1.612A10 10 0 0 0 24 6.838V2.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.5.5V6h-4V2.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.5.5v4.338a10 10 0 0 0-1.725.97l-2.73-1.576a.5.5 0 0 0-.683.183l-2.5 4.33a.5.5 0 0 0 .183.683L2.6 12.614A10 10 0 0 0 2 16v3l5 1 1 4h4l1-4h6l1 4h4l1-4 5-1v-3a10 10 0 0 0-.58-3.332l2.147-1.24a.5.5 0 0 0 .183-.683M9 18a3 3 0 1 1 3-3 3 3 0 0 1-3 3m9-2h-4v-2h4Zm5 2a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/><path fill='#66bb6a' d='m26 22-1 4h-6l-1-4h-4l-1 4H7l-1-4-4-1v3a4 4 0 0 0 4 4h20a4 4 0 0 0 4-4v-3Z'/></svg>",
"godot": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='m26 22-1 4h-6l-1-4h-4l-1 4H7l-1-4-4-1v3a4 4 0 0 0 4 4h20a4 4 0 0 0 4-4v-3Z'/><path fill='#42a5f5' d='m31.75 10.745-2.5-4.33a.5.5 0 0 0-.683-.183l-2.793 1.612A10 10 0 0 0 24 6.838V2.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.5.5V6h-4V2.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.5.5v4.338a10 10 0 0 0-1.725.97l-2.73-1.576a.5.5 0 0 0-.683.183l-2.5 4.33a.5.5 0 0 0 .183.683L2.6 12.614A10 10 0 0 0 2 16v3l5 1 1 4h4l1-4h6l1 4h4l1-4 5-1v-3a10 10 0 0 0-.58-3.332l2.147-1.24a.5.5 0 0 0 .183-.683M9 18a3 3 0 1 1 3-3 3 3 0 0 1-3 3m9-2h-4v-2h4Zm5 2a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/></svg>",
"gradle": "<svg viewBox='0 0 32 32'><path fill='#0097a7' d='M16 10v2h6c-2 0-3-2-6-2'/><path fill='#0097a7' d='M26 4h-2a4 4 0 0 0-4 4h4a1 1 0 0 1 2 0v4H16v-2h-5.317A2.683 2.683 0 0 0 8 12.683v2.634A2.683 2.683 0 0 0 10.683 18H16v2h-5.98A4.02 4.02 0 0 1 6 16v-2c-2 0-4 4-4 8 0 5 1 6 2 6h4v-4h4v4h4v-4h4v4h4v-6a2 2 0 0 0 2-2v-2a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4m-4 12h-2a2 2 0 0 1-2-2h6a2 2 0 0 1-2 2'/></svg>",
- "grafana-alloy": "<svg viewBox='0 0 24 24'><path fill='#FF6F00' d='M20.173 8.483a14.7 14.7 0 0 0-3.287-3.92l-.025-.02a13 13 0 0 0-.784-.603C14.28 2.67 12.317 2 10.394 2 7.953 2 5.741 3.302 4.167 5.668 1.952 8.994 1.48 13.656 2.99 17.269c1.134 2.712 4.077 4.47 7.873 4.706q.415.024.833.025c1.757 0 3.531-.338 5.073-.975 1.962-.81 3.463-2.048 4.342-3.583 1.304-2.28.945-5.712-.938-8.96zm-8.871.508c.863 0 1.723.354 2.341 1.048.558.625.839 1.43.79 2.266a3.1 3.1 0 0 1-1.007 2.128l-.072.064a3.14 3.14 0 0 1-3.725.28 4.4 4.4 0 0 1-.745-.67 3 3 0 0 1-.17-.214 3.1 3.1 0 0 1-.416-.874l-.016-.057-.002-.007c-.277-1.08.04-2.339.905-3.138l.066-.061a3.12 3.12 0 0 1 2.05-.764zm-.908-5.84c1.683 0 3.418.598 5.018 1.73q.367.26.72.553l.386.348c2.95 2.744 3.873 5.42 3.642 8.189-.151 1.818-1.31 3.27-2.97 4.394-1.58 1.07-4 1.377-5.727 1.192-1.697-.182-3.456-.866-4.592-2.404-.939-1.273-1.218-2.64-1.091-4.107.127-1.459.712-2.823 1.662-3.772.533-.533 1.442-1.202 2.894-1.324-.68.156-1.33.48-1.887.976a4.29 4.29 0 0 0-1.378 3.869c.093.636.33 1.248.713 1.778a4.3 4.3 0 0 0 1.252 1.191c1.66 1.121 3.728 1.033 5.747-.306 1.1-.73 1.844-1.994 2.04-3.471.238-1.788-.336-3.623-1.575-5.033-1.347-1.533-3.212-2.44-5.116-2.49-1.77-.046-3.409.652-4.737 2.017-.407.417-.777.87-1.107 1.349q.358-.801.838-1.523C6.48 4.272 8.35 3.152 10.394 3.152z'/></svg>",
+ "grafana-alloy": "<svg viewBox='0 0 24 24'><path fill='#ff6f00' d='M20.173 8.483a14.7 14.7 0 0 0-3.287-3.92l-.025-.02a13 13 0 0 0-.784-.603C14.28 2.67 12.317 2 10.394 2 7.953 2 5.741 3.302 4.167 5.668 1.952 8.994 1.48 13.656 2.99 17.269c1.134 2.712 4.077 4.47 7.873 4.706q.415.024.833.025c1.757 0 3.531-.338 5.073-.975 1.962-.81 3.463-2.048 4.342-3.583 1.304-2.28.945-5.712-.938-8.96zm-8.871.508c.863 0 1.723.354 2.341 1.048.558.625.839 1.43.79 2.266a3.1 3.1 0 0 1-1.007 2.128l-.072.064a3.14 3.14 0 0 1-3.725.28 4.4 4.4 0 0 1-.745-.67 3 3 0 0 1-.17-.214 3.1 3.1 0 0 1-.416-.874l-.016-.057-.002-.007c-.277-1.08.04-2.339.905-3.138l.066-.061a3.12 3.12 0 0 1 2.05-.764zm-.908-5.84c1.683 0 3.418.598 5.018 1.73q.367.26.72.553l.386.348c2.95 2.744 3.873 5.42 3.642 8.189-.151 1.818-1.31 3.27-2.97 4.394-1.58 1.07-4 1.377-5.727 1.192-1.697-.182-3.456-.866-4.592-2.404-.939-1.273-1.218-2.64-1.091-4.107.127-1.459.712-2.823 1.662-3.772.533-.533 1.442-1.202 2.894-1.324-.68.156-1.33.48-1.887.976a4.29 4.29 0 0 0-1.378 3.869c.093.636.33 1.248.713 1.778a4.3 4.3 0 0 0 1.252 1.191c1.66 1.121 3.728 1.033 5.747-.306 1.1-.73 1.844-1.994 2.04-3.471.238-1.788-.336-3.623-1.575-5.033-1.347-1.533-3.212-2.44-5.116-2.49-1.77-.046-3.409.652-4.737 2.017-.407.417-.777.87-1.107 1.349q.358-.801.838-1.523C6.48 4.272 8.35 3.152 10.394 3.152z'/></svg>",
"grain": "<svg viewBox='0 0 32 32'><path fill='#ffa726' d='m16 2-4.97 9.782L16 16l5.094-4.218Z'/><path fill='#f57f17' d='M2 6.848V18.64L16 30l7.228-5.844Z'/><path fill='#f57c00' d='M30 18.645V6.71L17.334 17.235l7.284 5.808Z'/></svg>",
- "graphcool": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 300 300'><path fill='#4CAF50' stroke='#4CAF50' stroke-width='7.884' d='M246.886 107.727c-12.237-6.892-27.616 2.1-30.081 3.646l-52.834 29.965c-7.8-6.196-18.914-5.933-26.412.625-7.499 6.558-9.24 17.537-4.14 26.094 5.102 8.556 15.588 12.246 24.923 8.768s14.852-13.129 13.111-22.937l52.688-29.9.321-.196c3.464-2.188 11.5-5.462 15.256-3.34 2.706 1.524 4.252 6.629 4.376 14.148h-.066v66.092a17.31 17.31 0 0 1-8.635 14.95l-75.739 43.755a17.31 17.31 0 0 1-17.261 0l-75.74-43.756a17.31 17.31 0 0 1-8.634-14.95V113.22c.01-6.165 3.3-11.86 8.634-14.95l68.549-39.562c6.522 7.482 17.451 9.25 26 4.206s12.283-15.468 8.886-24.794-12.962-14.904-22.751-13.27c-9.79 1.636-17.022 10.02-17.204 19.944L59.397 85.632a31.93 31.93 0 0 0-15.978 27.588v87.454a31.93 31.93 0 0 0 15.927 27.602l75.74 43.755a31.93 31.93 0 0 0 31.846 0l75.74-43.755a31.93 31.93 0 0 0 15.927-27.58V137.12h.05c.373-14.913-3.616-24.794-11.762-29.389z'/></svg>",
+ "graphcool": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 300 300'><path fill='#4caf50' stroke='#4caf50' stroke-width='7.884' d='M246.886 107.727c-12.237-6.892-27.616 2.1-30.081 3.646l-52.834 29.965c-7.8-6.196-18.914-5.933-26.412.625-7.499 6.558-9.24 17.537-4.14 26.094 5.102 8.556 15.588 12.246 24.923 8.768s14.852-13.129 13.111-22.937l52.688-29.9.321-.196c3.464-2.188 11.5-5.462 15.256-3.34 2.706 1.524 4.252 6.629 4.376 14.148h-.066v66.092a17.31 17.31 0 0 1-8.635 14.95l-75.739 43.755a17.31 17.31 0 0 1-17.261 0l-75.74-43.756a17.31 17.31 0 0 1-8.634-14.95V113.22c.01-6.165 3.3-11.86 8.634-14.95l68.549-39.562c6.522 7.482 17.451 9.25 26 4.206s12.283-15.468 8.886-24.794-12.962-14.904-22.751-13.27c-9.79 1.636-17.022 10.02-17.204 19.944L59.397 85.632a31.93 31.93 0 0 0-15.978 27.588v87.454a31.93 31.93 0 0 0 15.927 27.602l75.74 43.755a31.93 31.93 0 0 0 31.846 0l75.74-43.755a31.93 31.93 0 0 0 15.927-27.58V137.12h.05c.373-14.913-3.616-24.794-11.762-29.389z'/></svg>",
"graphql": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M6 20h20v2H6z'/><circle cx='7' cy='21' r='3' fill='#ec407a'/><circle cx='16' cy='27' r='3' fill='#ec407a'/><circle cx='25' cy='21' r='3' fill='#ec407a'/><path fill='#ec407a' d='M6 10h20v2H6z'/><circle cx='7' cy='11' r='3' fill='#ec407a'/><circle cx='16' cy='5' r='3' fill='#ec407a'/><circle cx='25' cy='11' r='3' fill='#ec407a'/><path fill='#ec407a' d='M6 12h2v10H6zm18-2h2v12h-2z'/><path fill='#ec407a' d='m5.014 19.41 11.674 6.866L15.674 28 4 21.134z'/><path fill='#ec407a' d='M26.688 21.724 15.014 28.59 14 26.866 25.674 20zM5.124 10.382l11.415-7.29 1.077 1.686L6.2 12.068z'/><path fill='#ec407a' d='m25.798 12.067-11.415-7.29 1.077-1.685 11.415 7.29zM6.2 19.932l11.416 7.29-1.077 1.686-11.415-7.29z'/><path fill='#ec407a' d='m26.875 21.619-11.415 7.29-1.077-1.687 11.415-7.289zM5.877 22.6 16.04 3.686l1.762.946L7.638 23.546z'/><path fill='#ec407a' d='M24.361 23.545 14.197 4.633l1.761-.947 10.165 18.913z'/></svg>",
"gridsome": "<svg viewBox='0 0 32 32'><circle cx='16' cy='16' r='2' fill='#00bfa5'/><path fill='#00bfa5' d='M27.96 14H22v4h3.798A9.998 9.998 0 1 1 18 6.202V2.159A13.994 13.994 0 1 0 30 16v-.02A2.02 2.02 0 0 0 27.96 14'/></svg>",
"groovy": "<svg viewBox='0 0 32 32'><path fill='#26c6da' d='M19.322 2a6.5 6.5 0 0 1 4.352 1.419 4.55 4.55 0 0 1 1.685 3.662 5.82 5.82 0 0 1-1.886 4.275 6.04 6.04 0 0 1-4.34 1.846 4.15 4.15 0 0 1-2.385-.649 1.91 1.91 0 0 1-.936-1.603 1.6 1.6 0 0 1 .356-1.024 1.1 1.1 0 0 1 .861-.447q.469 0 .468.504a.79.79 0 0 0 .358.693 1.43 1.43 0 0 0 .826.245 3.1 3.1 0 0 0 2.39-1.573 5.66 5.66 0 0 0 1.154-3.39 2.64 2.64 0 0 0-.891-2.064 3.28 3.28 0 0 0-2.293-.812 6.18 6.18 0 0 0-4.086 1.736 12.9 12.9 0 0 0-3.215 4.557 13.4 13.4 0 0 0-1.233 5.36 5.86 5.86 0 0 0 1.091 3.723 3.53 3.53 0 0 0 2.905 1.372q3.058 0 5.848-4.002l2.935-.388q.546-.07.545.246a8 8 0 0 1-.423 1.24q-.421 1.097-1.152 3.668A12.7 12.7 0 0 0 26 17.72v1.66a14.2 14.2 0 0 1-4.055 2.57 10.38 10.38 0 0 1-2.764 5.931 6.7 6.7 0 0 1-4.806 2.11 3.3 3.3 0 0 1-2.012-.55 1.8 1.8 0 0 1-.718-1.514q0-2.685 5.634-5.212.532-1.766 1.152-3.507a8.6 8.6 0 0 1-2.853 2.323 7.4 7.4 0 0 1-3.48 1.01 5.46 5.46 0 0 1-4.366-2.093A8.1 8.1 0 0 1 6 15.122a11.6 11.6 0 0 1 1.966-6.426 14.7 14.7 0 0 1 5.162-4.862A12.44 12.44 0 0 1 19.322 2m-2.407 22.17q-4.055 1.875-4.054 3.695a.87.87 0 0 0 .999.97q1.964 0 3.055-4.665'/></svg>",
- "grunt": "<svg viewBox='0 0 300 300'><path fill='#F9A825' d='M61.045 79.22s4.718 18.193 5.39 24.259c.675 6.064-12.128 19.54-12.128 26.279s2.691 18.87 17.518 24.935 16.847 21.563 16.847 31c0 9.433-5.39 27.626-5.39 27.626s-3.368 45.148 14.825 53.234 14.151 8.086 18.192 8.086c4.037 0 33.688 12.136 51.21 7.416 17.523-4.711 22.234-10.105 22.234-10.105s17.523 1.348 22.908-14.151c5.394-15.492 8.757-47.84 6.735-62.665s-4.72-34.362 7.408-41.105c12.129-6.734 24.931-14.825 22.908-21.56S236.9 116.297 236.9 116.297s-2.697-16.84-.683-20.885c2.023-4.038 14.151-14.152 7.409-21.56-6.734-7.409-18.871-7.409-18.871-7.409l-15.491 1.34s-26.27-23.59-60.641-22.242c-34.346 1.348-55.914 22.234-55.914 22.234s-11.454-4.044-21.562 0c-10.114 4.045-12.128 0-10.106 11.454z'/><g fill='#e78724'><path d='M122.623 179.552s-13.345 20.404-23.133 34.884c-1.514 2.24 9.788 2.395 9.788 2.395l13.346-37.28zm6.353-42.303s-4.022 10.035-6.226 15.355c-2.392 5.783-11.511 8.66-11.511 8.66s4.728-4.958 4.728-17.519c0-5.262 6.802-7.359 6.802-7.359l6.208.872z'/><path d='M124.909 142.92s-8.242 9.81-23.544 4.317c-15.306-5.493-16.874-25.117-16.874-30.611s29.04 5.494 32.964 7.849 11.378 11.772 7.454 18.445M67.104 38.09s14.126-2.75 23.544 4.707c9.422 7.456 12.165 13.344 12.165 13.344L86.939 68.724s-7.67-3.557-10.026-13.761c-2.353-10.203-.783-14.911-9.81-16.873z'/><path d='M120.79 114.585c-6.084-8.634-16.107-12.4-24.11-13.025-13.646-1.066-20.125 3.758-20.125 3.758s22.349.946 28.79 9.407c11.992 15.746 29.487 17.025 32.284 13.567-3.235.161-6.793-1.442-10.602-5.043-2.38-2.253-4.309-5.924-6.239-8.664zm-37.73 30.528s-24.795-8.525-20.084-17.156c4.71-8.635.962-13.34.962-13.34s-13.847 10.337-10.32 21.324c2.17 6.742 5.808 11.626 25.783 20.984 9.58 17.793 1.978 49.829 2.434 49.977.759.246 13.452-6.274 12.585-36.246-.263-9.209-.995-21.765-11.355-25.555zm32.495-91.666c-10.398 5.283-24.642 12.638-24.642 12.638l5.606 2.804c-.534.868-.793 1.385-.793 1.385l5.748 2.616s14.044 10.176 22.653 16.717c-2.87-8.835-11.298-22.717-11.298-22.717s-2.393-6.378 2.713-13.444zm34.54 215.317c-31.958 0-42.783-16.223-42.783-16.223l4.995 12.161c1.888 5.263 22.027 21.634 37.74 21.634'/><path d='M110.815 241.575s2.94 17.97 14.713 17.97c10.004 0 12.952-15.179 12.952-18.316l-27.666.346zm-19.593-27.1c1.046-2.88 4.709-14.522 5.885-25.116 1.406-12.69.897-31.249.897-31.249s-3.141 12.166-5.896 14.132c-2.746 1.96-7.853 15.302-7.853 15.302s-2.738 19.862-4.037 26.538c3.602.722 6.628-1.4 10.986.393zm-28.43-78.732s6.668-2.356 8.634-16.874c1.96-14.521-2.357-27.079-2.357-27.079s.392 20.014-1.961 23.94c-2.357 3.922-4.317 5.1-7.065 10.204-2.749 5.103 1.176 8.239 2.749 9.81z'/></g><path fill='#FFCC80' d='m70.755 41.524-1.361-.864c-4.015.137 1.362.864 1.362.864z'/><path fill='#F9A825' d='M120.987 49.146s-6.278-1.046-9.419-5.754c-3.14-4.71-12.034-14.13-26.213-14.235-14.176-.107-28.204 3.768-28.204 3.768l12.244 7.73c1.296-.042 3.56-.025 7.305.098 15.366.51 17.732 15.705 17.732 15.705l54.162-7.457'/><path fill='#FAFAFA' d='M126.216 133.094s-7.326 7.326-18.576 4.97c-11.251-2.354-18.055-7.325-18.578-15.96-.523-8.634 2.357-12.034 2.357-12.034s12.564 3.93 18.583 9.16c6.019 5.238 10.467 13.608 16.223 13.871z'/><path fill='#BDBDBD' d='M117.352 130.48s-6.353 2.902-13.455.537c-9.297-3.1-9.917-12.77-9.917-12.77s-5.702 16.737 10.287 19.959c12.825 2.583 20.41-4.848 20.41-4.848z'/><g fill='#4E342E'><path d='M121.866 45.596c-.684-14.354 11.083-25.787 15.16-26.272.225 2.432-.227 11.919 3.848 14.836 1.187-7.746 5.2-16.29 21.045-21.889-3.394 9-.453 16.54.947 20.034 10.82-9.82 18.11-8.198 18.11-8.198s-7.518 12.973-4.577 21.486c-21.721-1.704-54.427 2.307-54.533.003M75.817 67.459c-5.182-4.3-1.32-8.29-1.729-13.175-.222-2.599-.427-5.052-1.285-6.406-4.77-7.53-16.018-9.815-16.133-9.839l-5.279-1.034 3.495-4.09c.452-.53 14.809-14.225 37.026-7.055 15.541 5.013 27.044 22.007 27.472 22.695l-7.787 3.663c-.09-.145-9.184-16.119-22.513-20.417-10.772-3.478-20.705-.17-24.964 1.998 4.522 1.806 10.434 5.094 14.003 10.72 1.686 2.655 1.965 6.002 2.237 9.24.361 4.288.698 8.336 3.856 10.957l-8.387 2.743zm-13.842 62.765s6.53-8.097 8.464-23.852c.76-6.206-.59-18.446-2.749-23.155.392 11.578-2.55 23.155-2.55 23.155s-1.982 21.497-3.158 23.852z'/><path d='M96.41 59.281c-.004-.016-2.096-9.158-7.894-13.773-8.182-6.507-18.17-3.045-18.267-3.015l-1.447-4.481c.485-.156 11.906-3.754 22.423 3.642 10.582 7.439 11.857 14.181 11.964 14.926l-6.776 2.701zm-13.923 65.136s-3.426-4.904-2.81-10.297c.307-2.664 1.541-5.397 3.745-6.367 4.094-1.793 5.912 2.845 5.912 2.845s-5.764 1.422-6.842 13.822z'/><path d='M120.099 131.893c.143.268.216.417.216.417s-10.685 8.019-21.634-.118c-5.863-4.358-5.308-15.138-3.513-19.71 4.67 1.061 8.63 2.969 11.941 5.197-4.183-3.733-7.846-6.48-7.836-6.545l-4.755-1.349-9.242-3.165c-.119.707-3.49 16.51 6.24 27.274 4.314 4.777 24.533 15.467 34.133-2.812l-5.55.83z'/><path d='M119.095 133.07c6.016 9.155 21.194 3.923 20.933-5.757 0 0-10.142 5.598-19.101-4.708-5.232-6.01-21.996-22.604-44.727-17.086 15.698 3.453 36.48 17.793 42.895 27.562z'/><path d='M74.077 158.905c1.447.836 6.55 6.121 6.553 14.027 0 3.964-.395 23.879-2.357 36.044 2.952 1.918 5.008 2.325 7.946 4.28 1.472-14.09 2.526-34.512 2.034-42.756-.559-9.398-5.591-15.705-10.253-18.394a187 187 0 0 0-4.523-2.498c-7.992-4.318-16.256-8.786-16.256-16.414 0-5.967 2.319-7.833 4.777-9.812l.264-.214c1.628-1.317 3.478-2.81 4.571-5.262 2.985-6.704 3.47-23.78 0-28.116-1.726-2.162-6.364-9.43-5.032-14.307.51-1.866 1.686-3.248 3.593-4.226 2.023-1.036 4.753-1.563 8.116-1.563 6.603 0 13.93 2.007 17.703 3.207 13.502 4.276 29.79 16.61 34.461 20.294l.23.172 2.985 2.08-1.866-3.132c-.115-.198-11.948-19.66-33.113-26.798-4.12-1.39-12.202-3.716-20.153-3.716-4.728 0-8.75.822-11.931 2.45-3.873 1.982-6.496 5.147-7.59 9.144-2.672 9.8 6.036 20.712 6.332 21.066 1.02 1.809 1.447 14.965-.897 20.236-.32.723-1.2 1.439-2.327 2.343-.345.28-.74.576-1.159.888-3.305 2.492-8.305 6.266-8.305 14.957 0 11.89 12.063 18.345 21.749 23.525 1.595.855 3.108 1.66 4.481 2.45z'/><path d='M75.817 67.459c-5.182-4.3-1.32-8.29-1.729-13.175-.222-2.599-.427-5.052-1.285-6.406-4.77-7.53-16.018-9.815-16.133-9.839l-5.279-1.034 3.495-4.09c.452-.53 14.809-14.225 37.026-7.055 15.541 5.013 27.044 22.007 27.472 22.695l-7.787 3.663c-.09-.145-9.184-16.119-22.513-20.417-10.772-3.478-20.705-.17-24.964 1.998 4.522 1.806 10.434 5.094 14.003 10.72 1.686 2.655 1.965 6.002 2.237 9.24.361 4.288.698 8.336 3.856 10.957l-8.387 2.743z'/><path d='m75.649 68.34-.335-.278c-3.843-3.182-3.161-6.331-2.501-9.365.3-1.398.617-2.854.488-4.342-.21-2.5-.41-4.86-1.164-6.052-4.539-7.17-15.516-9.464-15.623-9.489l-6.595-1.299 4.375-5.106c.082-.09 9.3-9.398 24.906-9.398 4.26 0 8.617.707 12.95 2.113 16.216 5.23 27.793 22.85 27.9 23.023l.469.748-9.218 4.342-.37-.584c-.987-1.686-9.727-16.1-22.094-20.088-3.034-.987-6.233-1.48-9.505-1.48-5.92 0-10.723 1.62-13.28 2.697 4.276 1.924 9.448 5.164 12.729 10.336 1.784 2.82 2.072 6.257 2.351 9.58.346 4.135.675 8.025 3.577 10.434l1.094.904-10.147 3.33z'/><path d='M96.41 59.281c-.004-.016-2.096-9.158-7.894-13.773-8.182-6.507-18.17-3.045-18.267-3.015l-1.447-4.481c.485-.156 11.906-3.754 22.423 3.642 10.582 7.439 11.857 14.181 11.964 14.926l-6.776 2.701z'/><path d='m95.842 60.35-.204-.9c-.021-.087-2.072-8.916-7.62-13.326-3.16-2.516-6.972-3.79-11.322-3.79-3.396 0-5.92.814-6.195.904l-.75.255-1.94-5.978.747-.238c.338-.107 3.364-1.053 7.656-1.053 5.657 0 11.002 1.661 15.466 4.802 10.854 7.63 12.178 14.678 12.293 15.459l.099.616-8.206 3.273zM76.696 40.762c4.648 0 8.905 1.43 12.301 4.131 4.926 3.923 7.242 10.846 7.943 13.33l5.337-2.128c-.483-1.885-2.61-7.54-11.512-13.798-4.257-2.993-9.156-4.509-14.562-4.509-2.967 0-5.27.472-6.41.76l.962 2.977a23.3 23.3 0 0 1 5.941-.763m48.339 214.47c-2.32 0-4.686-.84-6.326-2.26-1.256-1.083-5.501-4.984-7.374-9.761-.699-1.787-.543-3.215.472-4.245.699-.704 2.15-1.547 5.094-1.547l2.095-.003c2.5-.003 6.701-.003 10.295-.003h3.445c2.245 0 3.848.485 4.769 1.43 1.184 1.218.806 2.772.477 3.586-.576 1.43-2.96 6.085-7.006 10.402-1.447 1.554-3.552 2.409-5.928 2.409zm2.43-121.628-8.371-.539s2.14 3.773 3.526 8.652c1.565 5.517-1.431 13.584-1.431 13.584s11.162-12.638 6.276-21.7zm-10.452 143.73c-29.797-2.29-40.28-30.446-40.28-36.31l7.781.003h-3.89 3.89c.04 2.54 7.958 26.625 33.096 28.557l-.596 7.762z'/></g><path fill='#FAFAFA' d='M108.644 226.897c-.465-2.691-.513-5.243.055-7.751-2.265-1.702-6.328-2.335-9.377-3.33-2.5-1.579-15.253 1.611-24.512-9.588-11.602-14.028-9.16-22.406-9.16-22.406-2.976 3.305-5.854 2.039-10.393 17.448s2.13 26.148 14.562 36.928c5.87 5.09 14.176 7.186 21.313 7.992-2.393-2.401 4.087-2.648 8.551-4.317-.361-3.61.239-7.104 2.928-9.925 1.62-1.702 3.708-2.713 5.87-3.527.124-.28.223-.535.338-.806a7 7 0 0 1-.19-.756z'/><path fill='#BDBDBD' d='M99.632 241.805c-.257-.757-.452-1.53-.643-2.305-.206-.847-1.234-3.25.033-.507a10.2 10.2 0 0 1-.696-2.155c-20.129-.026-37.643-13.846-34.255-42.905-2.632-3.499-6.324 7.055-6.324 7.055S47.675 230.25 73.7 241.59c8.675 3.782 16.339 6.494 18.148 5.4 3.765.765 7.17-1.86 8.444-3.588-.238-.53-.452-1.06-.641-1.599z'/><path fill='#4E342E' d='M108.283 222.68c.03-2.454.532-4.83 1.718-7.196.082-.164.179-.32.267-.478-7.745-.066-16.667-.809-24.264-3.848-8.346-3.341-18.79-14.768-20.59-28.194 0 0-15.721 12.135-11.1 38.108 4.333 24.39 24.741 32.342 48.02 33.687.016-.158-2.615-5.592-1.785-8.918-13.213.962-27.998-4.144-35.127-15.886-6.528-10.738-4.695-29.083-2.014-31.772 6.553 20.664 30.111 24.865 44.887 24.504z'/><path fill='#4E342E' d='M110.717 207.887c-4.189 12.292-12.745 22.24-12.745 22.24s11.77-.543 12.419-2.624c.481-1.537 10.627-25.284 14.521-53.11-3.663 6.02-13.837 32.43-14.195 33.483z'/><g fill='#e78724'><path d='M177.394 179.552s13.34 20.404 23.132 34.884c1.515 2.24-9.791 2.395-9.791 2.395zm-6.356-42.303s4.024 10.035 6.224 15.355c2.393 5.783 11.512 8.66 11.512 8.66s-4.728-4.958-4.728-17.519c0-5.262-6.802-7.359-6.802-7.359l-6.2.872z'/><path d='M175.1 142.92s8.241 9.81 23.547 4.317 16.875-25.117 16.875-30.611-29.04 5.494-32.965 7.849-11.38 11.772-7.457 18.445m57.804-104.83s-14.126-2.75-23.546 4.707c-9.423 7.456-12.17 13.344-12.17 13.344l15.87 12.582s7.672-3.556 10.024-13.76c2.351-10.203.78-14.911 9.81-16.873z'/><path d='M179.227 114.585c6.083-8.634 16.103-12.4 24.109-13.025 13.647-1.066 20.122 3.758 20.122 3.758s-22.349.946-28.791 9.407c-11.993 15.746-29.481 17.025-32.279 13.567 3.232.161 6.792-1.442 10.6-5.043 2.375-2.253 4.308-5.924 6.232-8.664zm37.727 30.528s24.795-8.525 20.086-17.157c-4.709-8.642-.964-13.345-.964-13.345s13.85 10.336 10.318 21.321c-2.169 6.743-5.806 11.627-25.782 20.984-9.587 17.794-1.981 49.83-2.433 49.977-.765.247-13.453-6.273-12.59-36.245.264-9.2.996-21.765 11.356-25.556zm-32.492-91.666a5329 5329 0 0 1 24.64 12.638l-5.6 2.804c.535.868.798 1.385.798 1.385l-5.747 2.616S184.509 83.066 175.9 89.607c2.87-8.835 11.29-22.717 11.29-22.717s2.392-6.378-2.714-13.444zm-34.54 215.317c31.953 0 42.778-16.223 42.778-16.223l-4.991 12.161c-1.888 5.263-22.028 21.634-37.739 21.634'/><path d='M189.201 241.575s-2.943 17.97-14.713 17.97c-10.003 0-12.95-15.179-12.95-18.316zm19.592-27.1c-1.046-2.88-4.708-14.522-5.887-25.116-1.411-12.69-.894-31.249-.894-31.249s3.141 12.166 5.884 14.132c2.75 1.96 7.85 15.302 7.85 15.302s2.733 19.862 4.033 26.538c-3.599.722-6.627-1.4-10.985.393zm28.434-78.732s-6.673-2.356-8.634-16.874c-1.962-14.521 2.355-27.079 2.355-27.079s-.394 20.014 1.957 23.94c2.352 3.924 4.317 5.1 7.063 10.204 2.747 5.102-1.184 8.239-2.746 9.81z'/></g><path fill='#FFCC80' d='m229.254 41.524 1.365-.864c4.015.137-1.365.864-1.365.864'/><path fill='#F9A825' d='M179.03 49.146s6.28-1.046 9.418-5.754c3.141-4.71 12.038-14.13 26.216-14.235 14.178-.107 28.204 3.768 28.204 3.768l-12.244 7.73c-1.296-.042-3.56-.025-7.308.098-15.364.51-17.73 15.705-17.73 15.705l-54.154-7.457'/><path fill='#FAFAFA' d='M173.8 133.094s7.327 7.326 18.575 4.97c11.252-2.354 18.055-7.325 18.578-15.96.522-8.634-2.354-12.034-2.354-12.034s-12.56 3.93-18.577 9.16-10.464 13.608-16.222 13.871z'/><path fill='#BDBDBD' d='M182.664 130.48s6.356 2.902 13.456.537c9.3-3.1 9.918-12.77 9.918-12.77s5.7 16.737-10.29 19.959c-12.822 2.583-20.408-4.848-20.408-4.848z'/><g fill='#4E342E'><path d='M224.197 67.459c5.182-4.3 1.322-8.29 1.731-13.175.22-2.599.425-5.052 1.283-6.406 4.77-7.53 16.02-9.815 16.135-9.839l5.28-1.034-3.495-4.09c-.453-.53-14.81-14.225-37.027-7.055-15.54 5.013-27.044 22.007-27.471 22.695l7.786 3.663c.09-.145 9.185-16.119 22.522-20.417 10.772-3.478 20.705-.17 24.956 1.998-4.523 1.806-10.435 5.094-14.003 10.72-1.686 2.655-1.966 6.002-2.237 9.24-.362 4.288-.707 8.336-3.865 10.957l8.38 2.743zm13.838 62.765s-6.526-8.097-8.46-23.852c-.76-6.206.587-18.446 2.746-23.155-.393 11.578 2.553 23.155 2.553 23.155s1.985 21.497 3.161 23.852'/><path d='M203.607 59.281c.004-.016 2.097-9.158 7.896-13.773 8.18-6.507 18.17-3.045 18.263-3.015l1.455-4.481c-.485-.156-11.901-3.754-22.426 3.642-10.59 7.439-11.857 14.181-11.972 14.926l6.775 2.701zm13.924 65.136s3.425-4.904 2.806-10.297c-.304-2.664-1.538-5.397-3.741-6.367-4.095-1.793-5.91 2.845-5.91 2.845s5.763 1.422 6.845 13.822z'/><path d='M179.918 131.893c-.142.268-.216.417-.216.417s10.68 8.019 21.63-.118c5.866-4.358 5.313-15.138 3.512-19.71-4.666 1.061-8.626 2.969-11.94 5.197 4.186-3.733 7.848-6.48 7.835-6.545l4.759-1.349 9.234-3.165c.115.707 3.486 16.51-6.25 27.274-4.316 4.777-24.536 15.467-34.132-2.812l5.55.83z'/><path d='M180.913 133.07c-6.016 9.155-21.19 3.923-20.927-5.757 0 0 10.139 5.598 19.098-4.708 5.231-6.01 21.997-22.604 44.729-17.086-15.697 3.453-36.482 17.793-42.9 27.562z'/><path d='M225.94 158.905c-1.447.836-6.554 6.121-6.554 14.027-.003 3.964.393 23.879 2.357 36.044-2.952 1.918-5.008 2.325-7.946 4.28-1.472-14.09-2.53-34.512-2.035-42.756.562-9.398 5.592-15.705 10.257-18.394 1.461-.84 2.99-1.667 4.531-2.498 7.992-4.318 16.256-8.786 16.256-16.414 0-5.967-2.322-7.833-4.777-9.812l-.263-.214c-1.628-1.317-3.477-2.81-4.566-5.262-2.985-6.704-3.479-23.78-.009-28.116 1.727-2.162 6.365-9.43 5.033-14.307-.51-1.866-1.686-3.248-3.594-4.226-2.022-1.036-4.752-1.563-8.115-1.563-6.603 0-13.93 2.007-17.704 3.207-13.501 4.276-29.79 16.61-34.469 20.294-.131.098-.214.164-.23.172l-2.993 2.08 1.866-3.132c.116-.198 11.948-19.66 33.113-26.798 4.12-1.39 12.202-3.716 20.153-3.716 4.728 0 8.741.822 11.923 2.45 3.873 1.982 6.496 5.147 7.59 9.144 2.672 9.8-6.027 20.712-6.332 21.066-1.02 1.809-1.447 14.965.897 20.236.32.723 1.208 1.439 2.327 2.343q.53.42 1.159.888c3.305 2.492 8.305 6.266 8.305 14.957 0 11.89-12.055 18.345-21.74 23.525-1.588.855-3.109 1.66-4.474 2.45z'/><path d='M211.559 66.06c-1.918-3.249-6.822-9.52-18.125-15.032-12.613-6.147-27.212-9.267-43.396-9.275v-.003h-.058v.008c-16.182.008-30.785 3.133-43.399 9.275-11.297 5.518-16.198 11.783-18.122 15.031l-.822 1.094 9.677 2.153.31-.386.163-.357c.148-.642 1.447-4.095 12.238-9.357 11.536-5.626 24.989-8.478 39.987-8.486 14.998.006 28.45 2.859 39.986 8.486 10.788 5.262 12.095 8.715 12.243 9.355l.157.356.304.387 9.678-2.155-.822-1.088z'/><path d='M224.197 67.459c5.182-4.3 1.322-8.29 1.731-13.175.22-2.599.425-5.052 1.283-6.406 4.77-7.53 16.02-9.815 16.135-9.839l5.28-1.034-3.495-4.09c-.453-.53-14.81-14.225-37.027-7.055-15.54 5.013-27.044 22.007-27.471 22.695l7.786 3.663c.09-.145 9.185-16.119 22.522-20.417 10.772-3.478 20.705-.17 24.956 1.998-4.523 1.806-10.435 5.094-14.003 10.72-1.686 2.655-1.966 6.002-2.237 9.24-.362 4.288-.707 8.336-3.865 10.957l8.38 2.743z'/><path d='m214.223 65.017 1.09-.908c2.903-2.408 3.228-6.296 3.578-10.43.282-3.33.572-6.767 2.354-9.587 3.274-5.172 8.458-8.404 12.728-10.336-2.557-1.086-7.36-2.697-13.284-2.697-3.273 0-6.465.493-9.502 1.472-12.367 3.988-21.104 18.394-22.094 20.087l-.37.584-9.218-4.333.469-.748c.11-.173 11.676-17.794 27.895-23.024 4.342-1.406 8.7-2.113 12.959-2.113 15.615 0 24.832 9.308 24.906 9.399l4.375 5.114-6.595 1.291c-.107.017-11.084 2.319-15.623 9.489-.748 1.192-.946 3.552-1.16 6.052-.123 1.488.19 2.935.494 4.341.658 3.034 1.34 6.184-2.5 9.366l-.329.271-10.146-3.33z'/><path d='M203.607 59.281c.004-.016 2.097-9.158 7.896-13.773 8.18-6.507 18.17-3.045 18.263-3.015l1.455-4.481c-.485-.156-11.901-3.754-22.426 3.642-10.59 7.439-11.857 14.181-11.972 14.926l6.775 2.701z'/><path d='m195.968 57.08.09-.615c.116-.778 1.442-7.828 12.29-15.454 4.465-3.136 9.818-4.795 15.467-4.795 4.292 0 7.318.946 7.655 1.053l.749.246-1.94 5.978-.75-.247c-.279-.09-2.803-.904-6.19-.904-4.35 0-8.158 1.283-11.315 3.799-5.542 4.415-7.598 13.238-7.614 13.329l-.198.904-8.206-3.273zm33.294-15.557.966-2.975c-1.14-.29-3.447-.76-6.414-.76-5.405 0-10.305 1.518-14.562 4.51-8.903 6.257-11.03 11.912-11.513 13.797l5.342 2.127c.699-2.483 3.013-9.408 7.94-13.329 3.404-2.702 7.655-4.13 12.3-4.13 2.673 0 4.827.46 5.946.76zm-60.209 211.304c-4.043-4.323-6.435-8.975-7.013-10.404-.33-.814-.708-2.365.479-3.585.924-.943 2.526-1.427 4.766-1.427h3.453c3.594 0 7.795 0 10.295.008l2.097.008c2.943 0 4.399.847 5.09 1.546 1.02 1.028 1.175 2.459.476 4.251-1.874 4.777-6.117 8.683-7.375 9.76-1.645 1.423-4.005 2.262-6.323 2.262-2.377 0-4.482-.856-5.929-2.401zm3.5-119.227 8.367-.539s-2.138 3.773-3.523 8.652c-1.57 5.517 1.43 13.584 1.43 13.584s-11.166-12.639-6.281-21.7z'/></g><path fill='#e78724' d='M150.004 103.715c-5.336 0-12.293-.146-17.512-3.898 0 0 6.834 11.982 17.503 11.982l.008-1.516.01 1.516c10.67 0 17.504-11.98 17.504-11.98-5.22 3.75-12.182 3.897-17.513 3.897z'/><path fill='#4E342E' d='M164.698 103.657c-5.353 3.24-10.132 4.362-14.691 4.462-4.56-.1-9.339-1.222-14.692-4.461 0 0 7.795 9.513 14.653 9.587v.009h.038c.012 0 .024.008.038.008 6.858-.074 14.661-9.58 14.661-9.58zm18.3 173.686-.598-7.758c25.137-1.932 33.055-26.019 33.096-28.554h7.787c.008 5.863-10.484 34.022-40.274 36.311z'/><path fill='#FAFAFA' d='M191.372 226.897c.467-2.691.512-5.243-.055-7.751 2.268-1.702 6.328-2.335 9.377-3.33 2.493-1.579 15.255 1.611 24.512-9.588 11.596-14.028 9.151-22.406 9.151-22.406 2.977 3.305 5.856 2.039 10.392 17.448 4.539 15.41-2.134 26.148-14.564 36.928-5.87 5.09-14.174 7.186-21.314 7.992 2.392-2.401-4.079-2.648-8.544-4.317.362-3.61-.238-7.104-2.927-9.925-1.612-1.702-3.7-2.713-5.863-3.527-.123-.28-.222-.535-.337-.806.074-.247.14-.493.19-.756z'/><path fill='#BDBDBD' d='M200.376 241.805c.261-.757.452-1.53.644-2.305.21-.847 1.238-3.25-.03-.507a10.2 10.2 0 0 0 .695-2.155c20.13-.026 37.643-13.846 34.257-42.905 2.635-3.499 6.328 7.055 6.328 7.055s10.078 29.262-15.95 40.603c-8.672 3.782-16.335 6.494-18.144 5.4-3.77.765-7.17-1.86-8.442-3.588.238-.53.46-1.06.65-1.599z'/><g fill='#4E342E'><path d='M191.734 222.68c-.028-2.454-.532-4.83-1.715-7.196-.083-.164-.18-.32-.268-.478 7.746-.066 16.668-.809 24.265-3.848 8.35-3.341 18.795-14.768 20.59-28.194 0 0 15.726 12.135 11.105 38.108-4.341 24.39-24.747 32.342-48.023 33.687-.013-.158 2.619-5.592 1.787-8.918 13.213.962 27.998-4.144 35.135-15.886 6.529-10.738 4.695-29.083 2.014-31.772-6.545 20.664-30.11 24.865-44.887 24.504z'/><path d='M201.132 228.797c-2.78-3.006-7.893-6.59-19.682-6.59l-31.394-.019h-.074c-9.045.007-31.402.02-31.402.02-11.783 0-16.898 3.584-19.677 6.589-4.054 4.374-4.687 10.461-1.883 18.069 7.45 20.249 17.925 40.821 52.97 40.85v.003h.041c.017 0 .025.008.041.008 35.045-.024 45.52-20.597 52.97-40.85 2.804-7.606 2.171-13.69-1.883-18.065zm-5.456 16.649c-3.55 10.168-13.09 34.78-45.669 34.823-32.58-.043-42.117-24.654-45.668-34.823-1.963-5.64-1.814-9.816.46-12.41 1.555-1.772 5.123-3.889 13.255-3.889l8.497-.002c6.268-.006 14.138-.013 23.459-.017 9.316.004 17.19.01 23.457.017l8.493.003c8.135 0 11.703 2.113 13.257 3.88 2.272 2.591 2.423 6.768.459 12.409z'/><path d='M189.292 207.887c4.188 12.292 12.743 22.24 12.743 22.24s-11.766-.543-12.416-2.624c-.477-1.537-10.624-25.284-14.513-53.11 3.667 6.02 13.839 32.43 14.2 33.483z'/></g></svg>",
+ "grunt": "<svg viewBox='0 0 300 300'><path fill='#f9a825' d='M61.045 79.22s4.718 18.193 5.39 24.259c.675 6.064-12.128 19.54-12.128 26.279s2.691 18.87 17.518 24.935 16.847 21.563 16.847 31c0 9.433-5.39 27.626-5.39 27.626s-3.368 45.148 14.825 53.234 14.151 8.086 18.192 8.086c4.037 0 33.688 12.136 51.21 7.416 17.523-4.711 22.234-10.105 22.234-10.105s17.523 1.348 22.908-14.151c5.394-15.492 8.757-47.84 6.735-62.665s-4.72-34.362 7.408-41.105c12.129-6.734 24.931-14.825 22.908-21.56S236.9 116.297 236.9 116.297s-2.697-16.84-.683-20.885c2.023-4.038 14.151-14.152 7.409-21.56-6.734-7.409-18.871-7.409-18.871-7.409l-15.491 1.34s-26.27-23.59-60.641-22.242c-34.346 1.348-55.914 22.234-55.914 22.234s-11.454-4.044-21.562 0c-10.114 4.045-12.128 0-10.106 11.454z'/><g fill='#e78724'><path d='M122.623 179.552s-13.345 20.404-23.133 34.884c-1.514 2.24 9.788 2.395 9.788 2.395l13.346-37.28zm6.353-42.303s-4.022 10.035-6.226 15.355c-2.392 5.783-11.511 8.66-11.511 8.66s4.728-4.958 4.728-17.519c0-5.262 6.802-7.359 6.802-7.359l6.208.872z'/><path d='M124.909 142.92s-8.242 9.81-23.544 4.317c-15.306-5.493-16.874-25.117-16.874-30.611s29.04 5.494 32.964 7.849 11.378 11.772 7.454 18.445M67.104 38.09s14.126-2.75 23.544 4.707c9.422 7.456 12.165 13.344 12.165 13.344L86.939 68.724s-7.67-3.557-10.026-13.761c-2.353-10.203-.783-14.911-9.81-16.873z'/><path d='M120.79 114.585c-6.084-8.634-16.107-12.4-24.11-13.025-13.646-1.066-20.125 3.758-20.125 3.758s22.349.946 28.79 9.407c11.992 15.746 29.487 17.025 32.284 13.567-3.235.161-6.793-1.442-10.602-5.043-2.38-2.253-4.309-5.924-6.239-8.664zm-37.73 30.528s-24.795-8.525-20.084-17.156c4.71-8.635.962-13.34.962-13.34s-13.847 10.337-10.32 21.324c2.17 6.742 5.808 11.626 25.783 20.984 9.58 17.793 1.978 49.829 2.434 49.977.759.246 13.452-6.274 12.585-36.246-.263-9.209-.995-21.765-11.355-25.555zm32.495-91.666c-10.398 5.283-24.642 12.638-24.642 12.638l5.606 2.804c-.534.868-.793 1.385-.793 1.385l5.748 2.616s14.044 10.176 22.653 16.717c-2.87-8.835-11.298-22.717-11.298-22.717s-2.393-6.378 2.713-13.444zm34.54 215.317c-31.958 0-42.783-16.223-42.783-16.223l4.995 12.161c1.888 5.263 22.027 21.634 37.74 21.634'/><path d='M110.815 241.575s2.94 17.97 14.713 17.97c10.004 0 12.952-15.179 12.952-18.316l-27.666.346zm-19.593-27.1c1.046-2.88 4.709-14.522 5.885-25.116 1.406-12.69.897-31.249.897-31.249s-3.141 12.166-5.896 14.132c-2.746 1.96-7.853 15.302-7.853 15.302s-2.738 19.862-4.037 26.538c3.602.722 6.628-1.4 10.986.393zm-28.43-78.732s6.668-2.356 8.634-16.874c1.96-14.521-2.357-27.079-2.357-27.079s.392 20.014-1.961 23.94c-2.357 3.922-4.317 5.1-7.065 10.204-2.749 5.103 1.176 8.239 2.749 9.81z'/></g><path fill='#ffcc80' d='m70.755 41.524-1.361-.864c-4.015.137 1.362.864 1.362.864z'/><path fill='#f9a825' d='M120.987 49.146s-6.278-1.046-9.419-5.754c-3.14-4.71-12.034-14.13-26.213-14.235-14.176-.107-28.204 3.768-28.204 3.768l12.244 7.73c1.296-.042 3.56-.025 7.305.098 15.366.51 17.732 15.705 17.732 15.705l54.162-7.457'/><path fill='#fafafa' d='M126.216 133.094s-7.326 7.326-18.576 4.97c-11.251-2.354-18.055-7.325-18.578-15.96-.523-8.634 2.357-12.034 2.357-12.034s12.564 3.93 18.583 9.16c6.019 5.238 10.467 13.608 16.223 13.871z'/><path fill='#bdbdbd' d='M117.352 130.48s-6.353 2.902-13.455.537c-9.297-3.1-9.917-12.77-9.917-12.77s-5.702 16.737 10.287 19.959c12.825 2.583 20.41-4.848 20.41-4.848z'/><g fill='#4e342e'><path d='M121.866 45.596c-.684-14.354 11.083-25.787 15.16-26.272.225 2.432-.227 11.919 3.848 14.836 1.187-7.746 5.2-16.29 21.045-21.889-3.394 9-.453 16.54.947 20.034 10.82-9.82 18.11-8.198 18.11-8.198s-7.518 12.973-4.577 21.486c-21.721-1.704-54.427 2.307-54.533.003M75.817 67.459c-5.182-4.3-1.32-8.29-1.729-13.175-.222-2.599-.427-5.052-1.285-6.406-4.77-7.53-16.018-9.815-16.133-9.839l-5.279-1.034 3.495-4.09c.452-.53 14.809-14.225 37.026-7.055 15.541 5.013 27.044 22.007 27.472 22.695l-7.787 3.663c-.09-.145-9.184-16.119-22.513-20.417-10.772-3.478-20.705-.17-24.964 1.998 4.522 1.806 10.434 5.094 14.003 10.72 1.686 2.655 1.965 6.002 2.237 9.24.361 4.288.698 8.336 3.856 10.957l-8.387 2.743zm-13.842 62.765s6.53-8.097 8.464-23.852c.76-6.206-.59-18.446-2.749-23.155.392 11.578-2.55 23.155-2.55 23.155s-1.982 21.497-3.158 23.852z'/><path d='M96.41 59.281c-.004-.016-2.096-9.158-7.894-13.773-8.182-6.507-18.17-3.045-18.267-3.015l-1.447-4.481c.485-.156 11.906-3.754 22.423 3.642 10.582 7.439 11.857 14.181 11.964 14.926l-6.776 2.701zm-13.923 65.136s-3.426-4.904-2.81-10.297c.307-2.664 1.541-5.397 3.745-6.367 4.094-1.793 5.912 2.845 5.912 2.845s-5.764 1.422-6.842 13.822z'/><path d='M120.099 131.893c.143.268.216.417.216.417s-10.685 8.019-21.634-.118c-5.863-4.358-5.308-15.138-3.513-19.71 4.67 1.061 8.63 2.969 11.941 5.197-4.183-3.733-7.846-6.48-7.836-6.545l-4.755-1.349-9.242-3.165c-.119.707-3.49 16.51 6.24 27.274 4.314 4.777 24.533 15.467 34.133-2.812l-5.55.83z'/><path d='M119.095 133.07c6.016 9.155 21.194 3.923 20.933-5.757 0 0-10.142 5.598-19.101-4.708-5.232-6.01-21.996-22.604-44.727-17.086 15.698 3.453 36.48 17.793 42.895 27.562z'/><path d='M74.077 158.905c1.447.836 6.55 6.121 6.553 14.027 0 3.964-.395 23.879-2.357 36.044 2.952 1.918 5.008 2.325 7.946 4.28 1.472-14.09 2.526-34.512 2.034-42.756-.559-9.398-5.591-15.705-10.253-18.394a187 187 0 0 0-4.523-2.498c-7.992-4.318-16.256-8.786-16.256-16.414 0-5.967 2.319-7.833 4.777-9.812l.264-.214c1.628-1.317 3.478-2.81 4.571-5.262 2.985-6.704 3.47-23.78 0-28.116-1.726-2.162-6.364-9.43-5.032-14.307.51-1.866 1.686-3.248 3.593-4.226 2.023-1.036 4.753-1.563 8.116-1.563 6.603 0 13.93 2.007 17.703 3.207 13.502 4.276 29.79 16.61 34.461 20.294l.23.172 2.985 2.08-1.866-3.132c-.115-.198-11.948-19.66-33.113-26.798-4.12-1.39-12.202-3.716-20.153-3.716-4.728 0-8.75.822-11.931 2.45-3.873 1.982-6.496 5.147-7.59 9.144-2.672 9.8 6.036 20.712 6.332 21.066 1.02 1.809 1.447 14.965-.897 20.236-.32.723-1.2 1.439-2.327 2.343-.345.28-.74.576-1.159.888-3.305 2.492-8.305 6.266-8.305 14.957 0 11.89 12.063 18.345 21.749 23.525 1.595.855 3.108 1.66 4.481 2.45z'/><path d='M75.817 67.459c-5.182-4.3-1.32-8.29-1.729-13.175-.222-2.599-.427-5.052-1.285-6.406-4.77-7.53-16.018-9.815-16.133-9.839l-5.279-1.034 3.495-4.09c.452-.53 14.809-14.225 37.026-7.055 15.541 5.013 27.044 22.007 27.472 22.695l-7.787 3.663c-.09-.145-9.184-16.119-22.513-20.417-10.772-3.478-20.705-.17-24.964 1.998 4.522 1.806 10.434 5.094 14.003 10.72 1.686 2.655 1.965 6.002 2.237 9.24.361 4.288.698 8.336 3.856 10.957l-8.387 2.743z'/><path d='m75.649 68.34-.335-.278c-3.843-3.182-3.161-6.331-2.501-9.365.3-1.398.617-2.854.488-4.342-.21-2.5-.41-4.86-1.164-6.052-4.539-7.17-15.516-9.464-15.623-9.489l-6.595-1.299 4.375-5.106c.082-.09 9.3-9.398 24.906-9.398 4.26 0 8.617.707 12.95 2.113 16.216 5.23 27.793 22.85 27.9 23.023l.469.748-9.218 4.342-.37-.584c-.987-1.686-9.727-16.1-22.094-20.088-3.034-.987-6.233-1.48-9.505-1.48-5.92 0-10.723 1.62-13.28 2.697 4.276 1.924 9.448 5.164 12.729 10.336 1.784 2.82 2.072 6.257 2.351 9.58.346 4.135.675 8.025 3.577 10.434l1.094.904-10.147 3.33z'/><path d='M96.41 59.281c-.004-.016-2.096-9.158-7.894-13.773-8.182-6.507-18.17-3.045-18.267-3.015l-1.447-4.481c.485-.156 11.906-3.754 22.423 3.642 10.582 7.439 11.857 14.181 11.964 14.926l-6.776 2.701z'/><path d='m95.842 60.35-.204-.9c-.021-.087-2.072-8.916-7.62-13.326-3.16-2.516-6.972-3.79-11.322-3.79-3.396 0-5.92.814-6.195.904l-.75.255-1.94-5.978.747-.238c.338-.107 3.364-1.053 7.656-1.053 5.657 0 11.002 1.661 15.466 4.802 10.854 7.63 12.178 14.678 12.293 15.459l.099.616-8.206 3.273zM76.696 40.762c4.648 0 8.905 1.43 12.301 4.131 4.926 3.923 7.242 10.846 7.943 13.33l5.337-2.128c-.483-1.885-2.61-7.54-11.512-13.798-4.257-2.993-9.156-4.509-14.562-4.509-2.967 0-5.27.472-6.41.76l.962 2.977a23.3 23.3 0 0 1 5.941-.763m48.339 214.47c-2.32 0-4.686-.84-6.326-2.26-1.256-1.083-5.501-4.984-7.374-9.761-.699-1.787-.543-3.215.472-4.245.699-.704 2.15-1.547 5.094-1.547l2.095-.003c2.5-.003 6.701-.003 10.295-.003h3.445c2.245 0 3.848.485 4.769 1.43 1.184 1.218.806 2.772.477 3.586-.576 1.43-2.96 6.085-7.006 10.402-1.447 1.554-3.552 2.409-5.928 2.409zm2.43-121.628-8.371-.539s2.14 3.773 3.526 8.652c1.565 5.517-1.431 13.584-1.431 13.584s11.162-12.638 6.276-21.7zm-10.452 143.73c-29.797-2.29-40.28-30.446-40.28-36.31l7.781.003h-3.89 3.89c.04 2.54 7.958 26.625 33.096 28.557l-.596 7.762z'/></g><path fill='#fafafa' d='M108.644 226.897c-.465-2.691-.513-5.243.055-7.751-2.265-1.702-6.328-2.335-9.377-3.33-2.5-1.579-15.253 1.611-24.512-9.588-11.602-14.028-9.16-22.406-9.16-22.406-2.976 3.305-5.854 2.039-10.393 17.448s2.13 26.148 14.562 36.928c5.87 5.09 14.176 7.186 21.313 7.992-2.393-2.401 4.087-2.648 8.551-4.317-.361-3.61.239-7.104 2.928-9.925 1.62-1.702 3.708-2.713 5.87-3.527.124-.28.223-.535.338-.806a7 7 0 0 1-.19-.756z'/><path fill='#bdbdbd' d='M99.632 241.805c-.257-.757-.452-1.53-.643-2.305-.206-.847-1.234-3.25.033-.507a10.2 10.2 0 0 1-.696-2.155c-20.129-.026-37.643-13.846-34.255-42.905-2.632-3.499-6.324 7.055-6.324 7.055S47.675 230.25 73.7 241.59c8.675 3.782 16.339 6.494 18.148 5.4 3.765.765 7.17-1.86 8.444-3.588-.238-.53-.452-1.06-.641-1.599z'/><path fill='#4e342e' d='M108.283 222.68c.03-2.454.532-4.83 1.718-7.196.082-.164.179-.32.267-.478-7.745-.066-16.667-.809-24.264-3.848-8.346-3.341-18.79-14.768-20.59-28.194 0 0-15.721 12.135-11.1 38.108 4.333 24.39 24.741 32.342 48.02 33.687.016-.158-2.615-5.592-1.785-8.918-13.213.962-27.998-4.144-35.127-15.886-6.528-10.738-4.695-29.083-2.014-31.772 6.553 20.664 30.111 24.865 44.887 24.504z'/><path fill='#4e342e' d='M110.717 207.887c-4.189 12.292-12.745 22.24-12.745 22.24s11.77-.543 12.419-2.624c.481-1.537 10.627-25.284 14.521-53.11-3.663 6.02-13.837 32.43-14.195 33.483z'/><g fill='#e78724'><path d='M177.394 179.552s13.34 20.404 23.132 34.884c1.515 2.24-9.791 2.395-9.791 2.395zm-6.356-42.303s4.024 10.035 6.224 15.355c2.393 5.783 11.512 8.66 11.512 8.66s-4.728-4.958-4.728-17.519c0-5.262-6.802-7.359-6.802-7.359l-6.2.872z'/><path d='M175.1 142.92s8.241 9.81 23.547 4.317 16.875-25.117 16.875-30.611-29.04 5.494-32.965 7.849-11.38 11.772-7.457 18.445m57.804-104.83s-14.126-2.75-23.546 4.707c-9.423 7.456-12.17 13.344-12.17 13.344l15.87 12.582s7.672-3.556 10.024-13.76c2.351-10.203.78-14.911 9.81-16.873z'/><path d='M179.227 114.585c6.083-8.634 16.103-12.4 24.109-13.025 13.647-1.066 20.122 3.758 20.122 3.758s-22.349.946-28.791 9.407c-11.993 15.746-29.481 17.025-32.279 13.567 3.232.161 6.792-1.442 10.6-5.043 2.375-2.253 4.308-5.924 6.232-8.664zm37.727 30.528s24.795-8.525 20.086-17.157c-4.709-8.642-.964-13.345-.964-13.345s13.85 10.336 10.318 21.321c-2.169 6.743-5.806 11.627-25.782 20.984-9.587 17.794-1.981 49.83-2.433 49.977-.765.247-13.453-6.273-12.59-36.245.264-9.2.996-21.765 11.356-25.556zm-32.492-91.666a5329 5329 0 0 1 24.64 12.638l-5.6 2.804c.535.868.798 1.385.798 1.385l-5.747 2.616S184.509 83.066 175.9 89.607c2.87-8.835 11.29-22.717 11.29-22.717s2.392-6.378-2.714-13.444zm-34.54 215.317c31.953 0 42.778-16.223 42.778-16.223l-4.991 12.161c-1.888 5.263-22.028 21.634-37.739 21.634'/><path d='M189.201 241.575s-2.943 17.97-14.713 17.97c-10.003 0-12.95-15.179-12.95-18.316zm19.592-27.1c-1.046-2.88-4.708-14.522-5.887-25.116-1.411-12.69-.894-31.249-.894-31.249s3.141 12.166 5.884 14.132c2.75 1.96 7.85 15.302 7.85 15.302s2.733 19.862 4.033 26.538c-3.599.722-6.627-1.4-10.985.393zm28.434-78.732s-6.673-2.356-8.634-16.874c-1.962-14.521 2.355-27.079 2.355-27.079s-.394 20.014 1.957 23.94c2.352 3.924 4.317 5.1 7.063 10.204 2.747 5.102-1.184 8.239-2.746 9.81z'/></g><path fill='#ffcc80' d='m229.254 41.524 1.365-.864c4.015.137-1.365.864-1.365.864'/><path fill='#f9a825' d='M179.03 49.146s6.28-1.046 9.418-5.754c3.141-4.71 12.038-14.13 26.216-14.235 14.178-.107 28.204 3.768 28.204 3.768l-12.244 7.73c-1.296-.042-3.56-.025-7.308.098-15.364.51-17.73 15.705-17.73 15.705l-54.154-7.457'/><path fill='#fafafa' d='M173.8 133.094s7.327 7.326 18.575 4.97c11.252-2.354 18.055-7.325 18.578-15.96.522-8.634-2.354-12.034-2.354-12.034s-12.56 3.93-18.577 9.16-10.464 13.608-16.222 13.871z'/><path fill='#bdbdbd' d='M182.664 130.48s6.356 2.902 13.456.537c9.3-3.1 9.918-12.77 9.918-12.77s5.7 16.737-10.29 19.959c-12.822 2.583-20.408-4.848-20.408-4.848z'/><g fill='#4e342e'><path d='M224.197 67.459c5.182-4.3 1.322-8.29 1.731-13.175.22-2.599.425-5.052 1.283-6.406 4.77-7.53 16.02-9.815 16.135-9.839l5.28-1.034-3.495-4.09c-.453-.53-14.81-14.225-37.027-7.055-15.54 5.013-27.044 22.007-27.471 22.695l7.786 3.663c.09-.145 9.185-16.119 22.522-20.417 10.772-3.478 20.705-.17 24.956 1.998-4.523 1.806-10.435 5.094-14.003 10.72-1.686 2.655-1.966 6.002-2.237 9.24-.362 4.288-.707 8.336-3.865 10.957l8.38 2.743zm13.838 62.765s-6.526-8.097-8.46-23.852c-.76-6.206.587-18.446 2.746-23.155-.393 11.578 2.553 23.155 2.553 23.155s1.985 21.497 3.161 23.852'/><path d='M203.607 59.281c.004-.016 2.097-9.158 7.896-13.773 8.18-6.507 18.17-3.045 18.263-3.015l1.455-4.481c-.485-.156-11.901-3.754-22.426 3.642-10.59 7.439-11.857 14.181-11.972 14.926l6.775 2.701zm13.924 65.136s3.425-4.904 2.806-10.297c-.304-2.664-1.538-5.397-3.741-6.367-4.095-1.793-5.91 2.845-5.91 2.845s5.763 1.422 6.845 13.822z'/><path d='M179.918 131.893c-.142.268-.216.417-.216.417s10.68 8.019 21.63-.118c5.866-4.358 5.313-15.138 3.512-19.71-4.666 1.061-8.626 2.969-11.94 5.197 4.186-3.733 7.848-6.48 7.835-6.545l4.759-1.349 9.234-3.165c.115.707 3.486 16.51-6.25 27.274-4.316 4.777-24.536 15.467-34.132-2.812l5.55.83z'/><path d='M180.913 133.07c-6.016 9.155-21.19 3.923-20.927-5.757 0 0 10.139 5.598 19.098-4.708 5.231-6.01 21.997-22.604 44.729-17.086-15.697 3.453-36.482 17.793-42.9 27.562z'/><path d='M225.94 158.905c-1.447.836-6.554 6.121-6.554 14.027-.003 3.964.393 23.879 2.357 36.044-2.952 1.918-5.008 2.325-7.946 4.28-1.472-14.09-2.53-34.512-2.035-42.756.562-9.398 5.592-15.705 10.257-18.394 1.461-.84 2.99-1.667 4.531-2.498 7.992-4.318 16.256-8.786 16.256-16.414 0-5.967-2.322-7.833-4.777-9.812l-.263-.214c-1.628-1.317-3.477-2.81-4.566-5.262-2.985-6.704-3.479-23.78-.009-28.116 1.727-2.162 6.365-9.43 5.033-14.307-.51-1.866-1.686-3.248-3.594-4.226-2.022-1.036-4.752-1.563-8.115-1.563-6.603 0-13.93 2.007-17.704 3.207-13.501 4.276-29.79 16.61-34.469 20.294-.131.098-.214.164-.23.172l-2.993 2.08 1.866-3.132c.116-.198 11.948-19.66 33.113-26.798 4.12-1.39 12.202-3.716 20.153-3.716 4.728 0 8.741.822 11.923 2.45 3.873 1.982 6.496 5.147 7.59 9.144 2.672 9.8-6.027 20.712-6.332 21.066-1.02 1.809-1.447 14.965.897 20.236.32.723 1.208 1.439 2.327 2.343q.53.42 1.159.888c3.305 2.492 8.305 6.266 8.305 14.957 0 11.89-12.055 18.345-21.74 23.525-1.588.855-3.109 1.66-4.474 2.45z'/><path d='M211.559 66.06c-1.918-3.249-6.822-9.52-18.125-15.032-12.613-6.147-27.212-9.267-43.396-9.275v-.003h-.058v.008c-16.182.008-30.785 3.133-43.399 9.275-11.297 5.518-16.198 11.783-18.122 15.031l-.822 1.094 9.677 2.153.31-.386.163-.357c.148-.642 1.447-4.095 12.238-9.357 11.536-5.626 24.989-8.478 39.987-8.486 14.998.006 28.45 2.859 39.986 8.486 10.788 5.262 12.095 8.715 12.243 9.355l.157.356.304.387 9.678-2.155-.822-1.088z'/><path d='M224.197 67.459c5.182-4.3 1.322-8.29 1.731-13.175.22-2.599.425-5.052 1.283-6.406 4.77-7.53 16.02-9.815 16.135-9.839l5.28-1.034-3.495-4.09c-.453-.53-14.81-14.225-37.027-7.055-15.54 5.013-27.044 22.007-27.471 22.695l7.786 3.663c.09-.145 9.185-16.119 22.522-20.417 10.772-3.478 20.705-.17 24.956 1.998-4.523 1.806-10.435 5.094-14.003 10.72-1.686 2.655-1.966 6.002-2.237 9.24-.362 4.288-.707 8.336-3.865 10.957l8.38 2.743z'/><path d='m214.223 65.017 1.09-.908c2.903-2.408 3.228-6.296 3.578-10.43.282-3.33.572-6.767 2.354-9.587 3.274-5.172 8.458-8.404 12.728-10.336-2.557-1.086-7.36-2.697-13.284-2.697-3.273 0-6.465.493-9.502 1.472-12.367 3.988-21.104 18.394-22.094 20.087l-.37.584-9.218-4.333.469-.748c.11-.173 11.676-17.794 27.895-23.024 4.342-1.406 8.7-2.113 12.959-2.113 15.615 0 24.832 9.308 24.906 9.399l4.375 5.114-6.595 1.291c-.107.017-11.084 2.319-15.623 9.489-.748 1.192-.946 3.552-1.16 6.052-.123 1.488.19 2.935.494 4.341.658 3.034 1.34 6.184-2.5 9.366l-.329.271-10.146-3.33z'/><path d='M203.607 59.281c.004-.016 2.097-9.158 7.896-13.773 8.18-6.507 18.17-3.045 18.263-3.015l1.455-4.481c-.485-.156-11.901-3.754-22.426 3.642-10.59 7.439-11.857 14.181-11.972 14.926l6.775 2.701z'/><path d='m195.968 57.08.09-.615c.116-.778 1.442-7.828 12.29-15.454 4.465-3.136 9.818-4.795 15.467-4.795 4.292 0 7.318.946 7.655 1.053l.749.246-1.94 5.978-.75-.247c-.279-.09-2.803-.904-6.19-.904-4.35 0-8.158 1.283-11.315 3.799-5.542 4.415-7.598 13.238-7.614 13.329l-.198.904-8.206-3.273zm33.294-15.557.966-2.975c-1.14-.29-3.447-.76-6.414-.76-5.405 0-10.305 1.518-14.562 4.51-8.903 6.257-11.03 11.912-11.513 13.797l5.342 2.127c.699-2.483 3.013-9.408 7.94-13.329 3.404-2.702 7.655-4.13 12.3-4.13 2.673 0 4.827.46 5.946.76zm-60.209 211.304c-4.043-4.323-6.435-8.975-7.013-10.404-.33-.814-.708-2.365.479-3.585.924-.943 2.526-1.427 4.766-1.427h3.453c3.594 0 7.795 0 10.295.008l2.097.008c2.943 0 4.399.847 5.09 1.546 1.02 1.028 1.175 2.459.476 4.251-1.874 4.777-6.117 8.683-7.375 9.76-1.645 1.423-4.005 2.262-6.323 2.262-2.377 0-4.482-.856-5.929-2.401zm3.5-119.227 8.367-.539s-2.138 3.773-3.523 8.652c-1.57 5.517 1.43 13.584 1.43 13.584s-11.166-12.639-6.281-21.7z'/></g><path fill='#e78724' d='M150.004 103.715c-5.336 0-12.293-.146-17.512-3.898 0 0 6.834 11.982 17.503 11.982l.008-1.516.01 1.516c10.67 0 17.504-11.98 17.504-11.98-5.22 3.75-12.182 3.897-17.513 3.897z'/><path fill='#4e342e' d='M164.698 103.657c-5.353 3.24-10.132 4.362-14.691 4.462-4.56-.1-9.339-1.222-14.692-4.461 0 0 7.795 9.513 14.653 9.587v.009h.038c.012 0 .024.008.038.008 6.858-.074 14.661-9.58 14.661-9.58zm18.3 173.686-.598-7.758c25.137-1.932 33.055-26.019 33.096-28.554h7.787c.008 5.863-10.484 34.022-40.274 36.311z'/><path fill='#fafafa' d='M191.372 226.897c.467-2.691.512-5.243-.055-7.751 2.268-1.702 6.328-2.335 9.377-3.33 2.493-1.579 15.255 1.611 24.512-9.588 11.596-14.028 9.151-22.406 9.151-22.406 2.977 3.305 5.856 2.039 10.392 17.448 4.539 15.41-2.134 26.148-14.564 36.928-5.87 5.09-14.174 7.186-21.314 7.992 2.392-2.401-4.079-2.648-8.544-4.317.362-3.61-.238-7.104-2.927-9.925-1.612-1.702-3.7-2.713-5.863-3.527-.123-.28-.222-.535-.337-.806.074-.247.14-.493.19-.756z'/><path fill='#bdbdbd' d='M200.376 241.805c.261-.757.452-1.53.644-2.305.21-.847 1.238-3.25-.03-.507a10.2 10.2 0 0 0 .695-2.155c20.13-.026 37.643-13.846 34.257-42.905 2.635-3.499 6.328 7.055 6.328 7.055s10.078 29.262-15.95 40.603c-8.672 3.782-16.335 6.494-18.144 5.4-3.77.765-7.17-1.86-8.442-3.588.238-.53.46-1.06.65-1.599z'/><g fill='#4e342e'><path d='M191.734 222.68c-.028-2.454-.532-4.83-1.715-7.196-.083-.164-.18-.32-.268-.478 7.746-.066 16.668-.809 24.265-3.848 8.35-3.341 18.795-14.768 20.59-28.194 0 0 15.726 12.135 11.105 38.108-4.341 24.39-24.747 32.342-48.023 33.687-.013-.158 2.619-5.592 1.787-8.918 13.213.962 27.998-4.144 35.135-15.886 6.529-10.738 4.695-29.083 2.014-31.772-6.545 20.664-30.11 24.865-44.887 24.504z'/><path d='M201.132 228.797c-2.78-3.006-7.893-6.59-19.682-6.59l-31.394-.019h-.074c-9.045.007-31.402.02-31.402.02-11.783 0-16.898 3.584-19.677 6.589-4.054 4.374-4.687 10.461-1.883 18.069 7.45 20.249 17.925 40.821 52.97 40.85v.003h.041c.017 0 .025.008.041.008 35.045-.024 45.52-20.597 52.97-40.85 2.804-7.606 2.171-13.69-1.883-18.065zm-5.456 16.649c-3.55 10.168-13.09 34.78-45.669 34.823-32.58-.043-42.117-24.654-45.668-34.823-1.963-5.64-1.814-9.816.46-12.41 1.555-1.772 5.123-3.889 13.255-3.889l8.497-.002c6.268-.006 14.138-.013 23.459-.017 9.316.004 17.19.01 23.457.017l8.493.003c8.135 0 11.703 2.113 13.257 3.88 2.272 2.591 2.423 6.768.459 12.409z'/><path d='M189.292 207.887c4.188 12.292 12.743 22.24 12.743 22.24s-11.766-.543-12.416-2.624c-.477-1.537-10.624-25.284-14.513-53.11 3.667 6.02 13.839 32.43 14.2 33.483z'/></g></svg>",
"gulp": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M17.5 12V7.75l3.4-2.55a1.5 1.5 0 0 0-1.8-2.4l-4.6 3.45V12H8v2h2l1.38 16h9.255L22 14h2v-2Z'/></svg>",
"h": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M18.5 11a5.49 5.49 0 0 0-4.5 2.344V4H8v24h6V17a2 2 0 0 1 4 0v11h6V16.5a5.5 5.5 0 0 0-5.5-5.5'/></svg>",
"hack": "<svg viewBox='0 0 32 32'><path fill='#607d8b' d='m14 9-8 8V9l8-8zm12 12L16 31v-8l10-10z'/><path fill='#ffa000' d='m6 20 8-8v8'/><path fill='#607d8b' d='m6 30 8-8H6'/><path fill='#eceff1' d='m16 20 10-10H16'/></svg>",
"hadolint": "<svg viewBox='0 0 1024 1024'><path fill='#1976d2' d='M128 704h768c35.456 0 64 28.544 64 64v128c0 35.456-28.544 64-64 64H128c-35.456 0-64-28.544-64-64V768c0-35.456 28.544-64 64-64'/><path fill='#e0e0e0' d='M160 768h704c17.728 0 32 14.272 32 32s-14.272 32-32 32H160c-17.728 0-32-14.272-32-32s14.272-32 32-32'/><path fill='#1a237e' d='M160 64c-53.02 0-96 42.98-96 96v320c0 160 128 352 288 352h320c160 0 288-256 288-512V128c0-35.346-28.654-64-64-64s-64 28.654-64 64 28.654 64 64 64v128h-64c-64-64-128-128-192-128v-32c0-53.02-42.98-96-96-96s-96 42.98-96 96v32H256v-32c0-53.02-42.98-96-96-96'/><path fill='#fafafa' d='M224 320c-53.02 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96-42.98-96-96-96m256 0c-53.02 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96-42.98-96-96-96'/><path d='M224 384a32 32 0 0 0-32 32 32 32 0 0 0 32 32 32 32 0 0 0 32-32 32 32 0 0 0-32-32m256 0a32 32 0 0 0-32 32 32 32 0 0 0 32 32 32 32 0 0 0 32-32 32 32 0 0 0-32-32'/><path fill='#ad1457' d='M320 512h64c17.728 0 32 14.272 32 32s-14.272 32-32 32h-64c-17.728 0-32-14.272-32-32s14.272-32 32-32'/><path fill='#757575' d='M352 592c-8.864 0-16 7.136-16 16v6.762l-61.281 35.383A15.964 15.964 0 0 0 268.86 672a15.966 15.966 0 0 0 21.858 5.855L352 642.475l61.281 35.38A15.966 15.966 0 0 0 435.14 672a15.964 15.964 0 0 0-5.858-21.855L368 614.762V608c0-8.864-7.136-16-16-16m-80.422-368.008c-2.578.07-5.18.768-7.578 2.153A15.964 15.964 0 0 0 258.145 248l32 55.426A15.964 15.964 0 0 0 312 309.28a15.964 15.964 0 0 0 5.855-21.855l-32-55.426a15.96 15.96 0 0 0-14.277-8.008zm160.844 0A15.96 15.96 0 0 0 418.145 232l-32 55.426A15.964 15.964 0 0 0 392 309.28a15.964 15.964 0 0 0 21.855-5.855l32-55.426A15.964 15.964 0 0 0 440 226.145a16 16 0 0 0-7.578-2.153M160 128c-17.728 0-32 14.272-32 32v64c0 17.728 14.272 32 32 32s32-14.272 32-32v-64c0-17.728-14.272-32-32-32m384 0c-17.728 0-32 14.272-32 32v64c0 17.728 14.272 32 32 32s32-14.272 32-32v-64c0-17.728-14.272-32-32-32'/></svg>",
"haml": "<svg viewBox='0 0 300 300'><path fill='#f4511e' d='M75.766 33.97c-12.149-.304-27.153 6.049-27.153 6.049l49.613 100.64-26.308 112.47c24.012 20.305 50.496 10.593 50.496 10.593l12.187-87.03c1.54 1.458 3.172 2.794 4.818 4.028 5.289 3.746 11.018 6.609 16.746 8.813 5.73 2.203 11.68 3.966 17.63 5.288 3.967.882 7.711 1.543 11.677 1.984-1.763 3.966-2.864 8.152-2.643 12.78 0 .44.22.88.661 1.1h.22c4.186 2.204 8.593 3.97 13.44 5.071 4.628.881 9.697 1.323 14.545.662 5.068-.661 10.136-2.645 14.103-5.95s6.831-7.714 8.594-12.34l.22-.221v-.219l.663-5.949v-.222c2.203-1.322 4.406-2.644 6.61-4.406 2.644-2.204 5.068-4.629 6.83-7.714 1.764-3.085 2.865-6.831 2.644-10.577-.22-3.525-1.544-7.049-3.086-10.134-1.543-3.085-3.525-5.951-5.728-8.596-4.408-5.068-9.696-9.253-15.425-12.559-5.51-3.525-11.68-6.392-17.85-8.375l-2.424-.662-1.98-.66c-1.322-.44-2.426-1.101-3.527-1.542-2.204-1.322-3.748-2.645-4.85-4.408-2.203-3.305-2.423-8.371-1.321-13.66.22-1.322.662-2.645 1.103-3.967s.88-2.645 1.321-4.188a27 27 0 0 0 .603-4.44l29.451-25.77c-2.295-8.474-27.722-17.303-27.722-17.303l-75.57 64.269-43.61-82.277c-1.566-.353-3.245-.532-4.98-.575zm108.6 73.763c-.452 2.355-.652 4.836-.652 7.32.22 3.746 1.323 7.936 3.746 11.462s5.728 6.169 9.034 7.711a30 30 0 0 0 5.07 1.984l2.645.66 2.202.44c5.73 1.543 11.237 3.746 16.305 6.611s9.916 6.39 13.883 10.357 7.052 9.035 7.493 14.103c.22 3.526-.222 6.17-1.324 8.373-1.101 2.424-2.864 4.627-4.847 6.61-.881.882-1.983 1.762-2.864 2.423q0-2.974-.66-5.948c-.22-1.102-.442-1.983-.882-3.085-.44-.881-.88-1.982-1.982-2.643-.22 0-.443-.001-.443.219-1.322 3.305-3.525 6.171-5.948 8.154s-5.51 2.864-8.594 3.085c-3.085.22-6.392-.44-9.697-1.322-3.306-.881-6.609-1.985-9.914-3.086l-.443-.221c-.661-.22-1.539.002-1.98.443-1.762 2.204-3.086 4.184-4.628 6.388a59 59 0 0 0-2.864 5.07c-3.967-1.543-7.715-2.866-11.68-4.408-5.51-2.204-11.02-4.406-16.087-7.05-5.289-2.424-10.354-5.29-14.761-8.596-3.178-2.383-6.114-4.886-8.255-7.747l2.424-17.318z'/></svg>",
"handlebars": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M12.023 12a4 4 0 0 0-3.94 3.182 1 1 0 0 1-.972.818H5.229C4.446 16 4 15.552 4 15v-1H3a1 1 0 0 0-1 1v1c0 3.866 3.134 6 7 6 3.425 0 6.275-1.675 6.881-4.745.545-2.764-1.041-5.24-3.858-5.255'/><path fill='#ff7043' d='M29 14h-1v1c0 .552-.446 1-1.229 1H24.89a1 1 0 0 1-.973-.818A4 4 0 0 0 19.977 12c-2.817.016-4.403 2.491-3.858 5.255C16.725 20.325 19.575 22 23 22c3.866 0 7-2.134 7-6v-1a1 1 0 0 0-1-1'/></svg>",
- "hardhat": "<svg viewBox='0 0 24 24'><path fill='#FFD600' d='M9.87 12.15 9 6.46a9.9 9.9 0 0 1 6 0l-.87 5.69c-.07.49-.5.85-1 .85h-2.27a1 1 0 0 1-.99-.85M22 16c0-.79-.47-1.5-1.2-1.83A9.08 9.08 0 0 0 17 8.5l-1.76 4.84c-.14.4-.52.66-.94.66H9.7c-.42 0-.8-.26-.94-.66L7 8.5a9.1 9.1 0 0 0-3.8 5.66C2.47 14.5 2 15.2 2 16l6.45 1.84c.36.1.73.16 1.1.16h4.88c.37 0 .74-.06 1.1-.16z'/></svg>",
+ "hardhat": "<svg viewBox='0 0 24 24'><path fill='#ffd600' d='M9.87 12.15 9 6.46a9.9 9.9 0 0 1 6 0l-.87 5.69c-.07.49-.5.85-1 .85h-2.27a1 1 0 0 1-.99-.85M22 16c0-.79-.47-1.5-1.2-1.83A9.08 9.08 0 0 0 17 8.5l-1.76 4.84c-.14.4-.52.66-.94.66H9.7c-.42 0-.8-.26-.94-.66L7 8.5a9.1 9.1 0 0 0-3.8 5.66C2.47 14.5 2 15.2 2 16l6.45 1.84c.36.1.73.16 1.1.16h4.88c.37 0 .74-.06 1.1-.16z'/></svg>",
"harmonix": "<svg viewBox='0 0 32 32'><path fill='#536dfe' d='M27 13 16 2 5 13l8 8-6 6 3 3 6-6 6 6 3-3-6-6zm-17 0 6-6 6 6-6 6z'/></svg>",
"haskell": "<svg viewBox='0 0 300 300'><g stroke-width='2.422'><path fill='#ef5350' d='m23.928 240.5 59.94-89.852-59.94-89.855h44.955l59.94 89.855-59.94 89.852z'/><path fill='#ffa726' d='m83.869 240.5 59.94-89.852-59.94-89.855h44.955l119.88 179.71h-44.95l-37.46-56.156-37.468 56.156z'/><path fill='#ffee58' d='m228.72 188.08-19.98-29.953h69.93v29.956h-49.95zm-29.97-44.924-19.98-29.953h99.901v29.953z'/></g></svg>",
- "haxe": "<svg viewBox='0 0 210 210'><path fill='#FB8C00' d='m41.559 104.988 63.43-63.43 63.432 63.43-63.431 63.431z'/><path fill='#FFB300' d='M41.578 105.037 29.973 61.726 18.368 18.415l43.31 11.605 43.312 11.605-31.706 31.706z'/><path fill='#FFCA28' d='M104.735 41.555 61.545 30.01 18.367 18.413l22.927.185 23.228.294 20.263 11.36z'/><path fill='#FFEA00' d='m18.368 18.417 11.597 43.177 11.544 43.19-11.303-19.948-11.36-20.263-.294-23.229z'/><path fill='#EF6C00' d='m104.999 41.579 43.31-11.605 43.312-11.605-11.605 43.311-11.605 43.311-31.706-31.706z'/><path fill='#E64A19' d='m168.49 104.735 11.545-43.19 11.598-43.177-.185 22.927-.294 23.228-11.36 20.264z'/><path fill='#FFCA28' d='m191.628 18.365-43.176 11.597-43.19 11.544 19.948-11.303 20.263-11.36 23.228-.293z'/><path fill='#EF6C00' d='m168.419 104.987 11.605 43.311 11.605 43.311-43.311-11.605-43.311-11.605 31.706-31.706z'/><path fill='#FB8C00' d='m105.261 168.47 43.19 11.544 43.177 11.597-22.927-.185-23.229-.294-20.263-11.36z'/><path fill='#E64A19' d='m191.631 191.617-11.597-43.177-11.545-43.19 11.304 19.948 11.36 20.263.293 23.229z'/><path fill='#FFA000' d='m104.99 168.422-43.31 11.605-43.311 11.605 11.605-43.311 11.605-43.311 31.706 31.706z'/><path fill='#FFEA00' d='m41.51 105.27-11.545 43.19-11.597 43.176.185-22.927.294-23.228 11.36-20.264z'/><path fill='#EF6C00' d='m18.368 191.63 43.176-11.598 43.19-11.544-19.947 11.303-20.264 11.36-23.228.293z'/></svg>",
+ "haxe": "<svg viewBox='0 0 210 210'><path fill='#fb8c00' d='m41.559 104.988 63.43-63.43 63.432 63.43-63.431 63.431z'/><path fill='#ffb300' d='M41.578 105.037 29.973 61.726 18.368 18.415l43.31 11.605 43.312 11.605-31.706 31.706z'/><path fill='#ffca28' d='M104.735 41.555 61.545 30.01 18.367 18.413l22.927.185 23.228.294 20.263 11.36z'/><path fill='#ffea00' d='m18.368 18.417 11.597 43.177 11.544 43.19-11.303-19.948-11.36-20.263-.294-23.229z'/><path fill='#ef6c00' d='m104.999 41.579 43.31-11.605 43.312-11.605-11.605 43.311-11.605 43.311-31.706-31.706z'/><path fill='#e64a19' d='m168.49 104.735 11.545-43.19 11.598-43.177-.185 22.927-.294 23.228-11.36 20.264z'/><path fill='#ffca28' d='m191.628 18.365-43.176 11.597-43.19 11.544 19.948-11.303 20.263-11.36 23.228-.293z'/><path fill='#ef6c00' d='m168.419 104.987 11.605 43.311 11.605 43.311-43.311-11.605-43.311-11.605 31.706-31.706z'/><path fill='#fb8c00' d='m105.261 168.47 43.19 11.544 43.177 11.597-22.927-.185-23.229-.294-20.263-11.36z'/><path fill='#e64a19' d='m191.631 191.617-11.597-43.177-11.545-43.19 11.304 19.948 11.36 20.263.293 23.229z'/><path fill='#ffa000' d='m104.99 168.422-43.31 11.605-43.311 11.605 11.605-43.311 11.605-43.311 31.706 31.706z'/><path fill='#ffea00' d='m41.51 105.27-11.545 43.19-11.597 43.176.185-22.927.294-23.228 11.36-20.264z'/><path fill='#ef6c00' d='m18.368 191.63 43.176-11.598 43.19-11.544-19.947 11.303-20.264 11.36-23.228.293z'/></svg>",
"hcl": "<svg viewBox='0 0 32 32'><path fill='#eceff1' d='M18 1.2V14h-4v-4l-4 2v16.37l4 2.43V18h4v4l4-2V3.63z'/><path fill='#eceff1' d='M14 1.2 2 8.49v15.02l4 2.43v-15.2l8-4.86zm12 4.86v15.2l-8 4.86v4.68l12-7.29V8.49z'/></svg>",
"hcl_light": "<svg viewBox='0 0 32 32'><path fill='#455a64' d='M18 1.2V14h-4v-4l-4 2v16.37l4 2.43V18h4v4l4-2V3.63z'/><path fill='#455a64' d='M14 1.2 2 8.49v15.02l4 2.43v-15.2l8-4.86zm12 4.86v15.2l-8 4.86v4.68l12-7.29V8.49z'/></svg>",
"helm": "<svg data-name='Layer 1' viewBox='0 0 300 300'><path fill='#00acc1' d='M148.014 286.552c-1.986-1.376-4.331-5.94-5.67-11.019-1.19-4.517-1.64-16.429-.845-22.27.297-2.175.467-4.026.374-4.116-.088-.092-1.934-.38-4.102-.64-12.481-1.506-25.802-5.747-35.943-11.447-2.15-1.21-4.167-2.199-4.477-2.199-.312 0-1.407 1.62-2.436 3.6-4.777 9.198-13.122 18.332-19.465 21.305-2.315 1.086-5.402 1.35-6.829.585-2.152-1.152-2.79-5.72-1.503-10.777.879-3.463 4.416-10.749 7.283-15.005 1.267-1.88 3.768-5.068 5.562-7.086l3.254-3.667-1.697-1.623c-4.472-4.273-12.874-14.16-12.874-15.152 0-.354 11.463-8.317 12.298-8.543.424-.114 1.451.901 2.843 2.814 3.012 4.14 11.067 12.105 15.807 15.627 36.132 26.861 87.06 20.116 114.48-15.165 1.456-1.873 2.874-3.408 3.15-3.408.55 0 11.897 7.695 12.29 8.332.356.574-4.603 7-8.838 11.459-2.011 2.117-3.656 4.021-3.651 4.23 0 .211 1.582 2.045 3.508 4.078 6.121 6.453 12.145 16.528 13.818 23.109 1.284 5.056.646 9.625-1.504 10.777-.466.25-1.697.45-2.74.448-6.595-.015-17.532-10.605-24.022-23.26-1.253-2.44-2.46-4.437-2.687-4.437s-1.5.7-2.83 1.55c-9.43 6.044-23.51 11.257-35.69 13.213-2.59.418-4.922.924-5.182 1.124-.336.261-.294 1.482.15 4.22.791 4.893.486 16.505-.556 21.201-1.078 4.848-2.73 8.605-4.771 10.834-2.344 2.564-4.166 2.93-6.508 1.308zm-129.646-98.84c-.116-.308-.163-16.387-.099-35.734l.112-35.176h18.437l.116 12.949c.088 9.689.255 13.037.66 13.294.299.19 4.914.348 10.256.352 7.565.004 9.82-.123 10.199-.58.323-.392.525-4.815.602-13.301l.114-12.714h18.437v71.231H58.766l-.117-13.593c-.077-9.135-.275-13.785-.602-14.18-.379-.458-2.645-.587-10.256-.587s-9.876.13-10.256.587c-.326.395-.524 5.045-.603 14.18l-.116 13.593-9.12.119c-7.057.092-9.167-.007-9.33-.44zm74.624-.004c-.117-.306-.16-16.383-.097-35.73l.112-35.176h45.654l.121 7.798.119 7.798-13.508.116-13.51.117-.123 5.241c-.092 3.898.02 5.334.44 5.6.308.198 5.556.36 11.656.365l11.094.006-.119 7.805-.12 7.805-11.413.22-11.412.22v12.311l14.046.22 14.048.22v15.39l-23.39.111c-18.476.09-23.431 0-23.598-.44zm61.457 0c-.117-.306-.16-16.383-.097-35.73l.112-35.176h18.437l.22 27.7.22 27.702 13.388.22 13.39.22v15.39l-22.729.113c-17.95.088-22.776-.004-22.94-.44zm58.38-.005c-.114-.303-.158-16.378-.096-35.725l.114-35.176 9.658-.11c5.312-.057 9.876.042 10.142.22.65.442 10.399 26.997 12.551 34.186 2.236 7.48 2.062 7.036 2.57 6.53.238-.238 1.275-3.188 2.307-6.56 2.273-7.425 11.26-33.167 11.854-33.945.325-.427 2.69-.528 10.13-.44l9.707.119v71.231l-8.22.121-8.22.119-.29-1.165c-.607-2.417.206-27.862 1.12-35.132 1.44-11.435 1.223-11.613-2.172-1.76-3.671 10.661-10.968 30.259-11.665 31.33-.598.923-.958.989-5.188.989-3.115 0-4.68-.172-4.975-.55-.543-.695-9.505-25.085-11.954-32.531-1.728-5.255-2.666-7.145-2.636-5.307.007.44.411 4.06.897 8.05.908 7.476 1.713 32.53 1.12 34.9l-.29 1.154h-8.127c-6.098 0-8.178-.137-8.336-.55zm5.807-81.212c-1.728-2.933-6.079-8.407-9.761-12.281-11.855-12.477-26.13-20.363-43.656-24.118-5.02-1.075-6.351-1.176-15.585-1.196-10.872-.024-13.266.247-21.686 2.452-11.397 2.985-21.79 8.282-30.992 15.796-4.424 3.612-11.47 11.043-14.48 15.27-1.853 2.604-2.787 3.567-3.275 3.382-1.599-.607-12.312-7.811-12.308-8.273.016-1.042 7.233-10.091 12.13-15.211l5.063-5.294-3.349-3.632c-6.12-6.64-11.988-16.529-13.624-22.961-1.284-5.057-.646-9.625 1.506-10.78.464-.248 1.697-.45 2.74-.448 6.595.015 17.53 10.608 24.023 23.262 1.25 2.44 2.429 4.435 2.616 4.435.191 0 1.906-.89 3.814-1.979 9.805-5.595 25.545-10.584 36.26-11.487 1.849-.156 3.595-.427 3.878-.603.39-.241.33-1.42-.235-4.808-1.042-6.243-.717-18.472.624-23.552 2.126-8.051 5.54-12.56 9.014-11.907 3.243.609 6.387 5.898 8.025 13.498 1.066 4.958 1.092 18.903.044 23.472-.387 1.692-.576 3.214-.418 3.38.158.168 2.264.617 4.676.995 13.991 2.198 24.601 6.105 37.603 13.837.886.528 1.717.78 1.851.565s1.333-2.574 2.665-5.241c5.144-10.294 13.426-19.648 20.215-22.832 2.317-1.086 5.402-1.35 6.829-.584 2.154 1.154 2.792 5.72 1.506 10.777-.94 3.693-4.549 10.983-7.783 15.719-1.51 2.212-4.577 5.872-6.815 8.134l-4.068 4.112 4.002 4.256c4.047 4.302 11.194 13.426 12.503 15.961.593 1.145.613 1.466.123 1.917-1.005.926-11.8 7.264-12.371 7.264-.297 0-.884-.583-1.304-1.297'/></svg>",
- "heroku": "<svg viewBox='0 0 32 32'><path fill='#7E57C2' d='M28 2H6a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h22a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2M10 26v-6l4 3Zm14 0h-4v-6.45a2.55 2.55 0 0 0-.95-1.987 2.75 2.75 0 0 0-2.278-.478L10 18.44V6h4v7.56l2.16-.43A6.558 6.558 0 0 1 24 19.55Zm0-16h-4V6h4Z'/></svg>",
+ "heroku": "<svg viewBox='0 0 32 32'><path fill='#7e57c2' d='M28 2H6a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h22a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2M10 26v-6l4 3Zm14 0h-4v-6.45a2.55 2.55 0 0 0-.95-1.987 2.75 2.75 0 0 0-2.278-.478L10 18.44V6h4v7.56l2.16-.43A6.558 6.558 0 0 1 24 19.55Zm0-16h-4V6h4Z'/></svg>",
"hex": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='M4 8v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2m4 14V10h4v12Zm11.999-6L18 18.001 21 21l-3 3.001L19.999 26l3.003-3 3 2.999L28 24l-3-2.999 3-3L26.001 16l-3 3z'/></svg>",
"histoire": "<svg clip-rule='evenodd' viewBox='0 0 16 16'><path fill='#1de9b6' d='M8.814 1.004a.19.19 0 0 0-.162.09l-5.61 7.529A.247.247 0 0 0 3.25 9H7v5.817c-.001.204.266.25.373.076l5.592-7.567c.087-.14.025-.326-.14-.326H9V1.194a.19.19 0 0 0-.185-.19z'/></svg>",
"hjson": "<svg viewBox='0 0 24 24'><path fill='#78909c' d='M11.652 3.92a1.1 1.1 0 0 0-.626-.014 1.11 1.11 0 0 0-.786 1.359 1.11 1.11 0 0 0 1.362.788 1.11 1.11 0 0 0 .786-1.363 1.11 1.11 0 0 0-.736-.771zm-3.6-.229a1 1 0 0 0-.246-.027h-.002a418 418 0 0 1-3.02-.096.84.84 0 0 0-.38.089.69.69 0 0 0-.339.506v.001a.68.68 0 0 0 .168.553.9.9 0 0 0 .41.235l.002.001c1.275.388 2.552.766 3.923 1.179l.496.15-.133-.5c-.16-.6-.314-1.098-.423-1.592a.67.67 0 0 0-.292-.434.6.6 0 0 0-.164-.065m2.613 3.73a.56.56 0 0 0-.274.003l-.362.097a.554.554 0 0 0-.392.678l1.105 4.123a.55.55 0 0 0 .677.39l.364-.098a.55.55 0 0 0 .392-.676L11.07 7.816a.55.55 0 0 0-.404-.396z'/><path fill='#8bc34a' d='m21.578 9.77-2.203.59.59 2.203-5.508 1.476a2.28 2.28 0 0 0-1.612 2.793 2.28 2.28 0 0 0-2.79-1.61l-5.508 1.475-.59-2.205-2.203.59.59 2.205A2.28 2.28 0 0 0 5.138 18.9l4.406-1.18a2.28 2.28 0 0 1 2.792 1.61l.295 1.102 2.203-.59-.295-1.103a2.28 2.28 0 0 1 1.613-2.79l4.405-1.18a2.28 2.28 0 0 0 1.612-2.794v-.002z'/></svg>",
@@ -730,7 +737,7 @@
"hosts": "<svg viewBox='0 0 16 16'><path fill='#cfd8dc' d='m14 6-3-3v2H7v2h4v2M5 7l-3 3 3 3v-2h4V9H5z'/></svg>",
"hosts_light": "<svg viewBox='0 0 16 16'><path fill='#455a64' d='m14 6-3-3v2H7v2h4v2M5 7l-3 3 3 3v-2h4V9H5z'/></svg>",
"hpp": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M28 6V2h-2v4h-6V2h-2v4h-4v2h4v4h2V8h6v4h2V8h4V6zm-15.5 5A5.49 5.49 0 0 0 8 13.344V4H2v24h6V17a2 2 0 0 1 4 0v11h6V16.5a5.5 5.5 0 0 0-5.5-5.5'/></svg>",
- "html": "<svg viewBox='0 0 32 32'><path fill='#E65100' d='m4 4 2 22 10 2 10-2 2-22Zm19.72 7H11.28l.29 3h11.86l-.802 9.335L15.99 25l-6.635-1.646L8.93 19h3.02l.19 2 3.86.77 3.84-.77.29-4H8.84L8 8h16Z'/></svg>",
+ "html": "<svg viewBox='0 0 32 32'><path fill='#e65100' d='m4 4 2 22 10 2 10-2 2-22Zm19.72 7H11.28l.29 3h11.86l-.802 9.335L15.99 25l-6.635-1.646L8.93 19h3.02l.19 2 3.86.77 3.84-.77.29-4H8.84L8 8h16Z'/></svg>",
"http": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2m11.3 10h-5.64a22.5 22.5 0 0 0-2.705-7.616A12.03 12.03 0 0 1 27.3 12M20 16c0 .693-.037 1.357-.094 2h-7.811A22 22 0 0 1 12 16c0-.693.037-1.357.094-2h7.811c.058.643.095 1.307.095 2m-4 12c-.115 0-.226-.014-.34-.017A20.4 20.4 0 0 1 12.368 20h7.264a20.4 20.4 0 0 1-3.292 7.983c-.114.003-.225.017-.34.017m-3.632-16a20.4 20.4 0 0 1 3.292-7.983c.114-.003.225-.017.34-.017s.226.014.34.017A20.4 20.4 0 0 1 19.632 12Zm.683-7.618A22.4 22.4 0 0 0 10.339 12H4.7a12.03 12.03 0 0 1 8.35-7.618ZM4.18 14h5.91c-.052.647-.091 1.307-.091 2s.039 1.353.091 2H4.18a11.2 11.2 0 0 1 0-4m.52 6h5.638a22.4 22.4 0 0 0 2.712 7.618A12.03 12.03 0 0 1 4.7 20m14.255 7.616A22.5 22.5 0 0 0 21.661 20H27.3a12.03 12.03 0 0 1-8.344 7.616ZM27.819 18h-5.91c.052-.647.091-1.307.091-2s-.039-1.353-.091-2h5.91a11.2 11.2 0 0 1 0 4'/></svg>",
"huff": "<svg viewBox='0 0 32 32'><rect width='16' height='2' x='8' y='28' fill='#cfd8dc' rx='.5'/><path fill='#cfd8dc' d='M12 23v1h-1a1 1 0 0 0-1 1v1h12v-1a1 1 0 0 0-1-1h-1v-1a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1m11.916-11.126L20 6V4h2a9.24 9.24 0 0 0-6.176-1.999 8.063 8.063 0 0 0-7.822 8.2 12.3 12.3 0 0 0 .63 3.696L10 18v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-.586a1 1 0 0 0-.293-.707L16 12h2l2.874 1.916a.5.5 0 0 0 .277.084H23.5a.5.5 0 0 0 .5-.5v-1.349a.5.5 0 0 0-.084-.277M20 10h-1a1 1 0 0 1-1-1V8h1a1 1 0 0 1 1 1Z'/></svg>",
"huff_light": "<svg viewBox='0 0 32 32'><rect width='16' height='2' x='8' y='28' fill='#607d8b' rx='.5'/><path fill='#607d8b' d='M12 23v1h-1a1 1 0 0 0-1 1v1h12v-1a1 1 0 0 0-1-1h-1v-1a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1m11.916-11.126L20 6V4h2a9.24 9.24 0 0 0-6.176-1.999 8.063 8.063 0 0 0-7.822 8.2 12.3 12.3 0 0 0 .63 3.696L10 18v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-.586a1 1 0 0 0-.293-.707L16 12h2l2.874 1.916a.5.5 0 0 0 .277.084H23.5a.5.5 0 0 0 .5-.5v-1.349a.5.5 0 0 0-.084-.277M20 10h-1a1 1 0 0 1-1-1V8h1a1 1 0 0 1 1 1Z'/></svg>",
@@ -742,14 +749,14 @@
"image": "<svg viewBox='0 0 16 16'><path fill='#26a69a' d='M8.5 6h4l-4-4zM3.875 1H9.5l4 4v8.6c0 .773-.616 1.4-1.375 1.4h-8.25c-.76 0-1.375-.627-1.375-1.4V2.4c0-.777.612-1.4 1.375-1.4M4 13.6h8V8l-2.625 2.8L8 9.4zm1.25-7.7c-.76 0-1.375.627-1.375 1.4s.616 1.4 1.375 1.4c.76 0 1.375-.627 1.375-1.4S6.009 5.9 5.25 5.9'/></svg>",
"imba": "<svg stroke-linejoin='round' stroke-miterlimit='1.414' clip-rule='evenodd' viewBox='0 0 201 201'><path fill='#ffc400' d='M161.96 61.952c-3.043 13.905-32.633 79.576-36.431 94.457-2.698 10.575 11.229 23.851 13.555 15.159 6.84-25.548 37.32-86.251 39.023-98.893 1.468-10.897-14.66-17.516-16.147-10.723m-37.128 48.192a4.97 4.97 0 0 1 5.726 6.91c.023.012.021.015.021.016a19.04 19.04 0 0 1-13.667 10.676c-3.4.645-7.236 1.182-11.504 1.588-15.316 1.453-31.743-17.007-20.624-16.49 16.552.77 29.747-.447 40.047-2.7zm16.256-17.347a13.36 13.36 0 0 1-9.677 8.152c-20.232 4.242-49.32 2.59-63.662-.888-13.94-3.38-23.102-23.665-14.05-20.64 21.019 7.024 60.118 9.347 82.248 6.838a4.808 4.808 0 0 1 5.133 6.523c.011.004.011.004.008.015m8.398-23.8a11.39 11.39 0 0 1-9.973 8.037c-40.633 2.924-92.83-6.466-107.91-22.019C20.273 43.326 21 20.85 27.442 27.992c24.417 27.072 84.437 34.865 117.12 34.521a5.022 5.022 0 0 1 4.92 6.481q.003.002.001.003z'/></svg>",
"installation": "<svg viewBox='0 0 16 16'><path fill='#ff5722' d='M12 7h-2V2H6v5H4l4 4zm-9 5.5V14h10v-1.5z'/></svg>",
- "ionic": "<svg viewBox='0 0 512 512'><g fill='#448AFF'><path d='M423.59 132.8a31.86 31.86 0 0 0 5.408-17.804c0-17.675-14.33-32-32-32a31.85 31.85 0 0 0-17.805 5.409c-34.486-25.394-77.085-40.409-123.2-40.409-114.88 0-208 93.125-208 208 0 114.88 93.125 208 208 208 114.87 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.2zm-31.762 259.03c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089s-51.095-5.077-74.767-15.089c-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064c-10.013-23.672-15.09-48.828-15.09-74.768s5.077-51.095 15.089-74.767c9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09s51.094 5.077 74.766 15.089a191.2 191.2 0 0 1 37.802 21.327 31.85 31.85 0 0 0-3.568 14.679c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191 191 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767s-5.077 51.096-15.09 74.768c-9.675 22.873-23.527 43.418-41.175 61.064'/><circle cx='256' cy='256' r='96'/></g></svg>",
+ "ionic": "<svg viewBox='0 0 512 512'><g fill='#448aff'><path d='M423.59 132.8a31.86 31.86 0 0 0 5.408-17.804c0-17.675-14.33-32-32-32a31.85 31.85 0 0 0-17.805 5.409c-34.486-25.394-77.085-40.409-123.2-40.409-114.88 0-208 93.125-208 208 0 114.88 93.125 208 208 208 114.87 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.2zm-31.762 259.03c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089s-51.095-5.077-74.767-15.089c-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064c-10.013-23.672-15.09-48.828-15.09-74.768s5.077-51.095 15.089-74.767c9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09s51.094 5.077 74.766 15.089a191.2 191.2 0 0 1 37.802 21.327 31.85 31.85 0 0 0-3.568 14.679c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191 191 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767s-5.077 51.096-15.09 74.768c-9.675 22.873-23.527 43.418-41.175 61.064'/><circle cx='256' cy='256' r='96'/></g></svg>",
"istanbul": "<svg viewBox='0 0 32 32'><path fill='#fdd835' d='M30 13v-3h-2v3h-1v.5a.5.5 0 0 0 .5.5h.5v10h-1v-2h-1v-8h-1v6h-2v-3h-1v1h-1v-1h1a6.35 6.35 0 0 0-6-4 6.35 6.35 0 0 0-6 4h1v1h-1v-1H9v3H7v-6H6v8H5v2H4V14h.5a.5.5 0 0 0 .5-.5V13H4v-3H2v3H1v.5a.5.5 0 0 0 .5.5H2v12h8v-6h2v6h8v-6h2v6h8V14h.5a.5.5 0 0 0 .5-.5V13ZM14 24h-1v-2h1Zm3 0h-2v-2a1 1 0 0 1 2 0Zm2 0h-1v-2h1Zm-1-7v1h-1v-1Zm-3 0v1h-1v-1Zm-3 0h1v1h-1Zm7 1v-1h1v1ZM3 6 2 9h2zm26 0-1 3h2z'/><path fill='#fdd835' d='m16 10-1 2h2zm9.5 1-.5 2h1zm-19 0L6 13h1z'/></svg>",
"jar": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M22 10h2v4h-2z'/><path fill='#f44336' d='M28 2H4a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2m-2 12a2 2 0 0 1-2 2h-2v4a4 4 0 0 1-4 4h-8a4 4 0 0 1-4-4V8h18a2 2 0 0 1 2 2Z'/></svg>",
"java": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M4 26h24v2H4zM28 4H7a1 1 0 0 0-1 1v13a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4v-4h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 8h-4V6h4Z'/></svg>",
"javaclass": "<svg viewBox='0 0 32 32'><path fill='#1e88e5' d='M4 26h24v2H4zM28 4H7a1 1 0 0 0-1 1v13a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4v-4h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 8h-4V6h4Z'/></svg>",
"javascript-map": "<svg viewBox='0 0 16 16'><g fill='#ffca28'><path d='M12 5v1h1v7H6v-1H5l-.069 2 9.069.001V5z'/><path d='M2 2v9h9V2zm3 3h1v4a1.003 1.003 0 0 1-1 1H4a1.003 1.003 0 0 1-1-1V8h1v1h1zm3 0h2v1H8v1h1a1.003 1.003 0 0 1 1 1v1a1.003 1.003 0 0 1-1 1H7V9h2V8H8a1.003 1.003 0 0 1-1-1V6a1.003 1.003 0 0 1 1-1'/></g></svg>",
"javascript": "<svg viewBox='0 0 16 16'><path fill='#ffca28' d='M2 2v12h12V2zm6 6h1v4a1.003 1.003 0 0 1-1 1H7a1.003 1.003 0 0 1-1-1v-1h1v1h1zm3 0h2v1h-2v1h1a1.003 1.003 0 0 1 1 1v1a1.003 1.003 0 0 1-1 1h-2v-1h2v-1h-1a1.003 1.003 0 0 1-1-1V9a1.003 1.003 0 0 1 1-1'/></svg>",
- "jenkins": "<svg viewBox='0 0 180 180'><defs><clipPath id='a'><path fill='#37474f' d='M.899 144.42h144.42V0H.899z'/></clipPath></defs><g clip-path='url(#a)' transform='matrix(1.0691 0 0 -1.0691 9.4 166.143)'><g fill-rule='evenodd'><path fill='#f0d6b7' d='m107.96 30.661-12.506-1.876-16.883-1.876-10.943-.312-10.629.312-8.13 2.502-7.19 7.815-5.628 15.945-1.25 3.44-7.504 2.5-4.377 7.191-3.126 10.317 3.44 9.067 8.128 2.814 6.565-3.127 3.127-6.878 3.752.626 1.25 1.563-1.25 7.19-.313 9.068 1.876 12.505-.074 7.143 5.701 9.114 10.005 7.19 17.508 7.504 19.383-2.814 16.883-12.193 7.817-12.505 5.002-9.067 1.25-22.51-3.752-19.384-6.877-17.195-6.566-9.066'/><path fill='#335061' d='m97.334-23.425-44.709-1.876v-7.503l3.752-26.262-1.876-2.19-31.264 10.63-2.19 3.752-3.126 35.328-7.19 21.26-1.563 5.002 25.01 17.195 7.818 3.127 6.877-8.441 5.94-5.315 6.88-2.188 3.125-.938L68.57 1.899l2.814-3.44 7.19 2.502-5.002-9.693 27.2-12.818-3.439-1.876'/><path fill='#6d6b6d' d='m23.238 85.687 8.128 2.814 6.566-3.127 3.127-6.878 3.751.626.938 3.751-1.876 7.19 1.876 17.197-1.563 9.379 5.627 6.565 12.193 9.692-3.44 4.69-17.194-8.442-7.191-5.627-4.064-8.754-6.253-8.442-1.876-10.005z'/><path fill='#dcd9d8' d='M36.055 115.07s4.69 11.567 23.448 17.195c18.759 5.628.938 4.065.938 4.065l-20.321-7.817-7.817-7.816-3.438-6.253zm-9.379-27.195s-6.566 21.886 18.446 25.012l-.938 3.752-17.195-4.065-5.003-16.257 1.251-10.63z'/></g><g fill='#f7e4cd'><path fill-rule='evenodd' d='m36.681 58.799 4.094 3.966s1.847-.214 2.16-2.402c.312-2.19 1.25-21.886 14.693-32.516 1.227-.97-10.004 1.564-10.004 1.564L37.62 45.042m56.589 19.697s.729 9.477 3.28 8.748c2.553-.729 2.553-3.28 2.553-3.28s-6.198-4.01-5.833-5.468'/><path d='M120.16 99.442s-5.153-1.088-5.628-5.628c-.474-4.54 5.628-.938 6.566-.625m-38.771 5.94s-6.879-.938-6.879-5.314c0-4.378 7.817-4.065 10.005-2.19'/><g fill-rule='evenodd'><path d='M39.807 78.808s-11.881 7.191-13.131.312c-1.25-6.877-4.065-11.88 1.876-19.07l-4.064 1.25-3.752 9.691-1.25 9.38 7.19 7.504 8.129-.626 4.69-3.751zm5.628 19.696s5.315 27.512 32.203 32.827c22.136 4.375 33.765-.938 38.142-5.94 0 0-19.696 23.447-38.455 16.257-18.759-7.191-32.514-20.322-32.202-28.762.532-14.377.313-14.382.313-14.382m72.534 23.766s-9.066.312-9.38-7.817c0 0 0-1.25.625-2.5 0 0 7.192 8.129 11.568 3.751'/><path d='M78.268 111.1s-1.56 12.477-12.199 5.223c-6.878-4.69-6.252-11.255-5.002-12.505s.91-3.77 1.862-2.04c.952 1.728.638 7.356 4.078 8.918 3.439 1.564 9.077 3.31 11.26.404'/></g></g><path fill='#49728b' fill-rule='evenodd' d='M48.874 26.597 19.486 13.466s12.193-48.46 5.94-63.467l-4.377 1.563-.313 18.446-8.128 35.015-3.44 9.692 30.639 20.633 9.067-8.753M51.896-.206l4.17-5.087v-18.76h-5.003s-.625 13.132-.625 14.696c0 1.563.624 7.19.624 7.19M52-26.866l-14.069-.625 4.065-2.813L52-31.868'/><g fill-rule='evenodd'><path fill='#335061' d='m100.15-23.739 11.567.313 2.814-28.764-11.881-1.563z'/><path fill='#335061' d='m103.27-23.739 17.508.938s7.19 18.133 7.19 19.07c0 .939 6.253 26.263 6.253 26.263l-14.069 14.694-2.813 2.501-7.504-7.503V3.148z'/><path fill='#49728b' d='m111.09-21.55-10.942-2.188 1.563-8.755c4.064-1.876 10.943 3.127 10.943 3.127M111.4 33.162l21.885-16.257.626 7.503-16.57 15.32-5.94-6.566'/><path fill='#FAFAFA' d='m62.85-85.332-6.473 26.266-3.22 19.38-.531 14.385 29.296 1.56 18.226.003-1.658-32.83 2.814-25.324-.312-4.69-23.76-1.876z'/><path fill='#dcd9d8' d='M96.083-23.426s-1.563-32.515 3.127-55.65c0 0-9.38-5.94-23.136-7.503l26.262.938 3.126 1.875-3.752 51.273-.938 10.944'/><path fill='#FAFAFA' d='m115.06-49.691 12.193 3.44 23.135 1.25 3.44 10.629-6.254 18.446-7.19.938-10.005-3.127-9.599-4.686-5.095.935-3.972-1.56'/><path fill='#dcd9d8' d='M114.84-43.435s8.128 3.751 9.38 3.438L120.78-22.8l4.065 1.563s2.814-16.257 2.814-18.133c0 0 17.507-.938 19.07-.938 0 0 3.752 7.191 2.814 14.694l3.44-10.005.312-5.628-5.002-7.503-5.627-1.25-9.38.312-3.126 4.064-10.943-1.563-3.44-1.25'/></g><path fill='#FAFAFA' d='M102.56-21.241 95.682-3.733l-7.19 10.317s1.562 4.377 3.75 4.377h7.192l6.878-2.501-.625-11.568-3.127-18.134'/><path fill='#dcd9d8' fill-rule='evenodd' d='M103.9-15.297S95.145 1.585 95.145 4.086c0 0 1.563 3.752 3.752 2.814s6.879-3.439 6.879-3.439v5.94l-10.63 2.19-7.19-.939 12.193-28.763 2.5-.313'/><path fill='#FAFAFA' d='m65.664 25.968-8.661.942-8.13 2.501v-2.814l3.972-4.38 12.506-5.627'/><path fill='#dcd9d8' fill-rule='evenodd' d='M51.689 25.031s9.693-4.065 12.819-3.127l.311-3.748-8.752 1.872-5.316 3.752z'/><path fill='#d33833' fill-rule='evenodd' d='M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43'/><path fill='none' stroke='#d33833' stroke-width='2' d='M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43z'/><path fill='#d33833' fill-rule='evenodd' d='M89.66 18.569q-.021-.603-.047-1.21c-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669'/><path fill='none' stroke='#d33833' stroke-width='2' d='M89.66 18.569q-.021-.603-.047-1.21c-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669z'/><path fill='#d33833' fill-rule='evenodd' d='M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695'/><path fill='none' stroke='#d33833' stroke-width='2' d='M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695z'/><g fill-rule='evenodd'><path fill='#ef3d3a' d='M102.87 10.649s-2.19 3.127-.626 4.065 3.127 0 4.065 1.563 0 2.501.313 4.377 1.877 2.189 3.44 2.501c1.562.313 5.94.938 6.565-.625l-1.876 5.627-3.752 1.25-11.88-6.877-.626-3.44v-6.877M70.041.331c-.376 4.88-.773 9.752-1.215 14.626-.662 7.279 1.748 6.009 8.057 6.009.964 0 5.933-1.15 6.289-1.876 1.705-3.483-2.851-2.709 1.964-5.335 4.065-2.216 11.246 1.346 9.603 6.273-.919 1.095-4.789.341-6.176 1.06l-7.327 3.8c-3.108 1.612-10.29 3.962-13.603 1.709-8.395-5.71.53-19.974 3.524-25.93'/><path fill='#231f20' d='M78.268 111.1c-8.521 1.985-12.755-3.566-15.338-9.323-2.306.559-1.389 3.695-.806 5.294 1.525 4.194 7.672 9.778 12.694 9.02 2.161-.325 5.086-2.301 3.45-4.99m41.522-9.701.404-.016c1.926-4 3.593-8.238 6.022-11.769-1.628-3.79-12.322-7.144-12.157-.338 2.313 1.01 6.305.206 8.356 1.497-1.186 3.254-2.897 6.024-2.625 10.626m-37.16-.11c1.827-3.35 2.422-6.868 5.019-9.4 1.17-1.14 3.444-2.529 2.316-5.698-.263-.747-2.189-2.414-3.3-2.741-4.06-1.2-13.521-.248-10.317 4.814 3.358-.157 7.871-2.18 10.38.257-1.927 3.081-5.363 9.177-4.098 12.768m35.63-34.037c-6.113-3.927-12.93-8.197-22.947-7.207-2.14 1.86-2.956 6.002-.877 8.737 1.082-1.861.402-5.284 3.419-5.799 5.684-.972 12.299 3.477 16.387 5.032 2.535 4.275-.219 5.847-2.503 8.597-4.675 5.636-10.947 12.622-10.72 21.06 1.89 1.37 2.053-2.092 2.325-2.722 2.44-5.714 8.585-13.021 13.07-17.912 1.1-1.205 2.914-2.36 3.115-3.157.582-2.315-1.513-5.09-1.27-6.63m-80.591 4.135c-1.916 1.094-2.372 5.91-4.622 6.048-3.215.195-2.629-6.25-2.616-10.018-2.213 2.009-2.602 8.194-.976 11.37-1.853.91-2.68-1.003-3.708-1.677 1.32 9.595 14.036 4.45 11.922-5.723m84.482-8.13c-2.846-5.417-6.871-11.382-15.222-11.555-.17 1.75-.3 4.411.009 5.464 6.384.614 10.325 3.863 15.212 6.091m-40-3.512c5.326-2.8 15.114-3.102 22.353-2.89.388-1.586.379-3.545.394-5.48-9.305-.463-20.307 1.84-22.747 8.37m-1.013-5.222c3.683-9.247 16.341-8.182 27.016-7.927-.47-1.2-1.489-2.62-2.755-3.132-3.42-1.392-12.855-2.448-17.604.074-3.011 1.601-4.946 5.219-6.596 7.34-.797 1.024-4.765 3.64-.06 3.645'/><path fill='#81b0c4' d='M117.82 3.516c-4.322-7.402-8.457-15.005-13.585-21.534 2.15 6.32 3.07 16.9 3.394 24.965 4.498 2.105 8.349-.474 10.191-3.43'/><path fill='#231f20' d='M141.07-23.089c-4.839-.969-8.239-5.671-12.959-5.37 2.594 3.658 7.14 5.2 12.959 5.37m2.14-7.572c-3.944-.417-8.576-1.055-12.577-.726 1.894 2.892 9.19 1.894 12.577.726m1.37-6.529c-4.433-.096-9.942-.008-14.155.346 2.492 2.677 11.28.993 14.155-.346'/><path fill='#dcd9d8' d='M109.48-55.057c.636-5.567 2.843-11.207 2.566-17.304-2.45-.827-3.858-1.55-7.142-1.545-.232 5.181-.925 13.102-.718 18.041 1.615-.107 3.997 1.154 5.294.808'/><path fill='#f0d6b7' d='M102.33 26.985c-2.226-1.453-4.121-3.267-6.259-4.818-4.74-.235-7.327.328-10.81 3.05.057.219.407.121.42.39 5.075-2.262 11.524.92 16.648 1.378'/><path fill='#81b0c4' d='M75.694-7.603c1.394 6.04 6.857 9.17 11.817 12.497 5.12-6.498 8.234-14.855 11.663-22.92-8.102 2.443-16.38 6.406-23.481 10.423'/><path fill='#231f20' d='M104.18-55.865c-.207-4.94.486-12.86.718-18.041 3.283-.004 4.691.718 7.142 1.545.276 6.096-1.93 11.737-2.566 17.304-1.298.346-3.679-.914-5.294-.808m-51.13 28.09c2.165-19.906 5.301-36.639 11.054-54.266 12.766-3.876 28.157-4.214 39.441-.716-2.072 9.948-1.167 22.06-2.378 32.677-.912 7.98-.447 16.009-1.698 24.15-13.673 2.844-33 .665-46.418-1.845zm49.651 1.72c-.115-8.549.383-16.982 1.036-25.542 3.282.493 5.51.822 8.56 1.49-.99 8.241-.869 17.514-2.886 24.804-2.332-.023-4.385.027-6.71-.752m16.653 1.378c-1.558.357-3.372.014-4.86-.015.7-6.969 2.397-14.659 2.995-21.974 2.342-.073 3.593 1.032 5.52 1.403.102 6.421-.562 15.268-3.655 20.586m25.215-23.038c4.882 1.186 7.952 7.165 6.586 13.305-.916 4.127-2.548 11.898-4.295 14.538-1.29 1.953-4.79 4.51-7.584 2.72-4.545-2.91-12.552-3.755-15.867-7.278 1.662-5.534 2.178-13.135 2.864-20.146 5.678-.354 12.665 1.562 17.387-.471-3.297-1.068-7.575-1.077-10.423-2.633 2.328-1.125 7.778-.897 11.332-.035M99.17-18.025c-3.43 8.063-6.543 16.42-11.663 22.918-4.96-3.327-10.423-6.456-11.817-12.497 7.1-4.017 15.379-7.98 23.481-10.422zm8.453 24.971c-.325-8.065-1.245-18.644-3.395-24.965 5.128 6.53 9.263 14.132 13.585 21.534-1.842 2.957-5.693 5.536-10.19 3.431m-9.582 3.405c-1.943.21-3.592-2.233-6.117-1.177-.58-.64-1.105-1.333-1.695-1.958 5.579-6.723 8.114-16.262 12.423-24.163 2.312 7.59 2.045 15.904 2.555 24.188-3.177-.201-4.94 2.873-7.166 3.11m-6.161 8.132c-.208-2.303.328-3.056.791-5.695 7.57-2.367 6.248 10.388-.791 5.695m-8.394 2.755c-3.261 1.782-8.161 3.723-12.374 4.527-5.222.999-4.732-7.123-4.51-11.968.173-3.836 2.168-7.893 3.035-10.441.406-1.19.498-2.453 1.515-2.69 1.798-.418 7.73 1.954 9.42 2.875 3.575 1.95 6.348 5.045 9.384 7.123l.119 3.032c-1.826.91-3.935 1.555-6.615 1.673 1.818.914 4.492.901 6.148 1.989q.025.609.047 1.21c-3.024.234-4.176 1.58-6.17 2.67zm-31.152 5.659c-2.707-2.748 7.592-6.494 10.871-6.696-.018 1.739.991 3.378.788 4.626-3.895.684-9.013.232-11.66 2.07zm33.345-1.29c-.013-.27-.363-.172-.42-.39 3.482-2.722 6.07-3.285 10.81-3.05 2.137 1.551 4.033 3.365 6.259 4.818-5.124-.458-11.574-3.64-16.648-1.379zm30.606-9.282c-.146 3.053-.948 9.332-2.835 10.431-3.961 2.312-11.002-4.668-13.984-5.732.324-.934.86-1.674.901-2.868 1.764.434 3.912.137 5.44-.615-1.767-.198-3.727-.185-4.897-1.027-.429-1.239.105-2.927-.18-4.647 4.196-1.184 8.989-1.814 14.294-1.97 1.032 1.341 1.383 3.896 1.261 6.429zM47.777 24.24c-.85.606-6.6 8.087-7.388 7.777-10.405-4.103-20.134-11.199-28.828-17.91 8.29-17.787 11.635-39.579 12.227-60.582 9.496-4.441 17.836-10.844 30.722-11.512-1.491 10.55-2.852 19.962-3.699 29.895-3.237 1.365-7.882-.062-10.913.423-.025 3.651 4.628 1.6 5.015 4.054.292 1.858-2.56 1.998-1.631 4.923 2.368-.861 3.612-2.763 6.138-3.477 2.309 5.05-.032 13.985.3 18.205.064.792.397 4.39 2.172 3.759 1.57-.559-.09-9.569.082-13.563.157-3.68-.444-7.242 1.046-9.552a356 356 0 0 0 38.576 3.16c-2.964 1.272-6.485 2.475-10.345 4.651-2.093 1.18-8.69 3.635-9.293 5.622-.964 3.167 2.528 4.855 3.125 7.57-6.285-3.428-7.511 3.286-8.998 8.042-1.347 4.308-2.114 7.526-2.445 10.01-5.414 2.581-11.203 5.195-15.863 8.505m63.009 6.872c8.67 4.204 10.232-15.711 6.834-22.127.525-1.914 2.331-2.646 3.069-4.366-4.838-8.667-10.211-16.756-15.148-25.32 3.672 2.286 8.917.409 13.238 2.12 1.58.624 2.722 4.24 3.918 7.133 3.29 7.958 6.743 17.99 8.28 25.586.346 1.73 1.292 5.5 1.08 7.04-.378 2.758-4.12 4.803-6.022 6.508-3.506 3.15-5.714 5.921-9.371 8.866-1.483-2.189-4.666-3.66-5.878-5.44M27.95 107.99c-4.13-4.545-3.266-13.062-2.766-19.121 7.467 4.697 17.377-.372 17.284-8.36 3.565.094 1.332 4.452.687 7.259-2.107 9.169 3.55 19.13.256 27.516-6.395-.485-11.649-3.097-15.46-7.294zm29.558 26.38c-9.352-2.65-21.337-9.446-25.18-17.847 2.976.432 5.041 1.933 7.977 2.119 1.11.072 2.563-.466 3.838-.148 2.54.63 4.685 6.327 6.602 8.447 1.868 2.07 4.114 2.954 5.651 4.841.988.477 2.448.444 2.504 1.927-.428.457-.879.806-1.392.66zm48.681-2.493c-9.707 5.477-26.136 9.596-36.462 4.449-8.331-4.155-19.593-11.027-23.433-19.737 3.587-8.405-1.062-16.106-1.36-24.64-.157-4.54 2.139-8.504 2.315-13.446-1.228-2.025-4.978-2.275-7.574-2.136-.873 4.372-2.403 9.287-6.906 9.78-6.371.697-11.03-4.576-11.319-10.085-.342-6.48 4.978-17.22 12.517-16.475 2.913.287 3.629 3.207 6.802 3.177 1.72-3.432-2.653-4.51-3.103-6.964-.117-.634.363-3.112.642-4.274 1.37-5.658 4.422-12.982 7.427-17.29 3.814-5.464 11.307-6.288 19.37-6.823 1.44 3.101 6.743 2.846 10.2 2.035-4.143 1.64-7.993 5.617-11.185 9.137-3.665 4.039-7.378 8.371-7.566 13.65 6.927-9.61 12.65-18.003 25.246-22.23 9.53-3.196 20.662 1.465 27.986 6.608 3.039 2.137 4.853 5.529 7.013 8.634 8.082 11.626 11.854 28.219 11.024 44.303-.342 6.633-.327 13.244-2.552 17.706-2.326 4.666-10.193 8.84-14.8 4.62-.853 4.537 3.83 7.344 9.331 5.71-3.922 5.063-8.039 11.145-13.614 14.29zm18.084-149.66c7.585 3.77 21.757 10.149 26.512-.014 1.755-3.746 3.814-10.079 4.723-13.946 1.284-5.456-1.392-16.923-7-18.754-4.953-1.617-10.733-1.518-16.7-.32-.702.585-1.484 1.603-2.03 2.665-4.261.165-8.25-.229-11.615-1.98.319-3.15-1.812-3.656-3.81-4.305-1.48-5.872 2.963-13.541 1.9-18.896-.76-3.815-5.453-4.405-8.902-5.118-.113-2.12.15-3.89.386-5.683-.789-2.907-4.327-4.561-7.679-4.967-11.029-1.326-27.775-1.922-38.384 1.893-2.96 7.261-5.292 16.093-7.758 24.384-10.346-1.105-18.715 4.464-26.603 8.113-2.731 1.266-6.51 1.964-7.53 4.138-.99 2.105-.584 6.14-.83 9.95-.625 9.733-1.16 19.12-3.73 29.086-1.154 4.472-3.165 8.418-4.568 12.727C9.358 5.184 7.092 10.12 6.5 14.1c-.877 5.903 4.681 6.232 8.235 8.79 5.494 3.954 9.806 6.142 15.756 9.711 1.762 1.057 7.077 3.733 7.681 4.966 1.202 2.443-2.062 5.888-2.935 7.803-1.38 3.03-2.1 5.602-2.298 8.59-4.992.789-8.775 3.76-11.06 7.109-3.781 5.543-6.403 15.798-3.132 23.599.257.614 1.536 1.822 1.725 2.765.372 1.858-.7 4.329-.768 6.305-.343 10.14 1.716 18.875 8.541 21.932 2.771 11.038 12.688 14.71 22.032 20.195 3.493 2.05 7.343 3.36 11.32 4.824 14.263 5.25 36.15 4.261 47.987-4.692 5.02-3.797 13.044-11.813 15.914-17.617 7.58-15.323 7.042-40.931 1.74-59.571-.712-2.503-1.746-6.181-3.19-9.187-1.006-2.1-4.134-6.3-3.754-8.153.391-1.916 7.132-7.034 8.577-8.428 2.603-2.51 7.548-5.843 7.948-9.012.43-3.372-1.485-7.984-2.456-11.238-3.245-10.858-6.412-20.895-10.091-30.576'/><path fill='#f7e4cd' d='M73.674 57.38c.411.548 2.674 1.38 5.84-.144 0 0-3.752-.626-3.44-6.881l-1.564.313s-1.615 5.672-.836 6.712'/><path fill='#1d1919' d='M101.09 3.617a1.72 1.72 0 1 0-3.44.001 1.72 1.72 0 0 0 3.44-.001m1.72-7.972a1.72 1.72 0 1 0-3.44 0 1.72 1.72 0 0 0 3.44 0'/></g></g></svg>",
+ "jenkins": "<svg viewBox='0 0 180 180'><defs><clipPath id='a'><path fill='#37474f' d='M.899 144.42h144.42V0H.899z'/></clipPath></defs><g clip-path='url(#a)' transform='matrix(1.0691 0 0 -1.0691 9.4 166.143)'><g fill-rule='evenodd'><path fill='#f0d6b7' d='m107.96 30.661-12.506-1.876-16.883-1.876-10.943-.312-10.629.312-8.13 2.502-7.19 7.815-5.628 15.945-1.25 3.44-7.504 2.5-4.377 7.191-3.126 10.317 3.44 9.067 8.128 2.814 6.565-3.127 3.127-6.878 3.752.626 1.25 1.563-1.25 7.19-.313 9.068 1.876 12.505-.074 7.143 5.701 9.114 10.005 7.19 17.508 7.504 19.383-2.814 16.883-12.193 7.817-12.505 5.002-9.067 1.25-22.51-3.752-19.384-6.877-17.195-6.566-9.066'/><path fill='#335061' d='m97.334-23.425-44.709-1.876v-7.503l3.752-26.262-1.876-2.19-31.264 10.63-2.19 3.752-3.126 35.328-7.19 21.26-1.563 5.002 25.01 17.195 7.818 3.127 6.877-8.441 5.94-5.315 6.88-2.188 3.125-.938L68.57 1.899l2.814-3.44 7.19 2.502-5.002-9.693 27.2-12.818-3.439-1.876'/><path fill='#6d6b6d' d='m23.238 85.687 8.128 2.814 6.566-3.127 3.127-6.878 3.751.626.938 3.751-1.876 7.19 1.876 17.197-1.563 9.379 5.627 6.565 12.193 9.692-3.44 4.69-17.194-8.442-7.191-5.627-4.064-8.754-6.253-8.442-1.876-10.005z'/><path fill='#dcd9d8' d='M36.055 115.07s4.69 11.567 23.448 17.195c18.759 5.628.938 4.065.938 4.065l-20.321-7.817-7.817-7.816-3.438-6.253zm-9.379-27.195s-6.566 21.886 18.446 25.012l-.938 3.752-17.195-4.065-5.003-16.257 1.251-10.63z'/></g><g fill='#f7e4cd'><path fill-rule='evenodd' d='m36.681 58.799 4.094 3.966s1.847-.214 2.16-2.402c.312-2.19 1.25-21.886 14.693-32.516 1.227-.97-10.004 1.564-10.004 1.564L37.62 45.042m56.589 19.697s.729 9.477 3.28 8.748c2.553-.729 2.553-3.28 2.553-3.28s-6.198-4.01-5.833-5.468'/><path d='M120.16 99.442s-5.153-1.088-5.628-5.628c-.474-4.54 5.628-.938 6.566-.625m-38.771 5.94s-6.879-.938-6.879-5.314c0-4.378 7.817-4.065 10.005-2.19'/><g fill-rule='evenodd'><path d='M39.807 78.808s-11.881 7.191-13.131.312c-1.25-6.877-4.065-11.88 1.876-19.07l-4.064 1.25-3.752 9.691-1.25 9.38 7.19 7.504 8.129-.626 4.69-3.751zm5.628 19.696s5.315 27.512 32.203 32.827c22.136 4.375 33.765-.938 38.142-5.94 0 0-19.696 23.447-38.455 16.257-18.759-7.191-32.514-20.322-32.202-28.762.532-14.377.313-14.382.313-14.382m72.534 23.766s-9.066.312-9.38-7.817c0 0 0-1.25.625-2.5 0 0 7.192 8.129 11.568 3.751'/><path d='M78.268 111.1s-1.56 12.477-12.199 5.223c-6.878-4.69-6.252-11.255-5.002-12.505s.91-3.77 1.862-2.04c.952 1.728.638 7.356 4.078 8.918 3.439 1.564 9.077 3.31 11.26.404'/></g></g><path fill='#49728b' fill-rule='evenodd' d='M48.874 26.597 19.486 13.466s12.193-48.46 5.94-63.467l-4.377 1.563-.313 18.446-8.128 35.015-3.44 9.692 30.639 20.633 9.067-8.753M51.896-.206l4.17-5.087v-18.76h-5.003s-.625 13.132-.625 14.696c0 1.563.624 7.19.624 7.19M52-26.866l-14.069-.625 4.065-2.813L52-31.868'/><g fill-rule='evenodd'><path fill='#335061' d='m100.15-23.739 11.567.313 2.814-28.764-11.881-1.563z'/><path fill='#335061' d='m103.27-23.739 17.508.938s7.19 18.133 7.19 19.07c0 .939 6.253 26.263 6.253 26.263l-14.069 14.694-2.813 2.501-7.504-7.503V3.148z'/><path fill='#49728b' d='m111.09-21.55-10.942-2.188 1.563-8.755c4.064-1.876 10.943 3.127 10.943 3.127M111.4 33.162l21.885-16.257.626 7.503-16.57 15.32-5.94-6.566'/><path fill='#fafafa' d='m62.85-85.332-6.473 26.266-3.22 19.38-.531 14.385 29.296 1.56 18.226.003-1.658-32.83 2.814-25.324-.312-4.69-23.76-1.876z'/><path fill='#dcd9d8' d='M96.083-23.426s-1.563-32.515 3.127-55.65c0 0-9.38-5.94-23.136-7.503l26.262.938 3.126 1.875-3.752 51.273-.938 10.944'/><path fill='#fafafa' d='m115.06-49.691 12.193 3.44 23.135 1.25 3.44 10.629-6.254 18.446-7.19.938-10.005-3.127-9.599-4.686-5.095.935-3.972-1.56'/><path fill='#dcd9d8' d='M114.84-43.435s8.128 3.751 9.38 3.438L120.78-22.8l4.065 1.563s2.814-16.257 2.814-18.133c0 0 17.507-.938 19.07-.938 0 0 3.752 7.191 2.814 14.694l3.44-10.005.312-5.628-5.002-7.503-5.627-1.25-9.38.312-3.126 4.064-10.943-1.563-3.44-1.25'/></g><path fill='#fafafa' d='M102.56-21.241 95.682-3.733l-7.19 10.317s1.562 4.377 3.75 4.377h7.192l6.878-2.501-.625-11.568-3.127-18.134'/><path fill='#dcd9d8' fill-rule='evenodd' d='M103.9-15.297S95.145 1.585 95.145 4.086c0 0 1.563 3.752 3.752 2.814s6.879-3.439 6.879-3.439v5.94l-10.63 2.19-7.19-.939 12.193-28.763 2.5-.313'/><path fill='#fafafa' d='m65.664 25.968-8.661.942-8.13 2.501v-2.814l3.972-4.38 12.506-5.627'/><path fill='#dcd9d8' fill-rule='evenodd' d='M51.689 25.031s9.693-4.065 12.819-3.127l.311-3.748-8.752 1.872-5.316 3.752z'/><path fill='#d33833' fill-rule='evenodd' d='M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43'/><path fill='none' stroke='#d33833' stroke-width='2' d='M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43z'/><path fill='#d33833' fill-rule='evenodd' d='M89.66 18.569q-.021-.603-.047-1.21c-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669'/><path fill='none' stroke='#d33833' stroke-width='2' d='M89.66 18.569q-.021-.603-.047-1.21c-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669z'/><path fill='#d33833' fill-rule='evenodd' d='M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695'/><path fill='none' stroke='#d33833' stroke-width='2' d='M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695z'/><g fill-rule='evenodd'><path fill='#ef3d3a' d='M102.87 10.649s-2.19 3.127-.626 4.065 3.127 0 4.065 1.563 0 2.501.313 4.377 1.877 2.189 3.44 2.501c1.562.313 5.94.938 6.565-.625l-1.876 5.627-3.752 1.25-11.88-6.877-.626-3.44v-6.877M70.041.331c-.376 4.88-.773 9.752-1.215 14.626-.662 7.279 1.748 6.009 8.057 6.009.964 0 5.933-1.15 6.289-1.876 1.705-3.483-2.851-2.709 1.964-5.335 4.065-2.216 11.246 1.346 9.603 6.273-.919 1.095-4.789.341-6.176 1.06l-7.327 3.8c-3.108 1.612-10.29 3.962-13.603 1.709-8.395-5.71.53-19.974 3.524-25.93'/><path fill='#231f20' d='M78.268 111.1c-8.521 1.985-12.755-3.566-15.338-9.323-2.306.559-1.389 3.695-.806 5.294 1.525 4.194 7.672 9.778 12.694 9.02 2.161-.325 5.086-2.301 3.45-4.99m41.522-9.701.404-.016c1.926-4 3.593-8.238 6.022-11.769-1.628-3.79-12.322-7.144-12.157-.338 2.313 1.01 6.305.206 8.356 1.497-1.186 3.254-2.897 6.024-2.625 10.626m-37.16-.11c1.827-3.35 2.422-6.868 5.019-9.4 1.17-1.14 3.444-2.529 2.316-5.698-.263-.747-2.189-2.414-3.3-2.741-4.06-1.2-13.521-.248-10.317 4.814 3.358-.157 7.871-2.18 10.38.257-1.927 3.081-5.363 9.177-4.098 12.768m35.63-34.037c-6.113-3.927-12.93-8.197-22.947-7.207-2.14 1.86-2.956 6.002-.877 8.737 1.082-1.861.402-5.284 3.419-5.799 5.684-.972 12.299 3.477 16.387 5.032 2.535 4.275-.219 5.847-2.503 8.597-4.675 5.636-10.947 12.622-10.72 21.06 1.89 1.37 2.053-2.092 2.325-2.722 2.44-5.714 8.585-13.021 13.07-17.912 1.1-1.205 2.914-2.36 3.115-3.157.582-2.315-1.513-5.09-1.27-6.63m-80.591 4.135c-1.916 1.094-2.372 5.91-4.622 6.048-3.215.195-2.629-6.25-2.616-10.018-2.213 2.009-2.602 8.194-.976 11.37-1.853.91-2.68-1.003-3.708-1.677 1.32 9.595 14.036 4.45 11.922-5.723m84.482-8.13c-2.846-5.417-6.871-11.382-15.222-11.555-.17 1.75-.3 4.411.009 5.464 6.384.614 10.325 3.863 15.212 6.091m-40-3.512c5.326-2.8 15.114-3.102 22.353-2.89.388-1.586.379-3.545.394-5.48-9.305-.463-20.307 1.84-22.747 8.37m-1.013-5.222c3.683-9.247 16.341-8.182 27.016-7.927-.47-1.2-1.489-2.62-2.755-3.132-3.42-1.392-12.855-2.448-17.604.074-3.011 1.601-4.946 5.219-6.596 7.34-.797 1.024-4.765 3.64-.06 3.645'/><path fill='#81b0c4' d='M117.82 3.516c-4.322-7.402-8.457-15.005-13.585-21.534 2.15 6.32 3.07 16.9 3.394 24.965 4.498 2.105 8.349-.474 10.191-3.43'/><path fill='#231f20' d='M141.07-23.089c-4.839-.969-8.239-5.671-12.959-5.37 2.594 3.658 7.14 5.2 12.959 5.37m2.14-7.572c-3.944-.417-8.576-1.055-12.577-.726 1.894 2.892 9.19 1.894 12.577.726m1.37-6.529c-4.433-.096-9.942-.008-14.155.346 2.492 2.677 11.28.993 14.155-.346'/><path fill='#dcd9d8' d='M109.48-55.057c.636-5.567 2.843-11.207 2.566-17.304-2.45-.827-3.858-1.55-7.142-1.545-.232 5.181-.925 13.102-.718 18.041 1.615-.107 3.997 1.154 5.294.808'/><path fill='#f0d6b7' d='M102.33 26.985c-2.226-1.453-4.121-3.267-6.259-4.818-4.74-.235-7.327.328-10.81 3.05.057.219.407.121.42.39 5.075-2.262 11.524.92 16.648 1.378'/><path fill='#81b0c4' d='M75.694-7.603c1.394 6.04 6.857 9.17 11.817 12.497 5.12-6.498 8.234-14.855 11.663-22.92-8.102 2.443-16.38 6.406-23.481 10.423'/><path fill='#231f20' d='M104.18-55.865c-.207-4.94.486-12.86.718-18.041 3.283-.004 4.691.718 7.142 1.545.276 6.096-1.93 11.737-2.566 17.304-1.298.346-3.679-.914-5.294-.808m-51.13 28.09c2.165-19.906 5.301-36.639 11.054-54.266 12.766-3.876 28.157-4.214 39.441-.716-2.072 9.948-1.167 22.06-2.378 32.677-.912 7.98-.447 16.009-1.698 24.15-13.673 2.844-33 .665-46.418-1.845zm49.651 1.72c-.115-8.549.383-16.982 1.036-25.542 3.282.493 5.51.822 8.56 1.49-.99 8.241-.869 17.514-2.886 24.804-2.332-.023-4.385.027-6.71-.752m16.653 1.378c-1.558.357-3.372.014-4.86-.015.7-6.969 2.397-14.659 2.995-21.974 2.342-.073 3.593 1.032 5.52 1.403.102 6.421-.562 15.268-3.655 20.586m25.215-23.038c4.882 1.186 7.952 7.165 6.586 13.305-.916 4.127-2.548 11.898-4.295 14.538-1.29 1.953-4.79 4.51-7.584 2.72-4.545-2.91-12.552-3.755-15.867-7.278 1.662-5.534 2.178-13.135 2.864-20.146 5.678-.354 12.665 1.562 17.387-.471-3.297-1.068-7.575-1.077-10.423-2.633 2.328-1.125 7.778-.897 11.332-.035M99.17-18.025c-3.43 8.063-6.543 16.42-11.663 22.918-4.96-3.327-10.423-6.456-11.817-12.497 7.1-4.017 15.379-7.98 23.481-10.422zm8.453 24.971c-.325-8.065-1.245-18.644-3.395-24.965 5.128 6.53 9.263 14.132 13.585 21.534-1.842 2.957-5.693 5.536-10.19 3.431m-9.582 3.405c-1.943.21-3.592-2.233-6.117-1.177-.58-.64-1.105-1.333-1.695-1.958 5.579-6.723 8.114-16.262 12.423-24.163 2.312 7.59 2.045 15.904 2.555 24.188-3.177-.201-4.94 2.873-7.166 3.11m-6.161 8.132c-.208-2.303.328-3.056.791-5.695 7.57-2.367 6.248 10.388-.791 5.695m-8.394 2.755c-3.261 1.782-8.161 3.723-12.374 4.527-5.222.999-4.732-7.123-4.51-11.968.173-3.836 2.168-7.893 3.035-10.441.406-1.19.498-2.453 1.515-2.69 1.798-.418 7.73 1.954 9.42 2.875 3.575 1.95 6.348 5.045 9.384 7.123l.119 3.032c-1.826.91-3.935 1.555-6.615 1.673 1.818.914 4.492.901 6.148 1.989q.025.609.047 1.21c-3.024.234-4.176 1.58-6.17 2.67zm-31.152 5.659c-2.707-2.748 7.592-6.494 10.871-6.696-.018 1.739.991 3.378.788 4.626-3.895.684-9.013.232-11.66 2.07zm33.345-1.29c-.013-.27-.363-.172-.42-.39 3.482-2.722 6.07-3.285 10.81-3.05 2.137 1.551 4.033 3.365 6.259 4.818-5.124-.458-11.574-3.64-16.648-1.379zm30.606-9.282c-.146 3.053-.948 9.332-2.835 10.431-3.961 2.312-11.002-4.668-13.984-5.732.324-.934.86-1.674.901-2.868 1.764.434 3.912.137 5.44-.615-1.767-.198-3.727-.185-4.897-1.027-.429-1.239.105-2.927-.18-4.647 4.196-1.184 8.989-1.814 14.294-1.97 1.032 1.341 1.383 3.896 1.261 6.429zM47.777 24.24c-.85.606-6.6 8.087-7.388 7.777-10.405-4.103-20.134-11.199-28.828-17.91 8.29-17.787 11.635-39.579 12.227-60.582 9.496-4.441 17.836-10.844 30.722-11.512-1.491 10.55-2.852 19.962-3.699 29.895-3.237 1.365-7.882-.062-10.913.423-.025 3.651 4.628 1.6 5.015 4.054.292 1.858-2.56 1.998-1.631 4.923 2.368-.861 3.612-2.763 6.138-3.477 2.309 5.05-.032 13.985.3 18.205.064.792.397 4.39 2.172 3.759 1.57-.559-.09-9.569.082-13.563.157-3.68-.444-7.242 1.046-9.552a356 356 0 0 0 38.576 3.16c-2.964 1.272-6.485 2.475-10.345 4.651-2.093 1.18-8.69 3.635-9.293 5.622-.964 3.167 2.528 4.855 3.125 7.57-6.285-3.428-7.511 3.286-8.998 8.042-1.347 4.308-2.114 7.526-2.445 10.01-5.414 2.581-11.203 5.195-15.863 8.505m63.009 6.872c8.67 4.204 10.232-15.711 6.834-22.127.525-1.914 2.331-2.646 3.069-4.366-4.838-8.667-10.211-16.756-15.148-25.32 3.672 2.286 8.917.409 13.238 2.12 1.58.624 2.722 4.24 3.918 7.133 3.29 7.958 6.743 17.99 8.28 25.586.346 1.73 1.292 5.5 1.08 7.04-.378 2.758-4.12 4.803-6.022 6.508-3.506 3.15-5.714 5.921-9.371 8.866-1.483-2.189-4.666-3.66-5.878-5.44M27.95 107.99c-4.13-4.545-3.266-13.062-2.766-19.121 7.467 4.697 17.377-.372 17.284-8.36 3.565.094 1.332 4.452.687 7.259-2.107 9.169 3.55 19.13.256 27.516-6.395-.485-11.649-3.097-15.46-7.294zm29.558 26.38c-9.352-2.65-21.337-9.446-25.18-17.847 2.976.432 5.041 1.933 7.977 2.119 1.11.072 2.563-.466 3.838-.148 2.54.63 4.685 6.327 6.602 8.447 1.868 2.07 4.114 2.954 5.651 4.841.988.477 2.448.444 2.504 1.927-.428.457-.879.806-1.392.66zm48.681-2.493c-9.707 5.477-26.136 9.596-36.462 4.449-8.331-4.155-19.593-11.027-23.433-19.737 3.587-8.405-1.062-16.106-1.36-24.64-.157-4.54 2.139-8.504 2.315-13.446-1.228-2.025-4.978-2.275-7.574-2.136-.873 4.372-2.403 9.287-6.906 9.78-6.371.697-11.03-4.576-11.319-10.085-.342-6.48 4.978-17.22 12.517-16.475 2.913.287 3.629 3.207 6.802 3.177 1.72-3.432-2.653-4.51-3.103-6.964-.117-.634.363-3.112.642-4.274 1.37-5.658 4.422-12.982 7.427-17.29 3.814-5.464 11.307-6.288 19.37-6.823 1.44 3.101 6.743 2.846 10.2 2.035-4.143 1.64-7.993 5.617-11.185 9.137-3.665 4.039-7.378 8.371-7.566 13.65 6.927-9.61 12.65-18.003 25.246-22.23 9.53-3.196 20.662 1.465 27.986 6.608 3.039 2.137 4.853 5.529 7.013 8.634 8.082 11.626 11.854 28.219 11.024 44.303-.342 6.633-.327 13.244-2.552 17.706-2.326 4.666-10.193 8.84-14.8 4.62-.853 4.537 3.83 7.344 9.331 5.71-3.922 5.063-8.039 11.145-13.614 14.29zm18.084-149.66c7.585 3.77 21.757 10.149 26.512-.014 1.755-3.746 3.814-10.079 4.723-13.946 1.284-5.456-1.392-16.923-7-18.754-4.953-1.617-10.733-1.518-16.7-.32-.702.585-1.484 1.603-2.03 2.665-4.261.165-8.25-.229-11.615-1.98.319-3.15-1.812-3.656-3.81-4.305-1.48-5.872 2.963-13.541 1.9-18.896-.76-3.815-5.453-4.405-8.902-5.118-.113-2.12.15-3.89.386-5.683-.789-2.907-4.327-4.561-7.679-4.967-11.029-1.326-27.775-1.922-38.384 1.893-2.96 7.261-5.292 16.093-7.758 24.384-10.346-1.105-18.715 4.464-26.603 8.113-2.731 1.266-6.51 1.964-7.53 4.138-.99 2.105-.584 6.14-.83 9.95-.625 9.733-1.16 19.12-3.73 29.086-1.154 4.472-3.165 8.418-4.568 12.727C9.358 5.184 7.092 10.12 6.5 14.1c-.877 5.903 4.681 6.232 8.235 8.79 5.494 3.954 9.806 6.142 15.756 9.711 1.762 1.057 7.077 3.733 7.681 4.966 1.202 2.443-2.062 5.888-2.935 7.803-1.38 3.03-2.1 5.602-2.298 8.59-4.992.789-8.775 3.76-11.06 7.109-3.781 5.543-6.403 15.798-3.132 23.599.257.614 1.536 1.822 1.725 2.765.372 1.858-.7 4.329-.768 6.305-.343 10.14 1.716 18.875 8.541 21.932 2.771 11.038 12.688 14.71 22.032 20.195 3.493 2.05 7.343 3.36 11.32 4.824 14.263 5.25 36.15 4.261 47.987-4.692 5.02-3.797 13.044-11.813 15.914-17.617 7.58-15.323 7.042-40.931 1.74-59.571-.712-2.503-1.746-6.181-3.19-9.187-1.006-2.1-4.134-6.3-3.754-8.153.391-1.916 7.132-7.034 8.577-8.428 2.603-2.51 7.548-5.843 7.948-9.012.43-3.372-1.485-7.984-2.456-11.238-3.245-10.858-6.412-20.895-10.091-30.576'/><path fill='#f7e4cd' d='M73.674 57.38c.411.548 2.674 1.38 5.84-.144 0 0-3.752-.626-3.44-6.881l-1.564.313s-1.615 5.672-.836 6.712'/><path fill='#1d1919' d='M101.09 3.617a1.72 1.72 0 1 0-3.44.001 1.72 1.72 0 0 0 3.44-.001m1.72-7.972a1.72 1.72 0 1 0-3.44 0 1.72 1.72 0 0 0 3.44 0'/></g></g></svg>",
"jest": "<svg viewBox='0 0 32 32'><path fill='#f4511e' d='m21.032 8-1.878 4L20 13.998h2L22.928 12z'/><path fill='#f4511e' d='m14 2 2 8h2l3.032-6L24 10h2l2-8zm14 18h-2a4.34 4.34 0 0 1-4-4h-2a4.17 4.17 0 0 1-4.23 3.87c-1.522 2.38-5.155 4.283-7.77 5.148A4.724 4.724 0 0 1 5 20H4c-4.718 7.978 3.064 13.219 10.955 7.895C18.85 24.497 29.658 27.487 28 20'/><circle cx='7' cy='15' r='3' fill='#f4511e'/><circle cx='27' cy='15' r='3' fill='#f4511e'/><circle cx='16' cy='16' r='2' fill='#f4511e'/></svg>",
"jinja": "<svg viewBox='0 0 32 32'><path fill='#bdbdbd' d='M30 8V4a94.4 94.4 0 0 1-14 1A94.4 94.4 0 0 1 2 4v4s1.482.247 3.95.495L5.4 14H2v2h3.2L4 28h6V16h12v12h6l-1.2-12H30v-2h-3.4l-.55-5.505C28.517 8.247 30 8 30 8m-20 6V8.817c1.235.074 2.576.13 4 .16V14Zm12 0h-4V8.977a104 104 0 0 0 4-.16Z'/></svg>",
"jinja_light": "<svg viewBox='0 0 32 32'><path fill='#616161' d='M30 8V4a94.4 94.4 0 0 1-14 1A94.4 94.4 0 0 1 2 4v4s1.482.247 3.95.495L5.4 14H2v2h3.2L4 28h6V16h12v12h6l-1.2-12H30v-2h-3.4l-.55-5.505C28.517 8.247 30 8 30 8m-20 6V8.817c1.235.074 2.576.13 4 .16V14Zm12 0h-4V8.977a104 104 0 0 0 4-.16Z'/></svg>",
@@ -757,41 +764,43 @@
"json": "<svg viewBox='0 -960 960 960'><path fill='#f9a825' d='M560-160v-80h120q17 0 28.5-11.5T720-280v-80q0-38 22-69t58-44v-14q-36-13-58-44t-22-69v-80q0-17-11.5-28.5T680-720H560v-80h120q50 0 85 35t35 85v80q0 17 11.5 28.5T840-560h40v160h-40q-17 0-28.5 11.5T800-360v80q0 50-35 85t-85 35zm-280 0q-50 0-85-35t-35-85v-80q0-17-11.5-28.5T120-400H80v-160h40q17 0 28.5-11.5T160-600v-80q0-50 35-85t85-35h120v80H280q-17 0-28.5 11.5T240-680v80q0 38-22 69t-58 44v14q36 13 58 44t22 69v80q0 17 11.5 28.5T280-240h120v80z'/></svg>",
"jsr": "<svg viewBox='0 0 16 16'><path fill='#fdd835' d='M2 7h1v2h1V5h1v5H2m4-5h4v1H7v1.5h3V11H6v-1h3V8.5H6M11 6h3v2.5h-1V7h-1v4h-1'/></svg>",
"jsr_light": "<svg viewBox='0 0 16 16'><path fill='#37474f' d='M1 6h2V4h8v1h4v5h-2v2H5v-1H1'/><path fill='#fdd835' d='M2 7h1v2h1V5h1v5H2m4-5h4v1H7v1.5h3V11H6v-1h3V8.5H6M11 6h3v2.5h-1V7h-1v4h-1'/></svg>",
- "julia": "<svg viewBox='0 0 50 50'><g transform='translate(.21 -247.01)'><circle cx='13.497' cy='281.63' r='9.555' fill='#C62828'/><circle cx='36.081' cy='281.63' r='9.555' fill='#7E57C2'/><circle cx='24.722' cy='262.39' r='9.555' fill='#388E3C'/></g></svg>",
+ "julia": "<svg viewBox='0 0 50 50'><g transform='translate(.21 -247.01)'><circle cx='13.497' cy='281.63' r='9.555' fill='#c62828'/><circle cx='36.081' cy='281.63' r='9.555' fill='#7e57c2'/><circle cx='24.722' cy='262.39' r='9.555' fill='#388e3c'/></g></svg>",
"jupyter": "<svg viewBox='0 0 32 32'><path fill='#f57c00' d='M6.2 18a22.7 22.7 0 0 0 9.8 2 22.7 22.7 0 0 0 9.8-2 10.002 10.002 0 0 1-19.6 0m19.6-4a22.7 22.7 0 0 0-9.8-2 22.7 22.7 0 0 0-9.8 2 10.002 10.002 0 0 1 19.6 0'/><circle cx='27' cy='5' r='3' fill='#757575'/><circle cx='5' cy='27' r='3' fill='#9e9e9e'/><circle cx='5' cy='5' r='3' fill='#616161'/></svg>",
"just": "<svg fill='none' viewBox='0 0 16 16'><path fill='#ef5350' d='M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7m0 1a2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2 2 2 0 0 1 2-2m0 8a2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2 2 2 0 0 1 2-2'/></svg>",
- "karma": "<svg viewBox='0 0 64 64'><path fill='#009688' d='m37.275 55.618-20.29-26.686 9.53-7.247 20.29 26.686h.003l5.527 7.247z'/><path fill='#4DB6AC' d='M34.4 8.378 23.638 22.533V8.403H11.665V22.22l7.84 33.234h4.132V42.308l.003.003 20.29-26.686-.008-.006 5.504-7.24H34.558v.12z'/></svg>",
+ "karma": "<svg viewBox='0 0 64 64'><path fill='#009688' d='m37.275 55.618-20.29-26.686 9.53-7.247 20.29 26.686h.003l5.527 7.247z'/><path fill='#4db6ac' d='M34.4 8.378 23.638 22.533V8.403H11.665V22.22l7.84 33.234h4.132V42.308l.003.003 20.29-26.686-.008-.006 5.504-7.24H34.558v.12z'/></svg>",
"kcl": "<svg viewBox='0 0 24 24'><g stroke-width='.948'><path fill='#9ccc65' d='m2 3 5.087 1.007L9 12l-5-1Z'/><path fill='#e91e63' d='m3.957 11.996 5.087 1.007 1.912 7.993-5-1z'/><path fill='#26c6da' d='m10 13 5-2 7 5-5 2z'/><path fill='#ffcc80' d='m10 12 7-3 1-5-7 3z'/></g></svg>",
"key": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='M30 14H17.738a8 8 0 1 0 0 4H24v4h4v-4h2Zm-20 5a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3'/></svg>",
"keystatic": "<svg viewBox='0 0 32 32'><defs><linearGradient id='a' x1='385.222' x2='405.918' y1='482.514' y2='477.914' gradientTransform='matrix(.75 0 0 .75 -284.775 -343.25)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#78909c' stop-opacity='.2'/><stop offset='1' stop-color='#78909c'/></linearGradient></defs><path fill='#78909c' d='m17.714 9-3.428 14-1.715 7L28 14ZM4 18 19.429 2l-1.715 7Z'/><path fill='url(#a)' d='M17.714 9 4 18l10.286 5Z'/></svg>",
"kivy": "<svg viewBox='0 0 24 24'><path fill='#90a4ae' d='M1.123 4.881v8.456l3.643-3.643a.825.825 0 0 0 0-1.17zm4.45 14.066v-8.456L1.93 14.134a.825.825 0 0 0 0 1.17zM22.952 8.25 12.848 9.316l-.033.034 4.518 4.518zM6.595 4.64s.044 6.245-.006 8.414c-.037 1.619.703 2.106 1.41 2.848 1.018 1.067 3.022 2.968 3.022 2.968.396.39 1.025.393 1.418 0l3.485-3.485a1 1 0 0 0 0-1.417z'/></svg>",
"kl": "<svg viewBox='0 0 24 24'><path fill='#29b6f6' d='M12 1.703c-.25-.002-.5.11-.729.338a5356 5356 0 0 0-9.226 9.235c-.144.144-.229.347-.341.522v.41c.16.223.294.474.485.666a3260 3260 0 0 0 8.936 8.937c.193.192.443.325.666.486h.41c.205-.142.436-.256.609-.429q4.569-4.56 9.133-9.126c.47-.47.472-1.005.006-1.472L12.73 2.052c-.23-.23-.48-.346-.731-.349zM10.938 6.25l1.386.832q1.052.635 2.109 1.262l.04.026.016.013.017.013c.061.056.089.122.088.224a510 510 0 0 0 0 3.793.5.5 0 0 1-.007.094c-.015.07-.054.104-.142.11l-.044.001-.136-.003c-.323-.005-.648 0-.998 0v-2.543l.004-.668c0-.146-.039-.23-.17-.307-.893-.528-1.78-1.067-2.67-1.6-.051-.03-.101-.065-.173-.112l.001-.002-.001-.001zm.362 3.39c.068-.003.119.042.173.138q.128.22.264.439l.015.025q.145.231.292.47l-1.915 1.176c-.337.208-.673.418-1.014.618-.113.066-.154.143-.154.277.01.977.01 1.954.014 2.932v.253H7.664c-.004-.054-.014-.112-.014-.17-.005-1.25-.006-2.502-.015-3.752 0-.14.045-.22.164-.293a467 467 0 0 0 3.353-2.055l.016-.009.032-.018.016-.007.033-.013.012-.004.028-.005.01-.002zm5.677 3.125.314.54.346.595v.001c-.158.094-.298.177-.438.258l-3.097 1.798c-.106.062-.189.072-.3.01l-.893-.495-1.524-.843-.895-.493c-.035-.02-.068-.044-.129-.085h.001v-.001l.137-.25.495-.902 1.446.795c.442.244.886.484 1.323.735.121.069.212.071.334 0 .894-.526 1.792-1.044 2.689-1.563.057-.034.118-.062.191-.1'/></svg>",
- "knip": "<svg viewBox='0 0 24 24'><path fill='#EF6C00' d='m18.957 2.998-5.985 5.984 1.995 1.995 6.983-6.982v-.998m-9.975 9.477a.5.5 0 1 1 0-.998.5.5 0 0 1 0 .998M5.99 19.955a1.995 1.995 0 1 1 0-3.99 1.995 1.995 0 0 1 0 3.99m0-11.97a1.995 1.995 0 1 1 0-3.99 1.995 1.995 0 0 1 0 3.99m3.63-.36a3.9 3.9 0 0 0 .36-1.635 3.99 3.99 0 1 0-3.99 3.99c.589 0 1.137-.13 1.636-.36l2.354 2.355-2.354 2.354a3.9 3.9 0 0 0-1.636-.359 3.99 3.99 0 1 0 3.99 3.99c0-.588-.13-1.137-.36-1.636l2.355-2.354 6.982 6.982h2.993v-.997z'/></svg>",
- "kotlin": "<svg viewBox='0 0 24 24'><defs><linearGradient id='a' x1='1.725' x2='22.185' y1='22.67' y2='1.982' gradientTransform='translate(1.306 1.129)scale(.89324)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7C4DFF'/><stop offset='.5' stop-color='#D500F9'/><stop offset='1' stop-color='#EF5350'/></linearGradient></defs><path fill='url(#a)' d='M2.975 2.976v18.048h18.05v-.03l-4.478-4.511-4.48-4.515 4.48-4.515 4.443-4.477z'/></svg>",
+ "knip": "<svg viewBox='0 0 24 24'><path fill='#ef6c00' d='m18.957 2.998-5.985 5.984 1.995 1.995 6.983-6.982v-.998m-9.975 9.477a.5.5 0 1 1 0-.998.5.5 0 0 1 0 .998M5.99 19.955a1.995 1.995 0 1 1 0-3.99 1.995 1.995 0 0 1 0 3.99m0-11.97a1.995 1.995 0 1 1 0-3.99 1.995 1.995 0 0 1 0 3.99m3.63-.36a3.9 3.9 0 0 0 .36-1.635 3.99 3.99 0 1 0-3.99 3.99c.589 0 1.137-.13 1.636-.36l2.354 2.355-2.354 2.354a3.9 3.9 0 0 0-1.636-.359 3.99 3.99 0 1 0 3.99 3.99c0-.588-.13-1.137-.36-1.636l2.355-2.354 6.982 6.982h2.993v-.997z'/></svg>",
+ "kotlin": "<svg viewBox='0 0 24 24'><defs><linearGradient id='a' x1='1.725' x2='22.185' y1='22.67' y2='1.982' gradientTransform='translate(1.306 1.129)scale(.89324)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#7c4dff'/><stop offset='.5' stop-color='#d500f9'/><stop offset='1' stop-color='#ef5350'/></linearGradient></defs><path fill='url(#a)' d='M2.975 2.976v18.048h18.05v-.03l-4.478-4.511-4.48-4.515 4.48-4.515 4.443-4.477z'/></svg>",
"kubernetes": "<svg viewBox='0 0 24 24'><path fill='#448aff' d='M12.074 1.424a.638.638 0 0 0-.686.691v.173c.015.2.044.402.087.588.058.358.085.73.07 1.102a.65.65 0 0 1-.201.33l-.014.258a7 7 0 0 0-1.117.17 7.9 7.9 0 0 0-4.012 2.292l-.213-.157a.56.56 0 0 1-.374-.042 6 6 0 0 1-.829-.747c-.129-.143-.26-.299-.403-.428l-.128-.1a.8.8 0 0 0-.431-.171c-.2 0-.372.07-.501.212-.2.301-.127.675.16.904l.013.014c.03.029.087.072.115.1q.258.172.515.3c.33.186.631.403.918.647a.63.63 0 0 1 .114.358l.2.17a7.82 7.82 0 0 0-1.232 5.546l-.271.07a.84.84 0 0 1-.275.274c-.358.086-.73.17-1.102.17-.186 0-.387 0-.588.057l-.156.03h-.014v.015c-.043 0-.086.014-.115.014a.62.62 0 0 0-.4.789.625.625 0 0 0 .772.386 1 1 0 0 0 .188-.045c.186-.057.37-.127.528-.213a7.4 7.4 0 0 1 1.103-.316c.114 0 .244.057.33.129l.285-.042a8.04 8.04 0 0 0 3.54 4.426l-.1.258a.8.8 0 0 1 .044.358c-.143.344-.33.687-.56.987-.114.172-.215.33-.344.501 0 .043.001.117-.056.16-.014.043-.044.072-.059.114a.615.615 0 0 0 .372.787.62.62 0 0 0 .79-.372c.028-.043.055-.143.083-.143.072-.2.131-.387.174-.574a5.4 5.4 0 0 1 .473-1.102.5.5 0 0 1 .271-.129l.143-.257c1.82.702 3.84.701 5.688.014l.115.23a.53.53 0 0 1 .3.198c.186.33.301.674.43 1.032.043.187.102.373.174.588.028 0 .055.086.084.129.014.043.03.071.044.114a.61.61 0 0 0 .845.216.614.614 0 0 0 .213-.845c-.014-.057-.056-.13-.056-.174-.115-.157-.23-.329-.344-.486-.215-.316-.371-.63-.543-.974a.48.48 0 0 1 .042-.372 1.2 1.2 0 0 1-.1-.244c1.661-1.002 2.951-2.577 3.539-4.454.086.014.17.028.271.042.086-.115.201-.115.33-.115.387.057.73.16 1.103.302q.235.128.514.213c.058.014.116.03.202.03v.015c0 .014.058.013.1.028.344.043.617-.202.689-.532a.617.617 0 0 0-.532-.7c-.057-.014-.127-.03-.17-.072h-.588a7 7 0 0 1-1.102-.202.6.6 0 0 1-.274-.257l-.272-.07a7.8 7.8 0 0 0-1.262-5.531l.23-.2a.44.44 0 0 1 .114-.343c.273-.244.589-.46.918-.647a3.6 3.6 0 0 0 .5-.3 1 1 0 0 0 .13-.1c.043-.028.086-.058.086-.087.258-.243.273-.63 0-.859-.214-.257-.601-.257-.845 0-.043 0-.1.059-.142.087a11 11 0 0 0-.403.428c-.244.272-.53.532-.831.747a.55.55 0 0 1-.372.042l-.23.171a7.98 7.98 0 0 0-5.098-2.462l-.014-.274a.5.5 0 0 1-.201-.314 5.6 5.6 0 0 1 .07-1.102 4 4 0 0 0 .087-.588v-.316a.62.62 0 0 0-.546-.548m-.842 4.773-.174 3.223h-.014a.56.56 0 0 1-.114.302.543.543 0 0 1-.745.13h-.014L7.536 7.973a6.23 6.23 0 0 1 3.035-1.662c.23-.043.446-.086.66-.115zm1.544 0a6.38 6.38 0 0 1 3.682 1.777L13.837 9.85h-.014a.66.66 0 0 1-.3.073.535.535 0 0 1-.56-.518zm-6.2 2.938 2.406 2.19v.015c.086.071.157.16.157.274a.523.523 0 0 1-.372.657v.014l-3.095.89a6.4 6.4 0 0 1 .904-4.04m10.842.042c.73 1.189 1.032 2.595.932 3.984l-3.109-.89-.014-.014a.5.5 0 0 1-.257-.17.53.53 0 0 1 .056-.761l-.014-.042zm-5.915 2.322h.988l.615.758-.215.96-.887.431-.887-.43-.23-.96zm-2.308 2.65h.115c.243 0 .46.17.545.414 0 .1.001.23-.056.302v.042l-1.22 2.966a6.33 6.33 0 0 1-2.563-3.195zm5.274 0h.344l3.193.515a6.34 6.34 0 0 1-2.563 3.223l-1.234-3.022c-.115-.258.002-.558.26-.716m-2.521 1.298a.55.55 0 0 1 .529.277h.014l1.561 2.823a5 5 0 0 1-.615.171 6.4 6.4 0 0 1-3.481-.17l1.561-2.824h.014c.043-.1.115-.144.216-.215a.5.5 0 0 1 .201-.062'/></svg>",
"kusto": "<svg viewBox='0 0 16 16'><path fill='#1e88e5' d='M10 1C7.23 1 5 3.23 5 6v5h5c2.77 0 5-2.23 5-5s-2.23-5-5-5m1.5 3H13v5h-1.5zm-5 1H8v4H6.5zM9 6h1.5v3H9zM1 8v3h3V8zm0 4v2.498a.5.5 0 0 0 .502.502H4v-3zm4 0v3h3v-3z'/></svg>",
"label": "<svg viewBox='0 0 16 16'><path fill='#ffb300' d='m14.709 8.558-7.27-7.247a1 1 0 0 0-.893-.297l-4 .731c-.399.074-.714.38-.8.777l-.732 4.024c-.055.328.057.662.297.892l7.247 7.27c.393.39 1.025.39 1.417 0l4.734-4.733a1.006 1.006 0 0 0 0-1.417m-8.981-2.8c-1.434 1.554-3.65-.764-2.117-2.214 1.411-1.378 3.467.704 2.15 2.178z'/></svg>",
"laravel": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='M31.963 9.12c-.008-.03-.023-.056-.034-.085a1 1 0 0 0-.07-.156 2 2 0 0 0-.162-.205 1 1 0 0 0-.088-.072 1 1 0 0 0-.083-.068l-.044-.02-.035-.024-6-3a1 1 0 0 0-.894 0l-6 3-.035.024-.044.02a1 1 0 0 0-.083.068.7.7 0 0 0-.187.191 1 1 0 0 0-.064.086 1 1 0 0 0-.069.156c-.01.029-.026.055-.034.085a1 1 0 0 0-.037.265v5.382l-4 2V5.385a1 1 0 0 0-.037-.265c-.008-.03-.023-.056-.034-.085a1 1 0 0 0-.07-.156 1 1 0 0 0-.063-.086.7.7 0 0 0-.187-.191 1 1 0 0 0-.083-.068l-.044-.02-.035-.024-6-3a1 1 0 0 0-.894 0l-6 3-.035.024-.044.02a1 1 0 0 0-.083.068 1 1 0 0 0-.088.072 1 1 0 0 0-.1.119 1 1 0 0 0-.063.086 1 1 0 0 0-.069.156c-.01.029-.026.055-.034.085A1 1 0 0 0 0 5.385v19a1 1 0 0 0 .553.894l6 3 6 3c.014.007.03.005.046.011a.9.9 0 0 0 .802 0c.015-.006.032-.004.046-.01l12-6a1 1 0 0 0 .553-.895v-5.382l5.447-2.724a1 1 0 0 0 .553-.894v-6a1 1 0 0 0-.037-.265M9.236 21.385l4.211-2.106h.001L19 16.503l3.764 1.882L13 23.267ZM24 13.003v3.764l-4-2v-3.764Zm1-5.5 3.764 1.882L25 11.267l-3.764-1.882ZM8 19.767V9.003l4-2v10.764ZM7 3.503l3.764 1.882L7 7.267 3.236 5.385Zm-5 3.5 4 2v16.764l-4-2Zm6 16 4 2v3.764l-4-2Zm16 .764-10 5v-3.764l10-5Zm6-9-4 2v-3.764l4-2Z'/></svg>",
- "latex.clone": "<svg viewBox='0 0 1024 1024'><path fill='#26A69A' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
- "latexmk": "<svg viewBox='0 0 1024 1024'><path fill='#26a69a' d='M1024 128q-384 0-576 384L256 896l64-64 64-96c64 0 192-64 224-160-64 16-96-32-96-64 224 0 272-96 352-224 37.924-60.678 80-128 160-160M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "latex-class.clone": "<svg viewBox='0 0 1024 1024'><path fill='#e040fb' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "latex-package.clone": "<svg viewBox='0 0 1024 1024'><path fill='#b388ff' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "latex.clone": "<svg viewBox='0 0 1024 1024'><path fill='#00bfa5' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "latexmk": "<svg viewBox='0 0 1024 1024'><path fill='#00bfa5' d='M1024 128q-384 0-576 384L256 896l64-64 64-96c64 0 192-64 224-160-64 16-96-32-96-64 224 0 272-96 352-224 37.924-60.678 80-128 160-160M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128z'/></svg>",
"lbx": "<svg viewBox='0 0 1024 1024'><path fill='#2e7d32' d='M128 704v128c0 70.692 57.308 128 128 128h608c17.728 0 32-14.272 32-32V704z'/><path fill='#ffe082' d='M704 704v192h128V704z'/><path fill='#fff8e1' d='M192 704v96c0 53.184 42.816 96 96 96h544a96 96 0 0 1-96-96 96 96 0 0 1 96-96z'/><path fill='#ff1744' d='M320 832h192v192l-96-96-96 96z'/><path fill='#4caf50' d='M256 64c-70.692 0-128 57.308-128 128v640c0 11.088 1.557 21.787 4.207 32.047C146.767 807.565 197.672 768.07 256 768h608c17.728 0 32-14.272 32-32V96c0-17.728-14.272-32-32-32z'/><path fill='#e8f5e9' d='M448 128v64H256v64h256c0 33.778-26.676 86.11-73.947 144.959-7.257 9.034-15.068 18.276-23.123 27.611-4.479-5.242-8.838-10.461-13.002-15.634C370.55 373.95 352 336 352 320h-64c0 48 29.45 90.049 64.072 133.064 6.302 7.83 12.933 15.627 19.68 23.39-35.13 37.553-74.249 76.79-114.379 116.919l45.254 45.254c38.924-38.924 77.278-77.307 112.568-114.883 17.182 17.87 34.328 35.033 50.178 50.883l45.254-45.254c-16.799-16.799-34.744-34.801-52.309-53.17 10.301-11.813 20.31-23.56 29.63-35.162C538.675 377.889 576 318.222 576 256h128v-64H512v-64zm192 192L512 704h64l21.334-64h149.332L768 704h64L704 320zm32 96 53.334 160H618.666z'/></svg>",
"lefthook": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M6 16v6H2zm5.106-6.537-3.317 1.775a2.22 2.22 0 0 0-.895 2.873l.333.71L14 11.571v-.193a2.006 2.006 0 0 0-2.894-1.915m18.82 7.545a2 2 0 0 0-.393-.744l-7.89-8.883a2.76 2.76 0 0 0-3.138-.384L16 8v4.559a3.97 3.97 0 0 1-1.566 3.18L16 20l8.457 2.204 4.624-2.979a2 2 0 0 0 .845-2.217'/><path fill='#b71c1c' fill-rule='evenodd' d='m2 22 4-2 4 2-4 2zm12.434-6.262a6 6 0 0 1-1.194.695l-2.544 1.136A6.55 6.55 0 0 1 8 18v.764l9.71 4.855a4.05 4.05 0 0 0 2.343.366 7.8 7.8 0 0 0 2.667-.82 24 24 0 0 0 1.737-.96zm-6.97-1.635 5.829-2.937a.5.5 0 0 1 .712.475c.007.417-.005.871-.005 1.153a2.1 2.1 0 0 1-1.367 2.03l-2.987 1.067c-1.629.581-3.103-1.324-2.182-1.788'/></svg>",
"lerna": "<svg viewBox='0 0 24 24'><path fill='#448aff' d='M7.57 8.21c-.41.08-.99.06-1.26.06-.4 0-1.27-.48-1.75-.98-.25-.26-.71-.82-1.01-1.25-.31-.43-.62-.78-.71-.78-.17 0-.28.6-.29 1.6 0 .12-.01.22-.01.31l.01.03-.14 8.47c.27.07.54.13.82.19.58.12 1.06.22 1.08.23.01.02-.19.34-.45.71-.95 1.35-1.14 2-.6 2 .17 0 .53-.07.82-.17.33-.1.93-.16 1.58-.16 1.34 0 2.68.33 4.37 1.08 1.61.71 2.14.88 4 1.25.85.18 1.59.37 1.64.41.05.05-.07.22-.28.38s-.38.33-.38.37.22.04.51-.01c.27-.04.89-.14 1.36-.19 1.43-.18 2.45-.82 2.47-1.56.04-.94-.25-3.03-.43-3.14-.02-.01-.38.11-.46.24-.07.13-.16.33-.25.56-.18.46-.15.56-1.57.66-1.03.05-2.02-.19-2.82-.65-.67-.39-1.66-.74-2.18-1.54-.42-.65-.86-1.72-.78-1.87.03-.04.17-.03.32.02.39.15.96.38 2.73.15 1.54-.2 3-.04 3.39.16s.92.47.94.99c.01.52.33.63.36.63.17-.03.59-1.42.83-1.83.2-.32.31-.5 1.1-1.04.62-.43 1.13-.83 1.11-.9-.01-.06-.36-.47-.77-.92-.65-.7-.83-.82-1.42-1.02-2.05-.67-4.23-2.12-5.36-3.54-1.25-1.58-1.6-1.94-2.5-2.53-1.01-.67-2.32-1.24-3.3-1.43-1-.19-.72.13-.29.8 1.53 1.75 1.49 1.81 1.49 2.97-.16.9-1 1.03-1.92 1.24m3.17 1.03c.17 0 .58.11 1.29.35 1.74.58 1.84.85 2.41 1.85.13.22.22.41.21.42s-.17.05-.35.09c-.45.09-1.16.09-1.58 0-.76-.16-.72-.19-1.11-.57-.16-.16-.35-.42-.41-.56-.04-.1-.03-.12.19-.3.05-.04.1-.11.1-.15 0-.07-.22-.37-.34-.47-.13-.11-.49-.54-.49-.59 0-.03.03-.05.1-.05z'/></svg>",
"less": "<svg viewBox='0 0 24 24'><path fill='#0277bd' d='M8 3a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2H3v2h1a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h2v-2H8v-5a2 2 0 0 0-2-2 2 2 0 0 0 2-2V5h2V3m6 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3z'/></svg>",
- "liara": "<svg viewBox='0 0 16 16'><defs><linearGradient id='a' x1='11.328' x2='90.301' y1='8.203' y2='10.761' gradientTransform='matrix(.15939 0 0 .16254 -.134 -.857)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#69F0AE'/><stop offset='1' stop-color='#4FC3F7'/></linearGradient><linearGradient id='b' x1='11.328' x2='90.301' y1='8.203' y2='10.761' gradientTransform='matrix(.15939 0 0 .16253 .195 -.856)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#69F0AE'/><stop offset='1' stop-color='#4FC3F7'/></linearGradient><linearGradient id='c' x1='11.328' x2='90.301' y1='8.203' y2='10.761' gradientTransform='matrix(.15784 0 0 .16493 .15 -.355)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#69F0AE'/><stop offset='1' stop-color='#4FC3F7'/></linearGradient></defs><path fill='url(#a)' d='M8.5 8.811c0-.53.368-1.174.82-1.44l3.86-2.257c.452-.265.82-.052.82.479v4.596c0 .527-.368 1.17-.82 1.436l-3.86 2.261c-.452.265-.82.051-.82-.479zm0 0'/><path fill='url(#b)' d='M2 5.593c0-.53.368-.745.82-.479l3.86 2.258c.452.264.82.909.82 1.44v4.595c0 .53-.368.744-.82.48l-3.86-2.262c-.452-.264-.82-.909-.82-1.44zm0 0'/><path fill='url(#c)' d='M3.336 4.467c-.448-.27-.448-.706 0-.972l3.821-2.293c.447-.27 1.173-.27 1.62 0l3.888 2.332c.447.268.447.702 0 .971L8.843 6.799c-.447.268-1.173.268-1.624 0zm0 0'/></svg>",
+ "liara": "<svg viewBox='0 0 16 16'><defs><linearGradient id='a' x1='11.328' x2='90.301' y1='8.203' y2='10.761' gradientTransform='matrix(.15939 0 0 .16254 -.134 -.857)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#69f0ae'/><stop offset='1' stop-color='#4fc3f7'/></linearGradient><linearGradient id='b' x1='11.328' x2='90.301' y1='8.203' y2='10.761' gradientTransform='matrix(.15939 0 0 .16253 .195 -.856)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#69f0ae'/><stop offset='1' stop-color='#4fc3f7'/></linearGradient><linearGradient id='c' x1='11.328' x2='90.301' y1='8.203' y2='10.761' gradientTransform='matrix(.15784 0 0 .16493 .15 -.355)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#69f0ae'/><stop offset='1' stop-color='#4fc3f7'/></linearGradient></defs><path fill='url(#a)' d='M8.5 8.811c0-.53.368-1.174.82-1.44l3.86-2.257c.452-.265.82-.052.82.479v4.596c0 .527-.368 1.17-.82 1.436l-3.86 2.261c-.452.265-.82.051-.82-.479zm0 0'/><path fill='url(#b)' d='M2 5.593c0-.53.368-.745.82-.479l3.86 2.258c.452.264.82.909.82 1.44v4.595c0 .53-.368.744-.82.48l-3.86-2.262c-.452-.264-.82-.909-.82-1.44zm0 0'/><path fill='url(#c)' d='M3.336 4.467c-.448-.27-.448-.706 0-.972l3.821-2.293c.447-.27 1.173-.27 1.62 0l3.888 2.332c.447.268.447.702 0 .971L8.843 6.799c-.447.268-1.173.268-1.624 0zm0 0'/></svg>",
"lib": "<svg fill='none' viewBox='0 0 24 24'><path d='M0 0h24v24H0z'/><path fill='#8bc34a' d='M4 6H2v14c0 1.1.9 2 2 2h14v-2H4zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m0 14H8V4h12zM10 9h8v2h-8zm0 3h4v2h-4zm0-6h8v2h-8z'/></svg>",
- "lighthouse": "<svg viewBox='0 0 24 24'><path fill='#F4511E' d='M8.852 10.182V8.364h.851V4.727h-.851v-.909L12.258 2l3.407 1.818v.91h-.852v3.636h.852v1.818h-1.073L9.226 13.49l.477-3.31h-.851zm4.258-1.818V4.727h-1.703v3.637zM8 22l.034-.218L15.792 17l.443 3.073L13.11 22zm.894-6.21L15.077 12l.443 3.064-7.154 4.409z'/></svg>",
+ "lighthouse": "<svg viewBox='0 0 24 24'><path fill='#f4511e' d='M8.852 10.182V8.364h.851V4.727h-.851v-.909L12.258 2l3.407 1.818v.91h-.852v3.636h.852v1.818h-1.073L9.226 13.49l.477-3.31h-.851zm4.258-1.818V4.727h-1.703v3.637zM8 22l.034-.218L15.792 17l.443 3.073L13.11 22zm.894-6.21L15.077 12l.443 3.064-7.154 4.409z'/></svg>",
"lilypond": "<svg viewBox='0 0 32 32'><path fill='#66bb6a' d='M10 8v11.023A4.986 4.986 0 1 0 11.9 24h.1V11.6l16-3.2v8.623A4.986 4.986 0 1 0 29.9 22h.1V4Z'/></svg>",
"liquid": "<svg viewBox='0 0 24 24'><path fill='#29b6f6' d='M12 21.669a6.927 6.927 0 0 1-6.927-6.927C5.073 10.124 12 2.33 12 2.33s6.927 7.793 6.927 12.41A6.927 6.927 0 0 1 12 21.67z'/></svg>",
"lisp": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M16 2a14 14 0 1 0 14 14A14.003 14.003 0 0 0 16 2m8.93 20.43a11 11 0 0 1-7.19 4.43 6.094 6.094 0 0 1-4.79-5.9v-.05a5 5 0 0 1 .04-.66 7.95 7.95 0 0 1 2.3-4.95 5.99 5.99 0 0 0-2.23-9.9 11.004 11.004 0 0 1 11.87 17.03'/></svg>",
- "livescript": "<svg viewBox='0 0 32 32'><path fill='#0277BD' d='M4 2h4v28H4z'/><path fill='#0277BD' d='M2 24h28v4H2zm8-20h2v18h-2zm2 16h16v2H12zm2-4h14v2H14zm0-12h2v12h-2zm4 8h10v2H18zm0-8h2v8h-2zm4 4h6v2h-6zm0-4h2v4h-2z'/><path fill='#0277BD' d='M6 24.586 23.271 7.315l1.414 1.414L7.415 26z'/></svg>",
+ "livescript": "<svg viewBox='0 0 32 32'><path fill='#0277bd' d='M4 2h4v28H4z'/><path fill='#0277bd' d='M2 24h28v4H2zm8-20h2v18h-2zm2 16h16v2H12zm2-4h14v2H14zm0-12h2v12h-2zm4 8h10v2H18zm0-8h2v8h-2zm4 4h6v2h-6zm0-4h2v4h-2z'/><path fill='#0277bd' d='M6 24.586 23.271 7.315l1.414 1.414L7.415 26z'/></svg>",
"lock": "<svg viewBox='0 0 32 32'><path fill='#ffd54f' d='M25 12h-3V8a6 6 0 0 0-12 0v4H7a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V13a1 1 0 0 0-1-1M14 8a2 2 0 0 1 4 0v4h-4Zm2 17a4 4 0 1 1 4-4 4 4 0 0 1-4 4'/></svg>",
"log": "<svg fill='none' viewBox='0 0 24 24'><path d='M0 0h24v24H0z'/><path fill='#afb42b' d='M19 5v9h-5v5H5V5zm0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h10l6-6V5c0-1.1-.9-2-2-2m-7 11H7v-2h5zm5-4H7V8h10z'/></svg>",
"lolcode": "<svg viewBox='0 0 24 24'><path fill='#ef5350' d='m12 7.687-1.35.091c-.872-1.035-3.318-3.643-5.754-3.643 0 0-1.999 3.004-.04 7.013-.559.842-.904 1.278-.975 2.283l-1.958.294.213.995 1.786-.264.142.72-1.593.954.477.904 1.471-.904c1.167 2.477 4.12 3.735 7.581 3.735 3.46 0 6.414-1.258 7.58-3.735l1.472.904.477-.904-1.593-.953.142-.721 1.786.264.213-.995-1.958-.294c-.071-1.005-.416-1.441-.975-2.283 1.959-4.009-.04-7.013-.04-7.013-2.436 0-4.881 2.608-5.754 3.643zm-3.044 3.044a1.015 1.015 0 0 1 1.014 1.015 1.015 1.015 0 0 1-1.014 1.015 1.015 1.015 0 0 1-1.015-1.015 1.015 1.015 0 0 1 1.015-1.015m6.089 0a1.015 1.015 0 0 1 1.014 1.015 1.015 1.015 0 0 1-1.014 1.015 1.015 1.015 0 0 1-1.015-1.015 1.015 1.015 0 0 1 1.015-1.015m-4.06 3.045h2.03l-.71 1.41c.203.65.77 1.127 1.471 1.127a1.52 1.52 0 0 0 1.522-1.522h.508a2.03 2.03 0 0 1-2.03 2.03c-.761 0-1.42-.416-1.776-1.015a2.07 2.07 0 0 1-1.776 1.015 2.03 2.03 0 0 1-2.03-2.03h.508a1.52 1.52 0 0 0 1.522 1.522c.7 0 1.268-.477 1.471-1.126z'/></svg>",
"lottie": "<svg viewBox='0 0 32 32'><path fill='#00bfa5' d='M2 4v24a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2m20.237 8.11c-2.974.426-3.518 2.058-4.34 4.523-.92 2.764-2.145 6.436-7.635 7.217a1.996 1.996 0 1 1-.499-3.96c2.974-.426 3.518-2.058 4.34-4.523.92-2.764 2.145-6.436 7.635-7.217a1.996 1.996 0 1 1 .499 3.96'/></svg>",
- "ltx.clone": "<svg viewBox='0 0 1024 1024'><path fill='#26A69A' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "ltx.clone": "<svg viewBox='0 0 1024 1024'><path fill='#00bfa5' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
"lua": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M30 6a3.86 3.86 0 0 1-1.167 2.833 4.024 4.024 0 0 1-5.666 0A3.86 3.86 0 0 1 22 6a3.86 3.86 0 0 1 1.167-2.833 4.024 4.024 0 0 1 5.666 0A3.86 3.86 0 0 1 30 6m-9.208 5.208A10.6 10.6 0 0 0 13 8a10.6 10.6 0 0 0-7.792 3.208A10.6 10.6 0 0 0 2 19a10.6 10.6 0 0 0 3.208 7.792A10.6 10.6 0 0 0 13 30a10.6 10.6 0 0 0 7.792-3.208A10.6 10.6 0 0 0 24 19a10.6 10.6 0 0 0-3.208-7.792m-1.959 7.625a4.024 4.024 0 0 1-5.666 0 4.024 4.024 0 0 1 0-5.666 4.024 4.024 0 0 1 5.666 0 4.024 4.024 0 0 1 0 5.666'/></svg>",
- "luau": "<svg fill='none' viewBox='0 0 24 24'><path fill='#03A9F4' d='M22.495 6.331 6.33 2 2 18.164l16.164 4.33z'/><path fill='#FAFAFA' d='M19.933 7.81 16.7 6.944l-.866 3.233 3.233.866z'/></svg>",
+ "luau": "<svg fill='none' viewBox='0 0 24 24'><path fill='#03a9f4' d='M22.495 6.331 6.33 2 2 18.164l16.164 4.33z'/><path fill='#fafafa' d='M19.933 7.81 16.7 6.944l-.866 3.233 3.233.866z'/></svg>",
"lyric": "<svg viewBox='0 0 32 32'><path fill='#50c5ef' d='M26 7.7c0-.1-.1-.2-.2-.3l-5.2-5.2c-.1-.1-.3-.2-.4-.2h-.1q-.15 0 0 0H7.7c-1 .1-1.7.8-1.7 1.8v24.5c0 1 .8 1.7 1.7 1.7h16.6c1 0 1.7-.8 1.7-1.7zq0 .15 0 0M22 12h-4v8c0 2.2-1.8 4-4 4s-4-1.8-4-4 1.8-4 4-4c.7 0 1.4.2 2 .6V8h6z'/></svg>",
"makefile": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m29.5 24.02-1.6-.92a4.4 4.4 0 0 0 .09-.9A1.3 1.3 0 0 0 28 22a5.6 5.6 0 0 0-.1-1.1l1.6-.92a.493.493 0 0 0 .18-.68l-1.5-2.6a.45.45 0 0 0-.18-.18V6.01a2.006 2.006 0 0 0-2-2H4a2.006 2.006 0 0 0-2 2V22a2.006 2.006 0 0 0 2 2h10.53l-.03.02a.493.493 0 0 0-.18.68l1.5 2.6a.493.493 0 0 0 .68.18l1.6-.92a5.9 5.9 0 0 0 1.9 1.09v1.85a.495.495 0 0 0 .5.5h3a.495.495 0 0 0 .5-.5v-1.85a5.9 5.9 0 0 0 1.9-1.09l1.6.92a.493.493 0 0 0 .68-.18l1.5-2.6a.493.493 0 0 0-.18-.68M24 22.01a1.99 1.99 0 0 1-.88 1.65l-.18.11a2.04 2.04 0 0 1-1.88 0l-.18-.11a1.99 1.99 0 0 1-.88-1.65V22a2 2 0 0 1 .88-1.66l.18-.11a2.04 2.04 0 0 1 1.88 0l.18.11A2 2 0 0 1 24 22Zm2-4.63-.1.06a5.9 5.9 0 0 0-1.9-1.09V14.5a.495.495 0 0 0-.5-.5h-3a.495.495 0 0 0-.5.5v1.85a5.9 5.9 0 0 0-1.9 1.09l-1.6-.92a.493.493 0 0 0-.68.18l-1.5 2.6a.493.493 0 0 0 .18.68l1.6.92A5.6 5.6 0 0 0 16 22v.01L4 22V10.01h22Z'/></svg>",
"markdoc-config": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h6v-4H6v-2h6v-2H6v-2h6v-2H6v-2h6v-2h2V4l8 8h2v-1Z'/><path fill='#ffb300' d='M12 12v18h18V12Zm10 14h-2v-6l-2 2-2-2v6h-2V16h2l2 2 2-2h2Zm6 2h-4V14h4Z'/></svg>",
@@ -811,7 +820,7 @@
"minecraft-fabric": "<svg fill-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='1.414' clip-rule='evenodd' viewBox='0 0 16 16'><path fill='#38342a' d='M8 1v1H7v2H6v1H5v1H4v1H3v1H2v1H1v2h1v1h1v1h1v1h1v1h2v-1h1v-2h1v-1h1v-1h1V9h2V8h1V6h-1V5h-1V4h-1V3h-1V2H9V1z'/><path fill='#dbd0b4' d='M8 2v1h1v1h1v1h1v1h1V5h-1V4h-1V3H9V2zM7 4v2H5v1H4v1H3v2h1v1h1v1h2v-2H6V9h2v1h1V8H8V7h2V6H9V5H8V4z'/><path fill='#38342a' d='M8 4v1h1v1h1v1h1V6h-1V5H9V4z'/><path fill='#bcb29c' d='M9 4v1h1V4zm1 1v1h1V5zm1 1v1h1V6zm0 1h-1v1H9v2h1V9h1zm-2 3H7v2h1v-1h1zm-2 2H6v1h1z'/><path fill='#807a6d' d='M12 7v1h1V7z'/><path fill='#aea694' d='M2 9v1h1v1h1v1h1v1h1v-1H5v-1H4v-1H3V9z'/><path fill='#9a927e' d='M2 10v1h1v-1zm1 1v1h1v-1zm1 1v1h1v-1zm1 1v1h1v-1z'/><path fill='#c6bca5' d='M8 3v1h1V3zM6 5v1h1V5zm1 1v1h1V6zm1 1v1h2V7zM5 8v1h1V8zm1 1v1h2V9z'/></svg>",
"minecraft": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='M4 4v24h24V4Zm20 10h-6v2h4v8h-4v-4h-4v4h-4v-8h4v-2H8V8h6v6h4V8h6Z'/></svg>",
"mint": "<svg viewBox='0 0 256 256'><path fill='#43a047' d='M128 18.753c-2.84 10.841-27.094 17.766-28.772 31.512l-2.738-8.22C82.21 60.232 83.473 69.446 83.473 69.446l-1.2-6.593c-10.422 17.426-8.215 25.963-7.012 28.566l.505.892s-.225-.287-.505-.892l-2.748-4.846c-10.824 29.941-1.371 39.133-1.371 39.133l-5.822-6.764c-2.573 22.94 4.11 32.368 4.11 32.368l-4.196-3.94c-.534 15.582 9.677 24.833 9.677 24.833l-6.73-4.615c12.951 56.58 38.666 58.104 41.982 58.061l-10.25.331c6.836 7.318 15.562 10.156 21.464 11.267 3.218-17.067 6.176-58.395 6.623-166.69.447 108.29 3.407 149.62 6.625 166.69 5.903-1.112 14.629-3.951 21.464-11.267l-10.248-.33c3.32.042 29.03-1.488 41.98-58.062l-6.73 4.616s10.211-9.25 9.677-24.833l-4.196 3.94s6.683-9.427 4.11-32.368l-5.822 6.764s9.453-9.192-1.371-39.133l-2.746 4.84c-.281.609-.508.895-.508.895l.508-.894c1.204-2.609 3.403-11.145-7.015-28.561l-1.199 6.593s1.264-9.215-13.017-27.402l-2.74 8.221c-1.677-13.745-25.93-20.67-28.771-31.51z'/></svg>",
- "mist.clone": "<svg viewBox='0 0 24 24'><path fill='#2196F3' d='M12 21.669a6.927 6.927 0 0 1-6.927-6.927C5.073 10.124 12 2.33 12 2.33s6.927 7.793 6.927 12.41A6.927 6.927 0 0 1 12 21.67z'/></svg>",
+ "mist.clone": "<svg viewBox='0 0 24 24'><path fill='#2196f3' d='M12 21.669a6.927 6.927 0 0 1-6.927-6.927C5.073 10.124 12 2.33 12 2.33s6.927 7.793 6.927 12.41A6.927 6.927 0 0 1 12 21.67z'/></svg>",
"mjml": "<svg viewBox='0 0 120 120'><g transform='translate(9.943 14.253)scale(.8026)'><path fill='#ff5722' d='M14.5 0h57.3c8 0 14.5 6.5 14.5 14.5S79.8 29 71.8 29H14.5C6.5 29 0 22.5 0 14.5S6.5 0 14.5 0'/><ellipse cx='109.2' cy='14.5' fill='#ff5722' rx='14.8' ry='14.5'/><path fill='#ff5722' d='M52.6 43.3h56.6c8-.6 14.9 5.5 15.5 13.5s-5.5 14.9-13.5 15.5H52.6c-8 .6-14.9-5.5-15.5-13.5s5.5-14.9 13.5-15.5H52z'/><path fill='#ff1744' d='M14.8 43c8.2 0 14.8 6.6 14.8 14.8S23 72.6 14.8 72.6C6.6 72.5 0 65.9 0 57.8 0 49.6 6.6 43 14.8 43'/><path fill='#ff5722' d='M14.5 85h57.3c8 0 14.5 6.5 14.5 14.5S79.8 114 71.8 114H14.5C6.5 114 0 107.5 0 99.5S6.5 85 14.5 85'/><ellipse cx='109.2' cy='99.5' fill='#ff5722' rx='14.8' ry='14.5'/></g></svg>",
"mocha": "<svg viewBox='0 0 32 32'><path fill='#a1887f' d='M22 14c-.002 7.41-.07 10.857-2.486 14h-7.028C10.07 24.857 10.002 21.41 10 14zm.823-2H9.177A1.266 1.266 0 0 0 8 13.342C8 22 8 26 11.546 30h8.908C24 26 24 22 24 13.342A1.266 1.266 0 0 0 22.823 12m-4.82-9.998c1.15.46 2 2.075 2 3.998 0 1.925-.851 3.54-2.003 4V7.998c-1.15-.46-2-2.074-2-3.998s.851-3.54 2.003-4ZM13 6.004A2.11 2.11 0 0 1 14 8a2.1 2.1 0 0 1-1 2V9a2.11 2.11 0 0 1-1-1.998 2.1 2.1 0 0 1 1-1.998Z'/><path fill='#a1887f' d='M16 20h-3c0 2 1 4 1 6h4c0-2 1-4 1-6Z'/></svg>",
"modernizr": "<svg viewBox='0 0 32 32'><path fill='#e91e63' d='M10 10v4H6v4H2v4h12V10zm8 0v12h12a12 12 0 0 0-12-12'/></svg>",
@@ -822,18 +831,18 @@
"nano-staged": "<svg viewBox='0 0 24 24'><path fill='#b0bec5' d='M12 1.481A10.495 10.495 0 0 0 1.48 11.999c0 5.828 4.69 10.52 10.518 10.52S22.52 17.826 22.52 12A10.496 10.496 0 0 0 12 1.481M7.952 6.899a.2.2 0 0 1 .11.028l5.661 3.27a.21.21 0 0 0 .212 0l2.002-1.157a.21.21 0 0 1 .317.18v7.67a.21.21 0 0 1-.317.183l-5.662-3.27a.21.21 0 0 0-.21 0l-2.004 1.159a.21.21 0 0 1-.314-.183V7.11c0-.121.097-.208.205-.21z'/></svg>",
"nano-staged_light": "<svg viewBox='0 0 24 24'><path fill='#546e7a' d='M11.999 1.481A10.495 10.495 0 0 0 1.48 11.999c0 5.828 4.69 10.52 10.518 10.52 5.827 0 10.52-4.693 10.52-10.52a10.496 10.496 0 0 0-10.52-10.518zM7.953 6.899a.2.2 0 0 1 .109.028l5.662 3.27a.21.21 0 0 0 .212 0l2.002-1.157a.21.21 0 0 1 .317.18v7.67a.21.21 0 0 1-.317.183l-5.662-3.27a.21.21 0 0 0-.21 0l-2.004 1.16a.21.21 0 0 1-.315-.184V7.11a.21.21 0 0 1 .206-.21z'/></svg>",
"ndst": "<svg viewBox='0 0 32 32'><path fill='#0097a7' d='M8.2 12.2c-.8-.1-1.3.3-1.4 1-.1.9.3 1.3 1.1 1.4s1.2-.3 1.4-1c.1-.7-.3-1.3-1.1-1.4'/><path fill='#0097a7' d='M16 2c-1.4 0-2.8.2-4.1.6.2.7.5 1.3.9 1.9.9 1.5 2.3 2.2 4 2.3.8.1 1.6.1 2.4.1 2.1 0 3.8 2.4 2.9 4.6-.5 1.2-1.1 1.9-2.4 2.1-1.6.2-2.8-.3-3.6-1.7-.1-.3-.3-.6-.4-.9-.2-.6-.4-1.3-.7-1.9-.8-2.3-3.4-3.6-5.7-3.1-.8.1-1.8-.1-2.5-.5C3.8 8.1 2 11.8 2 16c0 7.7 6.3 14 14 14s14-6.3 14-14S23.7 2 16 2m5.8 21.3c-.5 1.2-1.1 1.9-2.4 2.1-1.6.2-2.8-.3-3.6-1.7-.1-.3-.3-.6-.4-.9-.2-.6-.4-1.3-.7-1.9-.9-2.4-3.5-3.6-5.8-3.2-1.3.2-3.2-.5-3.8-1.6-.8-1.3-.7-3.1.1-4.1.5-.6 1.3-1 2.1-1.2h.9c1.5.2 2.4.9 2.9 2.4.3 1.1.7 2.1 1.3 3.1.9 1.5 2.3 2.2 4 2.3.8.1 1.6.1 2.4.1 2.1 0 3.8 2.5 3 4.6'/><path fill='#0097a7' d='M18.4 21.8c-.6.1-.8.6-.8 1.1s.4 1 .7 1.3c.5.3 1.1.3 1.5-.3.1-.1.4-1 .2-1.3-.2-.5-1.4-.8-1.6-.8m1.7-9.7c.1-.1.4-1 .2-1.3-.2-.4-1.4-.7-1.6-.7-.6.1-.8.6-.8 1.1s.4 1 .7 1.3c.5.3 1.2.3 1.5-.4'/></svg>",
- "nest-controller.clone": "<svg viewBox='0 0 300 300'><path fill='#0288D1' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-decorator.clone": "<svg viewBox='0 0 300 300'><path fill='#AB47BC' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-filter.clone": "<svg viewBox='0 0 300 300'><path fill='#FF7043' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-gateway.clone": "<svg viewBox='0 0 300 300'><path fill='#AFB42B' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-guard.clone": "<svg viewBox='0 0 300 300'><path fill='#43A047' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-interceptor.clone": "<svg viewBox='0 0 300 300'><path fill='#FF9800' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-middleware.clone": "<svg viewBox='0 0 300 300'><path fill='#5C6BC0' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-module.clone": "<svg viewBox='0 0 300 300'><path fill='#E53935' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-pipe.clone": "<svg viewBox='0 0 300 300'><path fill='#00897B' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-resolver.clone": "<svg viewBox='0 0 300 300'><path fill='#EC407A' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest-service.clone": "<svg viewBox='0 0 300 300'><path fill='#FFCA28' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
- "nest": "<svg viewBox='0 0 300 300'><path fill='#FF1744' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-controller.clone": "<svg viewBox='0 0 300 300'><path fill='#0288d1' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-decorator.clone": "<svg viewBox='0 0 300 300'><path fill='#ab47bc' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-filter.clone": "<svg viewBox='0 0 300 300'><path fill='#ff7043' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-gateway.clone": "<svg viewBox='0 0 300 300'><path fill='#afb42b' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-guard.clone": "<svg viewBox='0 0 300 300'><path fill='#43a047' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-interceptor.clone": "<svg viewBox='0 0 300 300'><path fill='#ff9800' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-middleware.clone": "<svg viewBox='0 0 300 300'><path fill='#5c6bc0' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-module.clone": "<svg viewBox='0 0 300 300'><path fill='#e53935' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-pipe.clone": "<svg viewBox='0 0 300 300'><path fill='#00897b' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-resolver.clone": "<svg viewBox='0 0 300 300'><path fill='#ec407a' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest-service.clone": "<svg viewBox='0 0 300 300'><path fill='#ffca28' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
+ "nest": "<svg viewBox='0 0 300 300'><path fill='#ff1744' d='M172.382 24.41c-1.793 0-3.5.426-5.036.938 3.329 2.22 5.121 5.121 6.06 8.45.085.427.17.768.256 1.195s.17.768.17 1.195c.257 5.719-1.536 6.401-2.73 9.815-1.878 4.353-1.366 9.048.938 12.803.17.427.427.94.768 1.451-2.475-16.473 11.267-18.948 13.827-24.07.171-4.523-3.499-7.51-6.401-9.558-2.987-1.708-5.548-2.22-7.852-2.22zm20.655 3.67c-.256 1.536-.086 1.11-.17 1.877-.086.512-.086 1.195-.172 1.707-.17.512-.256 1.024-.426 1.537l-.512 1.536c-.257.512-.427.939-.683 1.536l-.512.768c-.171.171-.256.427-.427.598-.342.427-.683.939-.939 1.28-.427.427-.683.854-1.195 1.195v.085c-.427.342-.768.683-1.195 1.025-1.366 1.024-2.902 1.792-4.353 2.816-.427.342-.939.598-1.28.94-.427.34-.854.682-1.195 1.023l-1.195 1.195c-.341.427-.683.854-.939 1.28-.341.427-.683.94-.939 1.366-.256.512-.426.94-.683 1.537-.17.512-.426.938-.512 1.536-.17.597-.341 1.11-.426 1.622-.086.256-.086.597-.171.853s-.085.512-.17.768c0 .512-.086 1.11-.086 1.622 0 .427 0 .768.085 1.195 0 .512.086 1.024.17 1.622.086.512.172 1.024.342 1.536l.512 1.536c.171.342.342.683.427.94l-14.936-5.805c-2.561-.683-5.036-1.365-7.511-1.963-1.366-.341-2.732-.683-4.097-.939a137 137 0 0 0-11.864-1.792c-.17 0-.17-.086-.342-.086-3.926-.426-7.767-.597-11.608-.597-2.901 0-5.718.17-8.535.341-3.926.256-7.937.769-11.864 1.451-.939.171-1.963.342-2.902.513-2.048.426-3.926.853-5.889 1.28-.939.256-1.963.512-2.902.768-.939.427-1.878.854-2.816 1.195a23 23 0 0 1-2.134.939c-.171.085-.256.085-.342.17a34 34 0 0 0-1.792.94c-.17.085-.341.17-.427.17-.683.341-1.45.683-2.048 1.024-.427.171-.94.427-1.28.683-.171.17-.427.256-.598.342-.597.34-1.195.682-1.707.938-.598.342-1.11.683-1.536.94-.427.34-.94.597-1.28.938-.086.085-.171.085-.171.17a6.5 6.5 0 0 0-1.195.94l-.171.17c-.341.256-.683.513-.939.769-.17.085-.256.17-.427.256-.341.256-.682.597-.939.853-.085.17-.17.17-.256.256-.426.427-.768.683-1.195 1.11-.085 0-.085.085-.17.17-.427.342-.768.683-1.195 1.11-.085.085-.085.17-.17.17-.342.342-.684.684-.94 1.025-.17.17-.341.256-.426.427-.342.427-.683.768-1.11 1.195-.085.17-.17.17-.256.341-.512.512-.939 1.024-1.536 1.536l-.171.171c-1.024 1.11-2.134 2.22-3.329 3.158-1.195 1.024-2.39 2.049-3.67 2.902-1.28.939-2.56 1.707-3.926 2.475-1.28.683-2.646 1.366-4.097 1.963a36 36 0 0 1-4.268 1.537c-2.731.597-5.548 1.707-7.937 1.878-.513 0-1.11.17-1.622.17-.598.17-1.11.256-1.622.427s-1.024.427-1.536.597-1.024.427-1.536.683c-.427.342-.94.598-1.451.94-.427.34-.94.682-1.28 1.109-.428.341-.94.768-1.281 1.195-.427.426-.768.853-1.11 1.28-.341.512-.682.939-.938 1.536-.342.427-.683.94-.94 1.537s-.511 1.11-.682 1.707c-.17.512-.427 1.11-.598 1.707-.17.512-.256 1.024-.341 1.536 0 .085-.085.17-.085.17-.171.598-.171 1.366-.171 1.793-.085.427-.17.854-.17 1.28 0 .256 0 .598.085.854.085.427.17.853.256 1.195.17.427.256.768.426 1.195v.085c.171.427.427.768.683 1.195s.512.768.854 1.195c.341.341.683.683 1.11 1.024.426.427.767.683 1.194 1.024 1.537 1.366 1.963 1.793 3.926 2.902l1.025.512c.085 0 .17.086.17.086 0 .17 0 .17.086.341.085.512.17 1.024.341 1.537q.256.896.512 1.536c.256.64.342.768.512 1.195.086.17.171.256.171.341.256.512.512.94.768 1.451.342.427.683.94.94 1.366.34.427.682.853 1.109 1.195.426.427.768.683 1.195 1.11 0 0 .085.085.17.085.427.341.768.683 1.195.939.427.341.94.597 1.451.853.427.256.94.512 1.537.683.426.17.853.341 1.28.427.085.085.17.085.256.17.256.086.597.171.853.171-.17 3.5-.256 6.828.256 8.023.598 1.28 3.415-2.646 6.316-7.255-.426 4.524-.682 9.73 0 11.352s4.61-3.414 8.024-9.047c46.09-10.67 88.168 21.167 92.606 66.233-.853-6.999-9.474-10.925-13.485-9.986-1.963 4.78-5.292 11.01-10.584 14.851.427-4.268.256-8.706-.683-12.974-1.45 5.975-4.267 11.608-8.023 16.388-6.145.427-12.376-2.56-15.62-6.999-.255-.17-.34-.597-.511-.853-.17-.427-.427-.94-.512-1.366-.171-.427-.342-.939-.427-1.366s-.085-.938-.085-1.45v-.94c.085-.426.17-.938.341-1.365s.256-.939.427-1.366c.256-.426.426-.939.768-1.365 1.11-3.158 1.11-5.634-.939-7.17-.427-.256-.768-.427-1.195-.683-.256-.085-.597-.17-.853-.256-.171-.085-.342-.17-.513-.17-.426-.171-.938-.257-1.365-.342-.427-.17-.939-.17-1.366-.17-.427-.086-.939-.171-1.45-.171-.342 0-.684.085-.94.085-.512 0-.939.085-1.45.17-.427.086-.94.171-1.366.257-.427.17-.94.256-1.366.427s-.853.426-1.28.597-.768.427-1.195.683c-15.193 9.9-6.145 33.031 4.267 39.689-3.926.682-7.852 1.536-8.961 2.39l-.171.17a56 56 0 0 0 8.791 4.353c4.182 1.366 8.62 2.56 10.498 3.158v.085c5.378 1.11 10.84 1.537 16.388 1.195 28.764-2.048 52.406-23.898 56.674-52.833.17.598.256 1.11.426 1.708.171 1.194.427 2.39.598 3.67v.085c.17.597.17 1.195.256 1.707v.256c.085.598.17 1.195.17 1.707.086.683.171 1.451.171 2.134v1.024c0 .342.086.683.086 1.024 0 .427-.086.769-.086 1.195v.94c0 .426-.085.853-.085 1.28 0 .256 0 .512-.085.853 0 .427-.086.939-.086 1.451-.085.17-.085.427-.085.597-.085.513-.17.94-.17 1.537 0 .17 0 .427-.086.597-.085.683-.17 1.195-.256 1.878v.17c-.17.598-.256 1.196-.427 1.793v.17c-.17.598-.256 1.196-.427 1.793 0 .086-.085.17-.085.256-.17.598-.256 1.195-.427 1.793v.17c-.17.683-.427 1.195-.512 1.793-.085.085-.085.17-.085.17-.17.683-.427 1.28-.598 1.964-.256.682-.426 1.194-.683 1.877s-.426 1.28-.682 1.878c-.256.683-.512 1.195-.768 1.878h-.086c-.256.597-.512 1.195-.853 1.792-.086.17-.171.342-.171.427-.085.085-.085.17-.17.17-4.268 8.536-10.5 15.961-18.266 21.85-.512.342-1.024.684-1.536 1.11-.171.171-.342.171-.427.342a8.5 8.5 0 0 1-1.451.939l.17.426h.086c.939-.17 1.792-.256 2.731-.426h.085c1.708-.256 3.415-.598 5.036-.94.427-.085.94-.17 1.451-.34.342-.086.598-.171.94-.171.426-.086.938-.171 1.365-.256.427-.171.768-.171 1.195-.342 6.486-1.536 12.802-3.67 18.862-6.23-10.327 14.082-24.154 25.52-40.371 32.945a109 109 0 0 0 22.191-3.84c26.204-7.768 48.224-25.35 61.454-49.078-2.646 15.022-8.62 29.361-17.497 41.823 6.316-4.183 12.12-8.962 17.326-14.425 14.595-15.193 24.155-34.568 27.398-55.308 2.22 10.242 2.902 20.911 1.878 31.324 46.944-65.465 3.926-133.405-14.083-151.244-.085-.17-.17-.17-.17-.341-.086.085-.086.085-.086.17 0-.085 0-.085-.086-.17 0 .768-.085 1.536-.17 2.304-.17 1.537-.427 2.902-.683 4.353-.341 1.451-.683 2.902-1.11 4.268s-.938 2.817-1.536 4.182c-.597 1.28-1.195 2.646-1.963 3.926a53 53 0 0 1-2.305 3.67c-.853 1.196-1.792 2.39-2.645 3.5-.94 1.195-2.049 2.22-3.073 3.243-.683.598-1.195 1.11-1.878 1.622-.512.427-.939.854-1.536 1.28-1.195.94-2.305 1.793-3.67 2.56-1.195.77-2.56 1.537-3.841 2.22-1.366.683-2.731 1.195-4.097 1.792-1.366.513-2.817.94-4.268 1.366s-2.902.683-4.353.939a42 42 0 0 1-4.438.512c-1.024.085-2.048.17-3.158.17-1.536 0-2.987-.17-4.438-.255-1.537-.171-2.988-.342-4.439-.683-1.536-.256-2.902-.683-4.353-1.11h-.085c1.451-.17 2.902-.256 4.268-.512 1.536-.256 2.902-.597 4.353-.939 1.45-.426 2.902-.853 4.267-1.365 1.451-.512 2.817-1.195 4.097-1.793 1.366-.682 2.56-1.365 3.926-2.133 1.195-.854 2.476-1.707 3.67-2.561 1.195-.939 2.305-1.878 3.33-2.902 1.109-.939 2.048-2.048 3.072-3.158.939-1.195 1.878-2.305 2.731-3.5.17-.17.256-.426.427-.682a39 39 0 0 0 1.878-3.158c.682-1.28 1.365-2.56 1.963-3.926s1.11-2.732 1.536-4.183c.427-1.365.768-2.816 1.11-4.267.256-1.537.512-2.902.682-4.353.171-1.537.257-2.988.257-4.439 0-1.024-.086-2.048-.171-3.158-.17-1.536-.342-2.902-.512-4.353-.256-1.536-.598-2.902-.94-4.352-.426-1.366-.938-2.817-1.45-4.183s-1.195-2.731-1.793-4.011c-.682-1.28-1.45-2.56-2.219-3.841l-2.56-3.585c-.94-1.11-1.963-2.22-2.988-3.329-.512-.512-1.11-1.11-1.707-1.621-2.902-2.305-5.974-4.439-9.047-6.402-.427-.256-.853-.426-1.28-.683-2.049-1.536-4.012-2.219-6.06-2.902z'/></svg>",
"netlify": "<svg viewBox='0 0 24 24'><path fill='#00bfa5' d='m11.25 2.232-.127.127v4.567l.127.129h1.526l.126-.13V2.36l-.126-.127H11.25zM6.69 5.455 5.643 6.5v.21l1.6 1.6h1.108l.148-.148V7.055l-1.6-1.6zM1.026 11.11l-.127.127v1.528l.127.127h6.09l.127-.127v-1.528l-.127-.127zm15.858 0-.127.127v1.528l.127.127h6.09l.127-.127v-1.528l-.127-.127zm-9.64 4.58-1.6 1.6v.21l1.045 1.046h.21l1.6-1.6v-1.109l-.148-.146zm4.005 1.256-.127.13v4.566l.127.127h1.526l.126-.127v-4.567l-.126-.129z'/><path fill='#cfd8dc' d='M14.855 15.172h-1.523l-.127-.127V11.48c0-.634-.249-1.125-1.013-1.142-.394-.01-.844 0-1.325.019l-.072.074v4.61l-.127.128H9.146l-.128-.127V8.956l.128-.127h3.425a2.41 2.41 0 0 1 2.41 2.41v3.806z'/></svg>",
"netlify_light": "<svg viewBox='0 0 24 24'><path fill='#00bfa5' d='m11.25 2.232-.127.127v4.567l.127.129h1.526l.126-.13V2.36l-.126-.127H11.25zM6.69 5.455 5.643 6.5v.21l1.6 1.6h1.108l.148-.148V7.055l-1.6-1.6zM1.026 11.11l-.127.127v1.528l.127.127h6.09l.127-.127v-1.528l-.127-.127zm15.858 0-.127.127v1.528l.127.127h6.09l.127-.127v-1.528l-.127-.127zm-9.64 4.58-1.6 1.6v.21l1.045 1.046h.21l1.6-1.6v-1.109l-.148-.146zm4.005 1.256-.127.13v4.566l.127.127h1.526l.126-.127v-4.567l-.126-.129z'/><path fill='#00897b' d='M14.855 15.172h-1.523l-.127-.127V11.48c0-.634-.249-1.125-1.013-1.142-.394-.01-.844 0-1.325.019l-.072.074v4.61l-.127.128H9.146l-.128-.127V8.956l.128-.127h3.425a2.41 2.41 0 0 1 2.41 2.41v3.806z'/></svg>",
"next": "<svg viewBox='0 0 32 32'><path fill='#cfd8dc' d='M16 2a14 14 0 1 0 5.816 26.723L12 14v9a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h2.434a1 1 0 0 1 .857.486l11.491 19.15A14 14 0 0 0 16 2m8 16h-4V9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1Z'/></svg>",
@@ -846,11 +855,11 @@
"ngrx-selectors": "<svg viewBox='0 0 300 300'><path fill='#ff6e40' d='M150 27.324 35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z'/></svg>",
"ngrx-state": "<svg viewBox='0 0 300 300'><path fill='#9ccc65' d='M150 27.324 35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z'/></svg>",
"nim": "<svg viewBox='0 0 32 32'><path fill='#ffca28' d='M6 24h20v2a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2zM30 6l-9 9-5-11-5 11-9-9 4 14h20z'/></svg>",
- "nix": "<svg viewBox='0 0 500 500'><g stroke-width='.395'><path fill='#1976D2' d='M133.347 451.499c0-.295-2.752-5.283-6.116-11.084s-6.116-10.776-6.116-11.055 9.514-16.889 21.143-36.912c11.629-20.022 21.323-36.798 21.542-37.279.346-.76-1.608-4.363-14.896-27.466-8.412-14.625-15.294-26.785-15.294-27.023 0-.5 24.46-43.501 25.206-44.31.414-.45.592-.384 1.078.395.32.513 16.876 29.256 36.791 63.87 62.62 108.85 74.852 130.01 75.41 130.46.3.242.544.554.544.694s-11.836.21-26.302.154c-23.023-.09-26.313-.175-26.393-.694-.11-.714-27.662-48.825-28.86-50.392-.746-.978-.906-1.035-1.426-.51-.688.696-28.954 49.323-29.49 50.733l-.364.96h-13.23c-10.895 0-13.228-.095-13.228-.538zm167.58-125.61c-.134-.216 1.189-2.863 2.939-5.882 6.924-11.944 84.29-145.75 96.49-166.88 7.143-12.371 13.143-22.465 13.334-22.433.362.062 25.86 43.105 25.86 43.655 0 .174-6.761 11.952-15.025 26.173-8.46 14.557-14.932 26.104-14.81 26.421.185.483 4.563.564 30.213.564h29.996l.957 1.48c.527.814 3.296 5.547 6.155 10.518s5.45 9.29 5.757 9.597c.705.705.703.724-.16 1.572-.396.388-3.36 5.323-6.588 10.965-3.228 5.643-6.056 10.387-6.285 10.543s-19.695.171-43.256.034l-42.84-.249-.803 1.15c-.442.632-7.505 12.736-15.696 26.897l-14.892 25.747h-15.486c-8.518 0-20.015.116-25.551.259-6.55.168-10.15.121-10.308-.135zm-133.75-157.86c-56.373-.055-102.5-.182-102.5-.282s5.617-10.132 12.481-22.294L89.64 123.34h30.332c27.113 0 30.332-.065 30.332-.611 0-.336-6.659-12.228-14.797-26.427s-14.797-25.917-14.797-26.04 2.682-4.853 5.96-10.51 6.003-10.578 6.056-10.934c.086-.586 1.375-.648 13.572-.648 7.412 0 13.463.143 13.446.317-.018.174.22.707.53 1.184.31.476 9.763 16.937 21.007 36.578 11.244 19.64 20.71 36.022 21.036 36.4.554.647 2.549.691 31.428.691h30.837l12.896 22.145c7.093 12.18 12.8 22.301 12.682 22.492-.117.19-4.776.303-10.352.249-5.575-.054-56.26-.143-112.63-.198z'/><path fill='#64B5F6' d='M23.046 238.939c-6.098 10.563-6.69 11.711-6.224 12.078.282.224 3.18 5.044 6.44 10.712s6.016 10.355 6.123 10.417c.106.061 13.585.153 29.95.204 16.367.052 29.994.23 30.285.399.473.273-1.08 3.094-14.637 26.574l-15.166 26.269 12.907 21.865c7.1 12.026 12.982 21.906 13.068 21.956s23.257-39.831 51.492-88.624c11.352-19.617 21.214-36.64 30.37-52.442 23.308-40.452 30.68-53.468 30.73-54.132-1.096-.11-6.141-.187-13.006-.216-3.945-.01-7.82-.02-12.75-.002l-25.341.092-15.42 26.706c-14.256 24.693-15.445 26.663-16.278 26.86l-.023.037c-.012.003-1.622-.001-1.826 0-4.29.062-20.453.063-40.226-.01-22.632-.082-41.615-.125-42.183-.096-.567.03-1.147-.03-1.29-.132-.141-.102-3.29 5.066-6.996 11.485zm205.16-190.3c-.123.149 5.62 10.392 12.761 22.763 12.2 21.131 89.393 155.03 96.276 167 1.503 2.613 2.92 4.803 3.443 5.348.9-1.249 3.532-5.63 7.954-13.219a1343 1343 0 0 1 10.05-17.76l6.606-11.443c.691-1.403.753-1.818.652-2.117-.161-.48-6.903-12.332-14.982-26.337-8.078-14.005-14.824-25.849-14.99-26.32a.73.73 0 0 1-.01-.366l-.426-.913 21.636-36.976c3.69-6.307 6.425-11.042 9.471-16.29 9.158-15.948 12.036-21.189 11.895-21.55-.126-.324-2.7-4.83-5.72-10.017-3.021-5.185-5.845-10.148-6.275-11.026-.483-.987-.734-1.364-1.1-1.456-.054.014-.083.018-.144.035-.42.112-5.455.195-11.19.185s-11.22.024-12.187.073l-1.76.089-14.998 25.978c-12.824 22.212-15.084 25.964-15.595 25.883-.024-.004-.15-.189-.235-.301-.109.066-.2.09-.271.05-.256-.148-7.144-11.902-15.306-26.119L279.4 48.817c-.116-.186-.444-.744-.458-.752-.476-.275-50.502.287-50.737.57zm-18.646 283.09c-.047.109-.026.262.043.48.328 1.05 25.338 43.735 25.772 43.985.206.119 14.178.239 31.05.266 26.65.044 30.749.152 31.234.832.307.43 9.987 17.214 21.513 37.296s21.152 36.627 21.394 36.767 5.926.243 12.633.23c6.705-.013 12.4.099 12.657.246.131.076.381-.141.851-.795l6.008-10.406c5.234-9.065 6.62-11.684 6.294-11.888-.575-.36-15.597-26.643-23.859-41.482-3.09-5.45-5.37-9.516-5.44-9.774-.196-.712-.066-.822 1.155-.98 1.956-.252 57.397-.057 58.071.205.237.092.79-.569 2.593-3.497 1.866-3.067 5.03-8.524 11.001-18.866 7.22-12.505 13.043-22.784 12.941-22.843s-.77-.051-1.489.016l-.046.001c-4.451.204-33.918.203-149.74.025-38.96-.06-69.786-.09-71.912-.072-1.12.01-2.095.076-2.66.172a.3.3 0 0 0-.062.083z'/></g></svg>",
+ "nix": "<svg viewBox='0 0 500 500'><g stroke-width='.395'><path fill='#1976d2' d='M133.347 451.499c0-.295-2.752-5.283-6.116-11.084s-6.116-10.776-6.116-11.055 9.514-16.889 21.143-36.912c11.629-20.022 21.323-36.798 21.542-37.279.346-.76-1.608-4.363-14.896-27.466-8.412-14.625-15.294-26.785-15.294-27.023 0-.5 24.46-43.501 25.206-44.31.414-.45.592-.384 1.078.395.32.513 16.876 29.256 36.791 63.87 62.62 108.85 74.852 130.01 75.41 130.46.3.242.544.554.544.694s-11.836.21-26.302.154c-23.023-.09-26.313-.175-26.393-.694-.11-.714-27.662-48.825-28.86-50.392-.746-.978-.906-1.035-1.426-.51-.688.696-28.954 49.323-29.49 50.733l-.364.96h-13.23c-10.895 0-13.228-.095-13.228-.538zm167.58-125.61c-.134-.216 1.189-2.863 2.939-5.882 6.924-11.944 84.29-145.75 96.49-166.88 7.143-12.371 13.143-22.465 13.334-22.433.362.062 25.86 43.105 25.86 43.655 0 .174-6.761 11.952-15.025 26.173-8.46 14.557-14.932 26.104-14.81 26.421.185.483 4.563.564 30.213.564h29.996l.957 1.48c.527.814 3.296 5.547 6.155 10.518s5.45 9.29 5.757 9.597c.705.705.703.724-.16 1.572-.396.388-3.36 5.323-6.588 10.965-3.228 5.643-6.056 10.387-6.285 10.543s-19.695.171-43.256.034l-42.84-.249-.803 1.15c-.442.632-7.505 12.736-15.696 26.897l-14.892 25.747h-15.486c-8.518 0-20.015.116-25.551.259-6.55.168-10.15.121-10.308-.135zm-133.75-157.86c-56.373-.055-102.5-.182-102.5-.282s5.617-10.132 12.481-22.294L89.64 123.34h30.332c27.113 0 30.332-.065 30.332-.611 0-.336-6.659-12.228-14.797-26.427s-14.797-25.917-14.797-26.04 2.682-4.853 5.96-10.51 6.003-10.578 6.056-10.934c.086-.586 1.375-.648 13.572-.648 7.412 0 13.463.143 13.446.317-.018.174.22.707.53 1.184.31.476 9.763 16.937 21.007 36.578 11.244 19.64 20.71 36.022 21.036 36.4.554.647 2.549.691 31.428.691h30.837l12.896 22.145c7.093 12.18 12.8 22.301 12.682 22.492-.117.19-4.776.303-10.352.249-5.575-.054-56.26-.143-112.63-.198z'/><path fill='#64b5f6' d='M23.046 238.939c-6.098 10.563-6.69 11.711-6.224 12.078.282.224 3.18 5.044 6.44 10.712s6.016 10.355 6.123 10.417c.106.061 13.585.153 29.95.204 16.367.052 29.994.23 30.285.399.473.273-1.08 3.094-14.637 26.574l-15.166 26.269 12.907 21.865c7.1 12.026 12.982 21.906 13.068 21.956s23.257-39.831 51.492-88.624c11.352-19.617 21.214-36.64 30.37-52.442 23.308-40.452 30.68-53.468 30.73-54.132-1.096-.11-6.141-.187-13.006-.216-3.945-.01-7.82-.02-12.75-.002l-25.341.092-15.42 26.706c-14.256 24.693-15.445 26.663-16.278 26.86l-.023.037c-.012.003-1.622-.001-1.826 0-4.29.062-20.453.063-40.226-.01-22.632-.082-41.615-.125-42.183-.096-.567.03-1.147-.03-1.29-.132-.141-.102-3.29 5.066-6.996 11.485zm205.16-190.3c-.123.149 5.62 10.392 12.761 22.763 12.2 21.131 89.393 155.03 96.276 167 1.503 2.613 2.92 4.803 3.443 5.348.9-1.249 3.532-5.63 7.954-13.219a1343 1343 0 0 1 10.05-17.76l6.606-11.443c.691-1.403.753-1.818.652-2.117-.161-.48-6.903-12.332-14.982-26.337-8.078-14.005-14.824-25.849-14.99-26.32a.73.73 0 0 1-.01-.366l-.426-.913 21.636-36.976c3.69-6.307 6.425-11.042 9.471-16.29 9.158-15.948 12.036-21.189 11.895-21.55-.126-.324-2.7-4.83-5.72-10.017-3.021-5.185-5.845-10.148-6.275-11.026-.483-.987-.734-1.364-1.1-1.456-.054.014-.083.018-.144.035-.42.112-5.455.195-11.19.185s-11.22.024-12.187.073l-1.76.089-14.998 25.978c-12.824 22.212-15.084 25.964-15.595 25.883-.024-.004-.15-.189-.235-.301-.109.066-.2.09-.271.05-.256-.148-7.144-11.902-15.306-26.119L279.4 48.817c-.116-.186-.444-.744-.458-.752-.476-.275-50.502.287-50.737.57zm-18.646 283.09c-.047.109-.026.262.043.48.328 1.05 25.338 43.735 25.772 43.985.206.119 14.178.239 31.05.266 26.65.044 30.749.152 31.234.832.307.43 9.987 17.214 21.513 37.296s21.152 36.627 21.394 36.767 5.926.243 12.633.23c6.705-.013 12.4.099 12.657.246.131.076.381-.141.851-.795l6.008-10.406c5.234-9.065 6.62-11.684 6.294-11.888-.575-.36-15.597-26.643-23.859-41.482-3.09-5.45-5.37-9.516-5.44-9.774-.196-.712-.066-.822 1.155-.98 1.956-.252 57.397-.057 58.071.205.237.092.79-.569 2.593-3.497 1.866-3.067 5.03-8.524 11.001-18.866 7.22-12.505 13.043-22.784 12.941-22.843s-.77-.051-1.489.016l-.046.001c-4.451.204-33.918.203-149.74.025-38.96-.06-69.786-.09-71.912-.072-1.12.01-2.095.076-2.66.172a.3.3 0 0 0-.062.083z'/></g></svg>",
"nodejs": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='M16 20.003v2h4a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2v-2h4v-2h-4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2v2Z'/><path fill='#8bc34a' d='m16 3.003-12 7v14l4 2h6v-13.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v11.5H8l-2-1.034V11.15l10-5.833 10 5.833v11.703l-10 5.833-1.745-1.022L13 29.253l3 1.75 12-7v-14Z'/></svg>",
"nodejs_alt": "<svg viewBox='0 0 32 32'><path fill='#388e3c' d='M15.354 2.831 4.647 8.861A1.25 1.25 0 0 0 4 9.953V22.03a1.26 1.26 0 0 0 .646 1.095l10.709 6.039a1.32 1.32 0 0 0 1.294 0l10.705-6.038A1.26 1.26 0 0 0 28 22.03V9.96a1.25 1.25 0 0 0-.647-1.093L16.65 2.836a1.32 1.32 0 0 0-1.294 0Z'/><path fill='#4caf50' d='M4.305 22.784a1.3 1.3 0 0 0 .381.328l9.185 5.18 1.53.862a1.3 1.3 0 0 0 .745.166 1.4 1.4 0 0 0 .254-.046L27.693 9.082a1.3 1.3 0 0 0-.294-.234L20.38 4.894l-3.705-2.082a1.3 1.3 0 0 0-.335-.13Z'/><path fill='#66bb6a' d='M27.693 22.784a1.3 1.3 0 0 1-.38.328l-9.185 5.18-1.53.862a1.3 1.3 0 0 1-.745.166 1.4 1.4 0 0 1-.254-.046L4.305 9.08a1.3 1.3 0 0 1 .295-.234l7.018-3.952 3.705-2.082a1.3 1.3 0 0 1 .335-.13Z'/></svg>",
"nodemon": "<svg viewBox='0 0 32 32'><path fill='#8bc34a' d='m27.354 8.517-10.708-6.34A1.27 1.27 0 0 0 15.999 2a1.27 1.27 0 0 0-.647.178L4.647 8.516A1.33 1.33 0 0 0 4 9.664v12.678a1.33 1.33 0 0 0 .647 1.147l10.705 6.333a1.26 1.26 0 0 0 1.293 0l10.708-6.333A1.33 1.33 0 0 0 28 22.341V9.664a1.33 1.33 0 0 0-.646-1.147M24 23.307a.5.5 0 0 1-.658.474l-5-1.667A.5.5 0 0 1 18 21.64V15.6a.5.5 0 0 0-.223-.415l-1.5-1a.5.5 0 0 0-.554 0l-1.5 1A.5.5 0 0 0 14 15.6v6.039a.5.5 0 0 1-.342.474l-5 1.667A.5.5 0 0 1 8 23.306V12.31a.5.5 0 0 1 .276-.447l1.942-.971A2 2 0 0 1 10 10V6l3.333 3.333 2.443-1.221a.5.5 0 0 1 .448 0l2.443 1.221L22 6v4a2 2 0 0 1-.218.89l1.942.972a.5.5 0 0 1 .276.447Z'/></svg>",
- "npm": "<svg viewBox='0 0 32 32'><path fill='#E53935' d='M4 4v24h24V4Zm20 20h-4V12h-4v12H8V8h16Z'/></svg>",
+ "npm": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M4 4v24h24V4Zm20 20h-4V12h-4v12H8V8h16Z'/></svg>",
"nuget": "<svg viewBox='0 0 32 32'><circle cx='5' cy='5' r='3' fill='#0288d1'/><path fill='#0288d1' d='M8 14v10a6 6 0 0 0 6 6h10a6 6 0 0 0 6-6V14a6 6 0 0 0-6-6H14a6 6 0 0 0-6 6m7 4a3 3 0 1 1 3-3 3 3 0 0 1-3 3m7 8a4 4 0 1 1 4-4 4 4 0 0 1-4 4'/></svg>",
"nunjucks": "<svg viewBox='0 0 32 32'><path fill='#388e3c' d='M12 4v12L8 4H4v24h4V18l4 10h4V4zm12 6v14h-2v-4h-4v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V10Z'/></svg>",
"nuxt": "<svg viewBox='0 0 32 32'><path fill='#00e676' d='m30.27 23-6.93-12a1.98 1.98 0 0 0-1.73-1 1.96 1.96 0 0 0-1.73 1l-2.27 3.93-3.88-6.71a1.996 1.996 0 0 0-3.46 0L1.73 23a2 2 0 0 0 1.73 3h8.915a6 6 0 0 0 5.197-3.001L21.61 16l3.46 6h-2.31l-2.31 4h8.09a2 2 0 0 0 1.73-3m-17.896-1H6.93L12 13.22l3.3 5.71-1.193 2.069A2 2 0 0 1 12.374 22'/></svg>",
@@ -859,28 +868,28 @@
"objective-cpp": "<svg viewBox='0 0 32 32'><path fill='#ffab40' d='M28 14v-4h-2v4h-6v-4h-2v4h-4v2h4v4h2v-4h6v4h2v-4h4v-2z'/><path fill='#ffab40' d='M13.563 22A5.57 5.57 0 0 1 8 16.437v-2.873A5.57 5.57 0 0 1 13.563 8H18V2h-4.437A11.563 11.563 0 0 0 2 13.563v2.873A11.564 11.564 0 0 0 13.563 28H18v-6Z'/></svg>",
"ocaml": "<svg viewBox='0 0 24 24'><path d='m12.019 15.021.003-.008c-.005-.021-.006-.026-.003.008'/><path fill='#ff9800' d='M4.51 3.273a2.523 2.523 0 0 0-2.524 2.523V11.3c.361-.13.88-.898 1.043-1.085.285-.327.337-.743.478-1.006C3.83 8.612 3.886 8.2 4.62 8.2c.342 0 .478.08.71.39.16.216.438.615.568.882.15.307.396.724.503.808q.122.095.233.137c.119.044.218-.037.297-.1.102-.082.145-.247.24-.467.135-.317.283-.697.367-.83.146-.23.195-.501.352-.633.232-.195.535-.208.618-.225.466-.092.677.225.907.43.15.133.355.403.5.765.114.283.26.544.32.707.059.158.203.41.289.713.077.275.286.486.365.616 0 0 .121.34.858.65.16.067.482.176.674.246.32.116.63.101 1.025.054.281 0 .434-.408.562-.734.075-.193.148-.745.197-.902.048-.153-.064-.27.031-.405.112-.156.178-.164.242-.368.138-.436.936-.458 1.384-.458.374 0 .327.363.96.239.364-.072.714.046 1.1.149.324.086.63.184.812.398.119.139.412.834.113.863.029.035.05.099.104.134-.067.262-.357.075-.518.041-.217-.045-.37.007-.583.101-.363.162-.894.143-1.21.407-.27.223-.269.721-.394 1 0 0-.348.895-1.106 1.443-.194.14-.574.477-1.4.605a5.3 5.3 0 0 1-1.1.043c-.186-.009-.362-.018-.549-.02-.11-.002-.48-.013-.461.022l-.041.103.024.138c.015.083.019.149.022.225.006.157-.013.32-.005.478.017.328.138.627.154.958.017.368.199.758.375 1.059.067.114.169.128.213.269.052.161.003.333.028.505.1.668.292 1.366.592 1.97l.008.014c.371-.062.743-.196 1.226-.267.885-.132 2.115-.064 2.906-.138 2-.188 3.085.82 4.882.407V5.796a2.523 2.523 0 0 0-2.523-2.523zm-.907 11.144q-.022 0-.046.003c-.159.025-.313.08-.412.24-.08.13-.108.355-.164.505-.064.175-.176.338-.274.505-.18.305-.504.581-.644.879-.028.06-.053.13-.076.2v3.402c.163.028.333.062.524.113 1.407.375 1.75.407 3.13.25l.13-.018c.105-.22.187-.968.255-1.2.054-.178.127-.32.155-.5.026-.173-.003-.337-.017-.493-.04-.393.285-.533.44-.87.14-.304.22-.651.336-.963.11-.298.284-.721.579-.872-.036-.041-.617-.06-.772-.076a5 5 0 0 1-.5-.07c-.314-.064-.656-.126-.965-.2a10 10 0 0 1-.947-.328c-.298-.138-.503-.497-.732-.507m5.737.83c-.74.149-.97.876-1.32 1.451-.192.319-.396.59-.548.928-.14.312-.128.657-.368.924a2.55 2.55 0 0 0-.528.922c-.023.067-.088.776-.158.943l1.101-.078c1.026.07.73.464 2.332.378l2.529-.078a7 7 0 0 0-.228-.588c-.07-.147-.16-.434-.218-.56a3.5 3.5 0 0 0-.309-.526c-.184-.215-.227-.23-.28-.503-.095-.473-.344-1.33-.637-1.923-.151-.306-.403-.562-.634-.784-.2-.195-.655-.522-.734-.505z'/></svg>",
"odin": "<svg stroke-linejoin='round' stroke-miterlimit='2' clip-rule='evenodd' viewBox='0 0 260 260'><path fill='#448aff' d='M73.123 212.264a99 99 0 0 1-6.865-5.18l98.605-170.789s4.059 1.506 7.412 3.113c4.226 2.026 7.726 4 7.726 4 47.796 27.596 64.198 88.805 36.602 136.6-27.595 47.798-88.804 64.198-136.6 36.603 0 0-3.347-1.924-6.879-4.346zm98.545-154.422L88.336 202.177c39.831 22.997 90.838 9.329 113.834-30.5 22.995-39.832 9.33-90.838-30.5-113.835zM47.944 187.19c-3.35-4.852-4.543-7.18-4.543-7.18-17.227-29.922-18.49-67.972 0-100s52.075-49.958 86.603-50c0 0 4.661-.011 8.543.36 5.847.56 9.735 1.315 9.735 1.315L53.986 195.011s-2.237-2.316-6.04-7.82zm72.084-139.903c-25.13 3.054-48.573 17.467-62.193 41.058-13.62 23.589-14.378 51.098-4.459 74.39z'/></svg>",
- "opa": "<svg viewBox='0 0 32 32'><path fill='#bdbdbd' d='M29.12 10.065c0-2.5-4.686-8.122-4.998-8.434 1.124 5.057.984 4.302 1.172 5.917a2.44 2.44 0 0 1-.1 1.261c-.78 1.802-3.572 2.817-3.572 2.817l4.373 3.749s3.125-3.124 3.125-5.31m-26.24 0c0-2.5 4.686-8.122 4.998-8.434-1.124 5.057-.984 4.302-1.172 5.917a2.44 2.44 0 0 0 .1 1.261c.78 1.802 3.572 2.817 3.572 2.817l-4.373 3.749S2.88 12.25 2.88 10.065'/><path fill='#546e7a' d='M16 8c4.897 0 8.452 3.007 9.996 7.375V22s-3.124 2.122-4.998 3.371A31.7 31.7 0 0 0 16.312 30H16Z'/><path fill='#78909c' d='M16 8c-4.897 0-8.452 3.007-9.996 7.375V22s3.124 2.122 4.998 3.371A31.7 31.7 0 0 1 15.688 30H16Z'/><circle cx='16' cy='16' r='2' fill='#FAFAFA'/></svg>",
+ "opa": "<svg viewBox='0 0 32 32'><path fill='#bdbdbd' d='M29.12 10.065c0-2.5-4.686-8.122-4.998-8.434 1.124 5.057.984 4.302 1.172 5.917a2.44 2.44 0 0 1-.1 1.261c-.78 1.802-3.572 2.817-3.572 2.817l4.373 3.749s3.125-3.124 3.125-5.31m-26.24 0c0-2.5 4.686-8.122 4.998-8.434-1.124 5.057-.984 4.302-1.172 5.917a2.44 2.44 0 0 0 .1 1.261c.78 1.802 3.572 2.817 3.572 2.817l-4.373 3.749S2.88 12.25 2.88 10.065'/><path fill='#546e7a' d='M16 8c4.897 0 8.452 3.007 9.996 7.375V22s-3.124 2.122-4.998 3.371A31.7 31.7 0 0 0 16.312 30H16Z'/><path fill='#78909c' d='M16 8c-4.897 0-8.452 3.007-9.996 7.375V22s3.124 2.122 4.998 3.371A31.7 31.7 0 0 1 15.688 30H16Z'/><circle cx='16' cy='16' r='2' fill='#fafafa'/></svg>",
"opam": "<svg viewBox='0 0 24 24'><path fill='#ff9800' d='M2.222 3.198v5.667c.255-.092.621-.635.736-.766.2-.231.237-.525.337-.71.228-.422.267-.712.786-.712.241 0 .337.055.5.275.114.152.31.434.402.622.106.217.279.511.355.57a.6.6 0 0 0 .164.097c.084.032.154-.026.21-.07.072-.058.103-.175.169-.33.096-.224.2-.492.26-.586.102-.162.137-.354.248-.447.164-.137.377-.147.436-.158.329-.065.478.158.64.303.106.094.25.285.354.54.08.2.182.384.225.5.041.11.143.289.204.502.055.194.202.343.258.435 0 0 .085.24.605.459.113.047.34.124.476.174.226.082.445.071.723.038.199 0 .307-.288.397-.518.053-.136.105-.526.14-.637.033-.108-.046-.191.021-.286.079-.11.126-.116.171-.26.098-.307.66-.323.977-.323.264 0 .23.256.678.169.256-.05.503.033.776.105.229.06.444.13.573.281.084.098.291.588.08.61.02.024.035.069.073.093-.047.185-.252.053-.366.03-.153-.032-.261.005-.41.07-.257.115-.632.102-.856.288-.19.157-.189.51-.277.706 0 0-.246.632-.781 1.018-.137.1-.405.337-.989.427a3.7 3.7 0 0 1-.775.031c-.132-.006-.256-.013-.388-.015-.077 0-.338-.009-.325.016l-.03.073.017.097c.011.059.014.105.016.16.004.11-.009.225-.003.337.011.231.097.442.108.676.012.26.14.535.265.747.047.081.12.09.15.19.037.114.003.235.02.357a4.7 4.7 0 0 0 .423 1.4c.263-.044.525-.138.866-.189.624-.092 1.493-.045 2.05-.097 1.413-.133 2.179.58 3.447.288V3.198zm1.141 7.866-.033.002c-.112.018-.22.056-.29.17-.057.091-.076.25-.116.356-.046.123-.124.239-.194.356-.127.216-.355.41-.454.62q-.03.067-.054.142v2.401c.115.02.235.044.37.08.993.265 1.236.287 2.21.176l.091-.012c.075-.155.132-.684.18-.847.038-.126.09-.226.11-.354.018-.121-.002-.237-.013-.348-.027-.276.202-.375.311-.613.099-.215.156-.46.237-.68.078-.21.2-.509.409-.615-.025-.03-.435-.043-.544-.054a4 4 0 0 1-.354-.05c-.221-.045-.463-.088-.68-.14a7 7 0 0 1-.67-.233c-.21-.097-.354-.35-.516-.357m4.05.586c-.523.105-.686.618-.932 1.024-.136.225-.28.416-.387.655-.1.22-.09.464-.26.652a1.8 1.8 0 0 0-.372.65c-.016.048-.062.549-.112.667l.777-.055c.724.05.515.327 1.646.266l1.785-.055a5 5 0 0 0-.161-.415c-.05-.103-.113-.306-.154-.395a2.4 2.4 0 0 0-.218-.37c-.13-.153-.16-.164-.198-.356a6.4 6.4 0 0 0-.449-1.358c-.107-.215-.285-.397-.447-.553-.142-.137-.463-.368-.518-.357m10.365-4.848v2h2v10h-10v-2h-2v4h14v-14z'/></svg>",
"openapi": "<svg viewBox='0 0 24 24'><path fill='#8bc34a' d='M10.86 4.29h-.36a9 9 0 0 0-1.11.12h-.03l-.24.05-.09.02-.15.03h-.03a9 9 0 0 0-2.12.8l-.13.07-.16.09-.11.06h-.01l-.03.03.09.15 2.63 4.36.15-.09a4 4 0 0 1 .16-.08 3.6 3.6 0 0 1 1.18-.33 4 4 0 0 1 .36-.02V4.3zM5.98 5.75 5.61 6a9 9 0 0 0-.76.63l3.74 3.74.12-.1-.01-.02zm8.47 7.4-.01.17-.01.18a3.57 3.57 0 0 1-.8 1.92l-.11.13c-.04.04-.08.1-.13.13l3.73 3.73.24-.26a9 9 0 0 0 .76-.94l.03-.03.15-.23.03-.06a8.83 8.83 0 0 0 1.37-4.39v-.18l.01-.18h-5.26zM2 13.5v.08l.01.15.02.23V14l.02.19v.02l.03.2c.06.42.15.84.27 1.25l.07.2v.01l.06.19.02.04.05.15.03.07.04.12.04.1.04.09.05.12.04.07.06.14.1.2.01.03.1.17.02.04 4.5-2.71.03-.01a3.6 3.6 0 0 1-.33-1.18zM7.78 15l-4.51 2.7.21.34.01.01.01.02.02.03.24.34h.01v.01l.11.15.02.02.12.14.02.03.11.13.05.06.1.1.05.06.02.02.07.08.03.03.12.13 3.73-3.73a4 4 0 0 1-.13-.13l-.11-.13-.1-.13-.1-.14-.1-.15zm4.93 1.22-.16.08a3.6 3.6 0 0 1-1.7.43 3.6 3.6 0 0 1-1.03-.15l-.16-.05a3 3 0 0 1-.17-.07L7.62 21l-.07.18-.07.15h.02l.01.01.14.05.17.07c.03 0 .05.02.08.03a9 9 0 0 0 1.8.43l.16.02.14.02h.04a9 9 0 0 0 .38.03h.44a9 9 0 0 0 1.47-.11h.02l.15-.04.1-.01.23-.05a9 9 0 0 0 2.15-.8l.14-.08.15-.08.1-.06h.01l.01-.01.04-.02-.1-.15-.09-.16z'/><path fill='#689f38' d='M11.21 4.3v5.27a3.7 3.7 0 0 1 .8.17l3.89-3.88a9 9 0 0 0-.44-.29h-.02l-.14-.1-.09-.04-.08-.05-.14-.07-.02-.02a9 9 0 0 0-.95-.42l-.03-.01-.21-.08A9 9 0 0 0 12 4.36l-.08-.01h-.07l-.14-.02h-.05l-.17-.02h-.06l-.15-.01zM4.6 6.87 4.47 7l-.12.13a9 9 0 0 0-.75.93l-.04.05a6 6 0 0 0-.15.23l-.03.04a8.83 8.83 0 0 0-1.37 4.4v.17l-.01.18h5.26l.01-.18.01-.17a3.57 3.57 0 0 1 .8-1.92c.03-.05.08-.09.12-.13.04-.05.07-.1.12-.14L4.6 6.88zM18.14 8.1l-3.89 3.89c.1.26.15.52.18.8h5.28v-.08l-.02-.14v-.07l-.01-.17v-.04l-.03-.2v-.01l-.03-.21a9 9 0 0 0-.27-1.24v-.01l-.06-.2-.07-.19-.01-.04a6 6 0 0 0-.08-.22l-.05-.12-.08-.2-.05-.11-.1-.21-.02-.04-.08-.17V9.1l-.1-.17v-.01a9 9 0 0 0-.5-.82zm-5.01 7.82-.13.1.01.01 2.72 4.5.37-.25a9 9 0 0 0 .76-.63l-3.72-3.71zm-4.55 0-.01.02-3.72 3.71.16.16c.02 0 .04.02.06.04l.13.1.03.03.15.12.01.02.16.12a9 9 0 0 0 .7.47l.03.02a9 9 0 0 0 .45.25l.02.01a8 8 0 0 0 .4.2l.14-.32 1.87-4.54v-.02a3.6 3.6 0 0 1-.58-.39'/><path fill='#e0e0e0' d='M19.53 2a2.46 2.46 0 0 0-1.74.72 2.47 2.47 0 0 0-.47 2.84l-5.37 5.37a2.47 2.47 0 1 0 1.12 1.12l5.37-5.37a2.47 2.47 0 0 0 2.84-3.96A2.46 2.46 0 0 0 19.53 2'/></svg>",
"openapi_light": "<svg viewBox='0 0 24 24'><path fill='#8bc34a' d='M10.86 4.29h-.36a9 9 0 0 0-1.11.12h-.03l-.24.05-.09.02-.15.03h-.03a9 9 0 0 0-2.12.8l-.13.07-.16.09-.11.06h-.01l-.03.03.09.15 2.63 4.36.15-.09a4 4 0 0 1 .16-.08 3.6 3.6 0 0 1 1.18-.33 4 4 0 0 1 .36-.02V4.3zM5.98 5.75 5.61 6a9 9 0 0 0-.76.63l3.72 3.72.02.02.12-.1-.01-.02zm8.47 7.4-.01.17-.01.18a3.57 3.57 0 0 1-.8 1.92l-.11.13c-.04.04-.08.1-.13.13l3.73 3.73.12-.13.12-.13a9 9 0 0 0 .76-.94l.03-.03.15-.23.03-.06a8.83 8.83 0 0 0 1.37-4.39v-.18l.01-.18h-5.26zM2 13.5v.08l.01.15.02.23V14l.02.19v.02l.03.2c.06.42.15.84.27 1.25l.07.2v.01l.06.19.02.04.05.15.03.07.04.12.04.1.04.09.05.12.04.07.06.14.02.04.08.16.01.03.1.17.02.04 4.5-2.71.03-.01a3.6 3.6 0 0 1-.33-1.18zM7.78 15l-4.51 2.7.21.34.01.01.01.02.02.03.24.34h.01v.01l.11.15.02.02.12.14.02.03.11.13.05.06.1.1.05.06.02.02.07.08.03.03.12.13 3.73-3.73a4 4 0 0 1-.13-.13l-.11-.13-.1-.13-.1-.14-.1-.15zm4.93 1.22-.16.08a3.6 3.6 0 0 1-1.7.43 3.6 3.6 0 0 1-1.03-.15l-.16-.05a3 3 0 0 1-.17-.07L7.62 21l-.07.18-.07.15h.02l.01.01.14.05.17.07c.03 0 .05.02.08.03a9 9 0 0 0 1.8.43l.08.01.08.01.14.02h.04a9 9 0 0 0 .38.03h.44a9 9 0 0 0 1.47-.11h.02l.15-.04.1-.01.23-.05a9 9 0 0 0 2.15-.8l.14-.08.15-.08.1-.06h.01l.01-.01.04-.02-.1-.15-.09-.16z'/><path fill='#689f38' d='M11.21 4.3v5.27a3.7 3.7 0 0 1 .8.17l3.89-3.88a9 9 0 0 0-.44-.29h-.02l-.14-.1-.09-.04-.08-.05-.14-.07-.02-.02a9 9 0 0 0-.95-.42l-.03-.01-.21-.08A9 9 0 0 0 12 4.36l-.08-.01h-.07l-.14-.02h-.05l-.17-.02h-.06l-.15-.01zM4.6 6.87 4.47 7l-.12.13a9 9 0 0 0-.75.93l-.04.05a6 6 0 0 0-.15.23l-.03.04a8.83 8.83 0 0 0-1.37 4.4v.17l-.01.18h5.26l.01-.18.01-.17a3.57 3.57 0 0 1 .8-1.92c.03-.05.08-.09.12-.13.04-.05.07-.1.12-.14L4.6 6.88zM18.14 8.1l-3.89 3.89c.1.26.15.52.18.8h5.28v-.08l-.02-.14v-.07l-.01-.17v-.04l-.03-.2v-.01l-.03-.21a9 9 0 0 0-.27-1.24v-.01l-.06-.2-.07-.19-.01-.04a6 6 0 0 0-.08-.22l-.05-.12-.04-.1-.04-.1-.05-.11-.1-.21-.02-.04-.08-.17V9.1l-.1-.17v-.01a9 9 0 0 0-.5-.82zm-5.01 7.82-.13.1.01.01 2.72 4.5.37-.25a9 9 0 0 0 .76-.63l-3.72-3.71zm-4.55 0-.01.02-3.72 3.71.06.06.1.1c.02 0 .04.02.06.04l.13.1.03.03.15.12.01.02.16.12a9 9 0 0 0 .7.47l.03.02a9 9 0 0 0 .45.25l.02.01a8 8 0 0 0 .4.2l.14-.32 1.87-4.54v-.02a3.6 3.6 0 0 1-.58-.39'/><path fill='#424242' d='M19.53 2a2.46 2.46 0 0 0-1.74.72 2.47 2.47 0 0 0-.47 2.84l-5.37 5.37a2.47 2.47 0 1 0 1.12 1.12l5.37-5.37a2.47 2.47 0 0 0 2.84-3.96A2.46 2.46 0 0 0 19.53 2' data-mit-no-recolor='true'/></svg>",
"otne": "<svg viewBox='0 0 1024 1024'><g fill='#00c853'><path d='M512 254.16A257.84 257.84 0 0 0 254.16 512 257.84 257.84 0 0 0 512 769.841a257.84 257.84 0 0 0 257.841-257.84 257.84 257.84 0 0 0-257.84-257.842zM941.74 512A429.74 429.74 0 0 1 512 941.74 429.74 429.74 0 0 1 82.262 512 429.74 429.74 0 0 1 512 82.262 429.74 429.74 0 0 1 941.74 512'/><path d='M695.945 450.836h-92.08l-.005 122.318h92.084zm-122.854-122.78H450.92v367.89h122.17zm-152.942 122.78h-92.084v122.318h92.08z'/></g></svg>",
- "packship": "<svg viewBox='0 0 16 16'><path fill='#9575CD' d='M2.997 14.967c-.344-.086-.654-.345-.765-.64-.136-.36-.157-.283 1.119-4.186s1.181-3.58 1.01-3.467c-.741.491-1.703.082-1.95-.83-.175-.648.027-1.006.899-1.587 1.274-.85 1.979-1.476 2.603-2.312.492-.659.682-.798 1.194-.873.623-.09 1.204.138 1.418.56.075.147.02.153.417-.053 4.991-2.592 6.93 4.168 2.114 7.369-1.361.904-3.966 1.598-4.84 1.29l-.128-.045-.144.439c-.08.241-.378 1.16-.663 2.042-.622 1.929-.645 1.972-1.152 2.21-.228.106-.854.153-1.132.083m.729-.772c.272-.068.224.052 1.27-3.18 1.267-3.918 1.463-4.385 2.38-5.681 2.03-2.868 4.835-3.397 4.577-.864-.218 2.145-2.177 3.72-5 4.02-.493.052-.577.117-.647.496-.1.542.18.631 1.35.431 3.043-.52 5.106-2.398 5.391-4.906.365-3.207-3.167-3.65-5.56-.698-.289.356-.294.418.059-.698.392-1.241.389-1.266-.198-1.266-.306 0-.397.047-.604.31-.072.09-.243.31-.38.486-.58.745-1.443 1.49-2.632 2.275-.652.43-.708.542-.468.922.157.247.337.32.56.226.238-.099 1.576-1.11 2.002-1.51.124-.118.226-.205.226-.193s-.7 2.17-1.555 4.795a462 462 0 0 0-1.556 4.82c0 .198.407.31.785.215m3.962-6.61c2.008-.431 3.407-1.742 3.486-3.266.031-.597-.03-.691-.436-.666-1.12.068-3.05 2.1-3.756 3.956l-.034.087.218-.022a6 6 0 0 0 .522-.088'/></svg>",
+ "packship": "<svg viewBox='0 0 16 16'><path fill='#9575cd' d='M2.997 14.967c-.344-.086-.654-.345-.765-.64-.136-.36-.157-.283 1.119-4.186s1.181-3.58 1.01-3.467c-.741.491-1.703.082-1.95-.83-.175-.648.027-1.006.899-1.587 1.274-.85 1.979-1.476 2.603-2.312.492-.659.682-.798 1.194-.873.623-.09 1.204.138 1.418.56.075.147.02.153.417-.053 4.991-2.592 6.93 4.168 2.114 7.369-1.361.904-3.966 1.598-4.84 1.29l-.128-.045-.144.439c-.08.241-.378 1.16-.663 2.042-.622 1.929-.645 1.972-1.152 2.21-.228.106-.854.153-1.132.083m.729-.772c.272-.068.224.052 1.27-3.18 1.267-3.918 1.463-4.385 2.38-5.681 2.03-2.868 4.835-3.397 4.577-.864-.218 2.145-2.177 3.72-5 4.02-.493.052-.577.117-.647.496-.1.542.18.631 1.35.431 3.043-.52 5.106-2.398 5.391-4.906.365-3.207-3.167-3.65-5.56-.698-.289.356-.294.418.059-.698.392-1.241.389-1.266-.198-1.266-.306 0-.397.047-.604.31-.072.09-.243.31-.38.486-.58.745-1.443 1.49-2.632 2.275-.652.43-.708.542-.468.922.157.247.337.32.56.226.238-.099 1.576-1.11 2.002-1.51.124-.118.226-.205.226-.193s-.7 2.17-1.555 4.795a462 462 0 0 0-1.556 4.82c0 .198.407.31.785.215m3.962-6.61c2.008-.431 3.407-1.742 3.486-3.266.031-.597-.03-.691-.436-.666-1.12.068-3.05 2.1-3.756 3.956l-.034.087.218-.022a6 6 0 0 0 .522-.088'/></svg>",
"palette": "<svg viewBox='0 0 16 16'><path fill='#4fc3f7' d='M12.278 8a1.167 1.167 0 0 1-1.167-1.167 1.167 1.167 0 0 1 1.167-1.166 1.167 1.167 0 0 1 1.166 1.166A1.167 1.167 0 0 1 12.278 8M9.944 4.889a1.167 1.167 0 0 1-1.166-1.167 1.167 1.167 0 0 1 1.166-1.166 1.167 1.167 0 0 1 1.167 1.166A1.167 1.167 0 0 1 9.944 4.89m-3.888 0a1.167 1.167 0 0 1-1.167-1.167 1.167 1.167 0 0 1 1.167-1.166 1.167 1.167 0 0 1 1.166 1.166A1.167 1.167 0 0 1 6.056 4.89M3.722 8a1.167 1.167 0 0 1-1.166-1.167 1.167 1.167 0 0 1 1.166-1.166A1.167 1.167 0 0 1 4.89 6.833 1.167 1.167 0 0 1 3.722 8M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 1.167 1.167 0 0 0 1.167-1.167c0-.303-.117-.575-.304-.777a1.2 1.2 0 0 1-.295-.778 1.167 1.167 0 0 1 1.166-1.167h1.377A3.89 3.89 0 0 0 15 7.222C15 3.784 11.866 1 8 1'/></svg>",
"panda": "<svg viewBox='0 0 24 24'><path fill='#ffd740' d='M4.524 20.862c-.258-.317-.958-2.683-1.319-4.451-1.238-6.075.1-10.397 3.824-12.354 1.596-.838 2.918-1.114 5.37-1.118 3.212-.007 5.102.617 6.808 2.244 2.52 2.403 2.735 6.732.459 9.222-1.267 1.387-4.598 2.82-6.551 2.82h-.593l-.408-1.239c-.224-.68-.456-1.502-.516-1.825l-.108-.586.656.088c.777.104 1.89-.27 2.365-.798.998-1.102.824-3.595-.302-4.333-1.063-.697-3.124-.653-4.166.089-1.888 1.345-1.382 6.248 1.172 11.343.248.495.406.944.351.999-.054.055-1.624.1-3.49.1-2.519 0-3.431-.052-3.552-.2z'/></svg>",
"parcel": "<svg viewBox='0 0 24 24'><path fill='#ffb74d' d='M2.007 10.96a.985.985 0 0 1-.37-1.37L3.137 7c.11-.2.28-.34.47-.42l7.83-4.4c.16-.12.36-.18.57-.18s.41.06.57.18l7.9 4.44c.19.1.35.26.44.46l1.45 2.52c.28.48.11 1.09-.36 1.36l-1 .58v4.96c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18s-.41-.06-.57-.18l-7.9-4.44a.99.99 0 0 1-.53-.88v-5.54c-.3.17-.68.18-1 0m10-6.81v6.7l5.96-3.35zm-7 11.76 6 3.38v-6.71l-6-3.37zm14 0v-3.22l-5 2.9c-.33.18-.7.17-1 .01v3.69zm-5.15-2.55 6.28-3.63-.58-1.01-6.28 3.63z'/></svg>",
"pascal": "<svg viewBox='0 0 24 24'><path fill='#0288d1' d='m8.863 14.765-1.158 6.597H3.937L7.191 2.637l6.559.013q3.035 0 4.77 1.685 1.737 1.685 1.518 4.398-.205 2.752-2.302 4.398-2.083 1.646-5.324 1.646zm.527-3.125 3.138.026q1.517 0 2.52-.785 1.003-.784 1.196-2.122t-.437-2.135q-.617-.797-1.839-.848l-3.55-.013z' aria-label='P'/></svg>",
"pawn": "<svg viewBox='0 0 32 32'><path fill='#ef6c00' d='M6 28h20v2H6zm8-18h4l4 14H10z'/><path fill='#ef6c00' d='M10 12h12v2H10z'/><circle cx='16' cy='7' r='4' fill='#ef6c00'/></svg>",
- "payload": "<svg viewBox='0 0 24 24'><path fill='#CFD8DC' d='m11.617 2 9.163 5.508v10.455l-6.9 3.991V11.5L4.706 6zm-.703 11.216v8.159L4 17.215z'/></svg>",
- "payload_light": "<svg viewBox='0 0 24 24'><path fill='#455A64' d='m11.617 2 9.163 5.508v10.455l-6.9 3.991V11.5L4.706 6zm-.703 11.216v8.159L4 17.215z'/></svg>",
+ "payload": "<svg viewBox='0 0 24 24'><path fill='#cfd8dc' d='m11.617 2 9.163 5.508v10.455l-6.9 3.991V11.5L4.706 6zm-.703 11.216v8.159L4 17.215z'/></svg>",
+ "payload_light": "<svg viewBox='0 0 24 24'><path fill='#455a64' d='m11.617 2 9.163 5.508v10.455l-6.9 3.991V11.5L4.706 6zm-.703 11.216v8.159L4 17.215z'/></svg>",
"pdf": "<svg viewBox='0 0 24 24'><path fill='#ef5350' d='M13 9h5.5L13 3.5zM6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m4.93 10.44c.41.9.93 1.64 1.53 2.15l.41.32c-.87.16-2.07.44-3.34.93l-.11.04.5-1.04c.45-.87.78-1.66 1.01-2.4m6.48 3.81c.18-.18.27-.41.28-.66.03-.2-.02-.39-.12-.55-.29-.47-1.04-.69-2.28-.69l-1.29.07-.87-.58c-.63-.52-1.2-1.43-1.6-2.56l.04-.14c.33-1.33.64-2.94-.02-3.6a.85.85 0 0 0-.61-.24h-.24c-.37 0-.7.39-.79.77-.37 1.33-.15 2.06.22 3.27v.01c-.25.88-.57 1.9-1.08 2.93l-.96 1.8-.89.49c-1.2.75-1.77 1.59-1.88 2.12-.04.19-.02.36.05.54l.03.05.48.31.44.11c.81 0 1.73-.95 2.97-3.07l.18-.07c1.03-.33 2.31-.56 4.03-.75 1.03.51 2.24.74 3 .74.44 0 .74-.11.91-.3m-.41-.71.09.11c-.01.1-.04.11-.09.13h-.04l-.19.02c-.46 0-1.17-.19-1.9-.51.09-.1.13-.1.23-.1 1.4 0 1.8.25 1.9.35M7.83 17c-.65 1.19-1.24 1.85-1.69 2 .05-.38.5-1.04 1.21-1.69zm3.02-6.91c-.23-.9-.24-1.63-.07-2.05l.07-.12.15.05c.17.24.19.56.09 1.1l-.03.16-.16.82z'/></svg>",
"pdm": "<svg viewBox='0 0 24 24'><path fill='#9575cd' d='m16.145 6.113 2.757 1.59a.54.54 0 0 1 .27.469v7.656a.54.54 0 0 1-.27.469l-1.675.965-.239-.137-.843-.488ZM5.973 13.25l9.101 5.254-2.804 1.621a.53.53 0 0 1-.54 0l-6.632-3.828a.54.54 0 0 1-.27-.469v-1.914l.067-.039Zm2.156-1.242 5.863-3.387v6.774Zm5.863-5.871-9.164 5.289V8.172a.54.54 0 0 1 .27-.469l6.632-3.828a.53.53 0 0 1 .54 0l1.722.996Zm-3.34-4.125a2.68 2.68 0 0 1 2.696 0L19.98 5.84a2.69 2.69 0 0 1 1.344 2.332v7.656c0 .961-.512 1.852-1.344 2.332l-6.632 3.828a2.68 2.68 0 0 1-2.696 0L4.02 18.16a2.69 2.69 0 0 1-1.344-2.332V8.172c0-.965.511-1.852 1.344-2.332Zm0 0'/></svg>",
"percy": "<svg viewBox='0 0 24 24'><path fill='#ba68c8' d='M22.847 7.51a.74.74 0 0 0-.706-.527.8.8 0 0 0-.288.051 3.2 3.2 0 0 1-1.064.21c-.914-.672-3.131-2.293-4.448-3.204 0 0 .178.531.418 1.521 0 0-2.086-1.193-3.473-2.106 0 0 .506 1.05.543 1.388 0 0-1.663-.309-4.092-.864 0 0 1.796 1.017 2.232 1.62 0 0-2.18-.182-4.747-.369 0 0 1.291.664 1.918 1.193 0 0-2.336.294-4.112.348 0 0 1.298 1.025 1.82 1.519 0 0-2.23.814-4.904 2.266 0 0 1.394.09 2.394.42 0 0-1.232 1.022-3.441 3.376 0 0 1.392-.026 2.517-.155 0 0-.902 1.31-2.038 3.672 0 0 .758-.449 1.487-.604 0 0 .03 2.674 1.533 3.128l.002-.002a.7.7 0 0 0 .205.034 1 1 0 0 0 .23-.029c.481-.124.852-.575 1.282-1.098.142-.173.288-.35.443-.523.171-.226.394-.461.66-.666.549-.423 1.305-.734 2.218-.575.99.12 1.589 1.093 2.072 1.878.37.6.662 1.074 1.08 1.126q.046.006.088.006c.641.011.88-.71 1.181-1.621l.062-.186.009.018c.224-.74.649-1.535 1.164-2.25.698-.968 1.59-1.828 2.438-2.22l.003.005c.46-.248.945-.481 1.422-.711h.001c1.483-.715 2.884-1.39 3.599-2.438.358-.524.542-1.171.548-1.924a6 6 0 0 0-.256-1.706zm-6.19 7.882c-.428.378-.848.85-1.218 1.364-.61.846-1.064 1.776-1.208 2.552.403.668.77 1 1.117 1.011h.011q.481.008.637-.606c.077-.305.1-.725.127-1.211.048-.886.11-2.027.534-3.11m-9.87 3.82-.005-.013a3.1 3.1 0 0 1 .697-.751c.442-.34 1.022-.588 1.716-.516a18 18 0 0 1-.274.647c-.342.763-.748 1.55-1.173 1.607l-.076.005c-.331-.006-.621-.326-.885-.98zm8.512-12.44-1.831-1.18L16.5 6.697zm-5.1.437 2.919.776 1.286-.389zm5.203 1.512.634-.448-2.43.352zm-4.15-.071-1.015.633-2.775-.304zm3.103 1.678 1.027-.533-3.79.33zm-2.813.564-.73 1.116-3.563.779zm-4.447.618-2.768 1.124 3.414-2.283zm-1.527 1.378-.13 1.17-1.611 1.887z' clip-rule='evenodd'/></svg>",
"perl": "<svg viewBox='0 0 24 24'><path fill='#ba68c8' d='M11.057 2.981c.537.735.028 1.653.141 2.472a3.42 3.42 0 0 1-1.03 2.415c-1.414 1.625-3.165 3.038-4.097 5.03a5.28 5.28 0 0 0 1.412 5.847c.706.735 1.54 1.342 2.472 1.738.17.805-1.088.184-1.455 0A6.7 6.7 0 0 1 4.361 16.4a5.44 5.44 0 0 1 .904-5.368c1.272-1.61 3.136-2.543 4.662-3.857.565-.55 1.003-1.3.932-2.119.156-.678-.254-1.469.212-2.09zm-.07 18.929c-.17.198-.467.325-.495.24-.042-.085.212-.127.381-.325.17-.183.127-.522.24-.522.1 0 .043.395-.14.607zm2.16 0c.17.198.453.31.495.24.028-.085-.212-.141-.395-.339-.156-.184-.113-.523-.24-.523-.085 0-.029.41.14.608zm-1.03.48c-.1 0-.071-.296-.071-.65 0-.367-.028-.663.07-.663.085 0 .057.296.057.663 0 .354.014.65-.057.65m-.495-20.765c.34.24.254 2.077.254 3.136 0 1.653.184 3.376-.805 4.916-.96 1.497-2.048 3.108-1.95 4.972.1 1.837.99 3.504 2.148 5.043.664.876-.353.509-.876.085a7.2 7.2 0 0 1-2.755-5.664c.142-1.907 1.597-3.348 2.628-4.803.805-1.13 1.186-1.879 1.215-3.645.028-1.412-.142-3.531.042-3.983.014-.043.07-.1.099-.057m.537 2.232c-.085 0-.043.396.028.72.424 2.26-.198 4.52-.749 6.682a12.77 12.77 0 0 0 .283 7.826c.607 1.568 1.71.791 2.161 1.568.34.593 1.272.198 1.978-.141 2.232-1.102 4.012-3.108 4.11-5.566.029-.494 0-.989-.07-1.497-.283-1.837-1.78-3.065-3.15-4.083-1.215-.89-2.74-1.483-3.659-2.613-.523-.65-.297-1.638-.381-2.458-.043-.452-.255-.042-.382-.268-.084-.127-.14-.17-.17-.17zm.72 3.616c.057 0 .17.071.325.226a20 20 0 0 0 2.161 1.921c1.272.961 2.43 2.091 2.967 3.504.339.875.339 1.836.226 2.74-.184 1.384-1.187 2.444-2.119 3.404-.339.354-1.06.791-1.074.678-.084-.367.763-1.172 1.159-1.695A5.93 5.93 0 0 0 16 10.962c-1.102-1.214-2.317-1.907-2.995-3.08-.14-.253-.183-.409-.113-.409z'/></svg>",
"php-cs-fixer": "<svg viewBox='0 0 24 24'><path fill='#ff7043' d='M22 2c-2.023.139-16.234 1.492-17.227 11.234a43 43 0 0 0-.234 3.135l4.313-4.308a1.5 1.5 0 0 1-.952.339 1.5 1.5 0 0 1-1.5-1.5 1.5 1.5 0 0 1 1.5-1.5 1.5 1.5 0 0 1 1.5 1.5 1.5 1.5 0 0 1-.349.963l2.476-2.474a.625.625 0 1 1 .885.884l-2.525 2.522A1.5 1.5 0 0 1 10.9 12.4a1.5 1.5 0 0 1 1.5 1.5 1.5 1.5 0 0 1-1.5 1.5 1.5 1.5 0 0 1-1.5-1.5 1.5 1.5 0 0 1 .379-.998L2.275 20.4a.937.937 0 1 0 1.327 1.325l2.232-2.229a44 44 0 0 0 4.92-.287c2.089-.213 3.791-1.033 5.18-2.207h-3.946l5.735-1.91a15.5 15.5 0 0 0 1.189-1.84h-3.17l4.162-2.078C21.543 7.191 21.929 3.025 22 2'/></svg>",
- "php": "<svg viewBox='0 0 24 24'><path fill='#1E88E5' d='M12 18.08c-6.63 0-12-2.72-12-6.08s5.37-6.08 12-6.08S24 8.64 24 12s-5.37 6.08-12 6.08m-5.19-7.95c.54 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.58 1.09q-.42.33-1.29.33h-.87l.53-2.76zm-3.5 5.55h1.44l.34-1.75h1.23c.54 0 .98-.06 1.33-.17.35-.12.67-.31.96-.58.24-.22.43-.46.58-.73.15-.26.26-.56.31-.88.16-.78.05-1.39-.33-1.82-.39-.44-.99-.65-1.82-.65H4.59zm7.25-8.33-1.28 6.58h1.42l.74-3.77h1.14c.36 0 .6.06.71.18s.13.34.07.66l-.57 2.93h1.45l.59-3.07c.13-.62.03-1.07-.27-1.36-.3-.27-.85-.4-1.65-.4h-1.27L12 7.35zM18 10.13c.55 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.57 1.09-.29.22-.72.33-1.3.33h-.85l.5-2.76zm-3.5 5.55h1.44l.34-1.75h1.22c.55 0 1-.06 1.35-.17.35-.12.65-.31.95-.58.24-.22.44-.46.58-.73.15-.26.26-.56.32-.88.15-.78.04-1.39-.34-1.82-.36-.44-.99-.65-1.82-.65h-2.75z'/></svg>",
+ "php": "<svg viewBox='0 0 24 24'><path fill='#1e88e5' d='M12 18.08c-6.63 0-12-2.72-12-6.08s5.37-6.08 12-6.08S24 8.64 24 12s-5.37 6.08-12 6.08m-5.19-7.95c.54 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.58 1.09q-.42.33-1.29.33h-.87l.53-2.76zm-3.5 5.55h1.44l.34-1.75h1.23c.54 0 .98-.06 1.33-.17.35-.12.67-.31.96-.58.24-.22.43-.46.58-.73.15-.26.26-.56.31-.88.16-.78.05-1.39-.33-1.82-.39-.44-.99-.65-1.82-.65H4.59zm7.25-8.33-1.28 6.58h1.42l.74-3.77h1.14c.36 0 .6.06.71.18s.13.34.07.66l-.57 2.93h1.45l.59-3.07c.13-.62.03-1.07-.27-1.36-.3-.27-.85-.4-1.65-.4h-1.27L12 7.35zM18 10.13c.55 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.57 1.09-.29.22-.72.33-1.3.33h-.85l.5-2.76zm-3.5 5.55h1.44l.34-1.75h1.22c.55 0 1-.06 1.35-.17.35-.12.65-.31.95-.58.24-.22.44-.46.58-.73.15-.26.26-.56.32-.88.15-.78.04-1.39-.34-1.82-.36-.44-.99-.65-1.82-.65h-2.75z'/></svg>",
"php_elephant": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M28 10a4 4 0 0 0-4-4h-6v6a6 6 0 0 1-6 6h-2v2h2v6h4v-6h8v6h4V16h2v-4a2 2 0 0 0-2-2'/><path fill='#0288d1' d='M12 4H8v2a6 6 0 0 0-6 6v6a2 2 0 0 0 2 2v2H2.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H6a2 2 0 0 0 2-2v-8h4a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4M6 14H4v-2h2Z'/></svg>",
"php_elephant_pink": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M28 10a4 4 0 0 0-4-4h-6v6a6 6 0 0 1-6 6h-2v2h2v6h4v-6h8v6h4V16h2v-4a2 2 0 0 0-2-2'/><path fill='#ec407a' d='M12 4H8v2a6 6 0 0 0-6 6v6a2 2 0 0 0 2 2v2H2.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H6a2 2 0 0 0 2-2v-8h4a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4M6 14H4v-2h2Z'/></svg>",
- "phpstan": "<svg viewBox='0 0 32 32'><path fill='#263238' d='M6.405 24.174 5.77 25.68c-.135.532.019 1.045.547 1.269.597.255 1.146-.02 1.316-.348l.218-.52z'/><path fill='#5C6BC0' d='M16.08 4.6s2.434-.1 4.47 1.499a.41.41 0 0 0 .536 0 .384.384 0 0 0 .029-.538c-.102-.124-.55-.574-.904-.593 0 0 .703-.962 2.531-.962 2.323 0 7.258 3.375 7.258 9.696 0 6.074-3.977 7.383-5.058 7.383-1.645 0-3.35-1.697-3.35-2.782a4.9 4.9 0 0 0 1.542-1.4 4.9 4.9 0 0 0 .82-1.92s.088-2.59 0-4.786a.46.46 0 0 0-.399-.374.41.41 0 0 0-.42.408c0 .245.108 1.763.027 4.486-.044 1.466-1.432 2.612-2.085 2.87q.319-.897.528-1.827c.05-.238 0-.405-.16-.472a.35.35 0 0 0-.477.203c-.106.215-3.885 12.498-9.11 12.498-4.416 0-6.961-6.3-6.961-7.745s.921-2.127 1.734-2.127 1.922.817 2.057 1.077-.582.883-.582.883l-.625-.505a.385.385 0 0 0-.489.015.363.363 0 0 0-.01.51c.16.16 4.933 4.006 4.933 4.006a.445.445 0 0 0 .568.037.376.376 0 0 0 .017-.529l-1.344-1.083 1.431-4.03s-2.197-.982-3.632.102c0 0-.757-1.261-2.26-1.261a2.5 2.5 0 0 0-1.738.665 2.54 2.54 0 0 0-.808 1.687S2 17.6 2 13.735C2 9.358 5.467 4 9.487 4a3.92 3.92 0 0 1 2.488.89l-.888.674a.407.407 0 0 0-.03.557.38.38 0 0 0 .508.052c.14-.103 1.571-1.574 4.515-1.574'/><path fill='#263238' d='m9.853 21.495 1.273-2.884q.765.143 1.543.16c1.97 0 5.76-1.602 5.76-5.94 0-4.337-3.687-6.043-5.95-6.043-3.059 0-6.045 2.304-6.045 5.85 0 3.694 2.695 5.14 2.695 5.14l-1.023 2.309zm9.907-8.49a.32.32 0 0 1-.269-.15.32.32 0 0 1-.018-.309c.018-.035.414-.849 1.244-.849s1.225.77 1.241.801a.33.33 0 0 1-.035.36.318.318 0 0 1-.531-.068c-.01-.018-.242-.452-.675-.452s-.666.48-.67.48a.32.32 0 0 1-.286.187'/><path fill='#D7CCC8' d='M12.425 16.696c2.117 0 3.833-1.728 3.833-3.86s-1.716-3.86-3.833-3.86-3.832 1.729-3.832 3.86c0 2.132 1.716 3.86 3.832 3.86'/><path fill='#263238' d='M12.425 14.828a1.985 1.985 0 0 0 1.978-1.992c0-1.1-.886-1.991-1.978-1.991a1.985 1.985 0 0 0-1.977 1.991c0 1.1.885 1.992 1.977 1.992'/></svg>",
+ "phpstan": "<svg viewBox='0 0 32 32'><path fill='#263238' d='M6.405 24.174 5.77 25.68c-.135.532.019 1.045.547 1.269.597.255 1.146-.02 1.316-.348l.218-.52z'/><path fill='#5c6bc0' d='M16.08 4.6s2.434-.1 4.47 1.499a.41.41 0 0 0 .536 0 .384.384 0 0 0 .029-.538c-.102-.124-.55-.574-.904-.593 0 0 .703-.962 2.531-.962 2.323 0 7.258 3.375 7.258 9.696 0 6.074-3.977 7.383-5.058 7.383-1.645 0-3.35-1.697-3.35-2.782a4.9 4.9 0 0 0 1.542-1.4 4.9 4.9 0 0 0 .82-1.92s.088-2.59 0-4.786a.46.46 0 0 0-.399-.374.41.41 0 0 0-.42.408c0 .245.108 1.763.027 4.486-.044 1.466-1.432 2.612-2.085 2.87q.319-.897.528-1.827c.05-.238 0-.405-.16-.472a.35.35 0 0 0-.477.203c-.106.215-3.885 12.498-9.11 12.498-4.416 0-6.961-6.3-6.961-7.745s.921-2.127 1.734-2.127 1.922.817 2.057 1.077-.582.883-.582.883l-.625-.505a.385.385 0 0 0-.489.015.363.363 0 0 0-.01.51c.16.16 4.933 4.006 4.933 4.006a.445.445 0 0 0 .568.037.376.376 0 0 0 .017-.529l-1.344-1.083 1.431-4.03s-2.197-.982-3.632.102c0 0-.757-1.261-2.26-1.261a2.5 2.5 0 0 0-1.738.665 2.54 2.54 0 0 0-.808 1.687S2 17.6 2 13.735C2 9.358 5.467 4 9.487 4a3.92 3.92 0 0 1 2.488.89l-.888.674a.407.407 0 0 0-.03.557.38.38 0 0 0 .508.052c.14-.103 1.571-1.574 4.515-1.574'/><path fill='#263238' d='m9.853 21.495 1.273-2.884q.765.143 1.543.16c1.97 0 5.76-1.602 5.76-5.94 0-4.337-3.687-6.043-5.95-6.043-3.059 0-6.045 2.304-6.045 5.85 0 3.694 2.695 5.14 2.695 5.14l-1.023 2.309zm9.907-8.49a.32.32 0 0 1-.269-.15.32.32 0 0 1-.018-.309c.018-.035.414-.849 1.244-.849s1.225.77 1.241.801a.33.33 0 0 1-.035.36.318.318 0 0 1-.531-.068c-.01-.018-.242-.452-.675-.452s-.666.48-.67.48a.32.32 0 0 1-.286.187'/><path fill='#d7ccc8' d='M12.425 16.696c2.117 0 3.833-1.728 3.833-3.86s-1.716-3.86-3.833-3.86-3.832 1.729-3.832 3.86c0 2.132 1.716 3.86 3.832 3.86'/><path fill='#263238' d='M12.425 14.828a1.985 1.985 0 0 0 1.978-1.992c0-1.1-.886-1.991-1.978-1.991a1.985 1.985 0 0 0-1.977 1.991c0 1.1.885 1.992 1.977 1.992'/></svg>",
"phpunit": "<svg viewBox='0 0 24 24'><path fill='#5c6bc0' d='M21.46 10.086c-.224.59-1.012.848-1.541.269-1.668-1.925-4.936-1.945-5.845.963-.694 2.092 1.035 4.28 4.093 4.003.671-.053 1.145.548.934 1.21l-1.521 4.157c-.386 1.023-1.376 1.574-2.547 1.191L3.384 17.631c-1.049-.37-1.624-1.513-1.28-2.54L6.419 3.298c.465-1.052 1.268-1.528 2.547-1.191l11.649 4.246c1.051.373 1.625 1.515 1.281 2.541zm-2.223 2.074c0-1.571-1.713-2.559-3.075-1.773s-1.363 2.76 0 3.546 3.075-.202 3.075-1.773'/></svg>",
"pinejs": "<svg viewBox='0 0 24 24'><path fill='#66bb6a' d='M12.038 3.115c-.134.002-.271.083-.426.24-.467.478-6.764 9.388-6.764 9.572 0 .264.458.677.75.677.154 0 .724-.24 1.268-.535.712-.386 1.012-.635 1.075-.892.254-1.04.873-1.57 1.827-1.57.73 0 1.494.723 1.6 1.517.073.542.174.66 1.259 1.463.649.48 1.244.874 1.323.874.08 0 .328-.096.553-.214.545-.284.886-.27 1.482.063.273.152.556.28.627.285.22.014 2.389-1.307 2.463-1.5.038-.1 0-.311-.083-.47-.119-.22-5.243-7.495-6.42-9.114-.195-.267-.362-.397-.534-.396'/><path fill='#388e3c' d='m8.392 13.584-3.472 1.97-1.534 2.13c-1.614 2.241-1.82 2.643-1.534 2.988.153.184 1.488.212 10.068.212 5.44 0 9.994-.039 10.118-.086.124-.048.226-.23.226-.407 0-.341-3.33-5.189-3.648-5.31-.383-.148-1.491.674-1.841 1.364-.392.773-.883 1.085-1.674 1.064-.698-.018-1.306-.55-1.574-1.378-.154-.478-.397-.725-1.457-1.485-.862-.618-1.345-.88-1.499-.811-.502.223-1.274.217-1.72-.014z'/></svg>",
"pipeline": "<svg viewBox='0 0 32 32'><path fill='#f57f17' d='M20 24h-8.1a5 5 0 0 0-.732-1.754l11.078-11.078A4.997 4.997 0 1 0 20.1 6h-8.202a5 5 0 1 0 0 2H20.1a5 5 0 0 0 .73 1.754L9.755 20.832A4.997 4.997 0 1 0 11.9 26H20v4h10V20H20ZM7 10a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3m18-6a3 3 0 1 1-3 3 3.003 3.003 0 0 1 3-3M7 28a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3m15-6h6v6h-6Z'/></svg>",
@@ -894,30 +903,31 @@
"poetry": "<svg viewBox='0 0 32 32'><path fill='#3f51b5' d='M20.137 17.834A18.52 18.52 0 0 1 6 24l5 6a25.1 25.1 0 0 0 13-8Z'/><path fill='#1976d2' d='M6 2v22a18.52 18.52 0 0 0 14.137-6.166Z'/><path fill='#29b6f6' d='m6 2 14.137 15.834A23.7 23.7 0 0 0 26 2Z'/></svg>",
"postcss": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M20 12v8h-8v-8zm2-2H10v12h12z'/><path fill='#e53935' d='M16 5.488 26.159 20H5.84zM16 2 2 22h28z'/><path fill='#e53935' d='M16 13a3 3 0 1 1-3 3 3.003 3.003 0 0 1 3-3m0-2a5 5 0 1 0 5 5 5 5 0 0 0-5-5'/><path fill='#e53935' d='M16 4A12 12 0 1 1 4 16 12.014 12.014 0 0 1 16 4m0-2a14 14 0 1 0 14 14A14 14 0 0 0 16 2'/></svg>",
"posthtml": "<svg viewBox='0 0 24 24'><g fill='#f57f17'><path d='M6.176 16.747c1.271 0 2.471-.494 3.327-1.35l6.203-5.506a2.97 2.97 0 0 1 2.118-.873c1.65 0 3 1.332 3 2.982s-1.35 2.982-3 2.982a3 3 0 0 1-2.153-.908l-.997-.883-1.35 1.183 1.129.979c.9.9 2.1 1.394 3.37 1.394 2.63 0 4.765-2.135 4.765-4.747s-2.135-4.747-4.764-4.747c-1.271 0-2.471.494-3.327 1.35l-6.203 5.506a2.97 2.97 0 0 1-2.117.873c-1.65 0-3-1.332-3-2.982s1.35-2.982 3-2.982c.794 0 1.552.308 2.152.908l1.024.892 1.323-1.183-1.129-.988a4.75 4.75 0 0 0-3.37-1.385c-2.63 0-4.765 2.126-4.765 4.738a4.74 4.74 0 0 0 4.764 4.747' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;font-variation-settings:normal;inline-size:0;isolation:auto;mix-blend-mode:normal;shape-margin:0;shape-padding:0;solid-color:#000;text-decoration-color:#000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/><path d='M17.824 6.963c-1.345 0-2.619.52-3.531 1.432l-.002.001-6.197 5.5-.006.006a2.67 2.67 0 0 1-1.912.79c-1.494 0-2.71-1.2-2.71-2.692s1.216-2.691 2.71-2.691c.718 0 1.398.275 1.947.824l.008.006 1.224 1.066 1.76-1.572-1.363-1.194H9.75a5.05 5.05 0 0 0-3.574-1.468C3.389 6.97 1.12 9.229 1.12 12c0 2.803 2.27 5.037 5.055 5.037 1.345 0 2.618-.52 3.53-1.432l6.2-5.502.006-.006a2.67 2.67 0 0 1 1.912-.789c1.493 0 2.709 1.2 2.709 2.692s-1.216 2.691-2.709 2.691c-.728 0-1.398-.274-1.947-.824l-.008-.006-1.195-1.058-1.793 1.572 1.367 1.183a5.03 5.03 0 0 0 3.576 1.479c2.787 0 5.055-2.267 5.055-5.037s-2.268-5.037-5.055-5.037zm0 .58c2.472 0 4.473 2.004 4.473 4.457s-2.001 4.457-4.473 4.457a4.45 4.45 0 0 1-3.166-1.31l-.006-.006-.887-.768.907-.795.793.701.002.002a3.3 3.3 0 0 0 2.357.992c1.807 0 3.291-1.464 3.291-3.273s-1.484-3.273-3.291-3.273c-.877 0-1.704.34-2.322.957l-.002.002-6.198 5.5-.005.006c-.8.799-1.926 1.265-3.122 1.265A4.444 4.444 0 0 1 1.703 12c0-2.453 2-4.447 4.472-4.447 1.205 0 2.32.462 3.166 1.3l.008.006.887.778-.887.793-.814-.71a3.32 3.32 0 0 0-2.36-.993c-1.806 0-3.29 1.464-3.29 3.273s1.484 3.273 3.29 3.273a3.27 3.27 0 0 0 2.323-.957l6.199-5.502.006-.005c.8-.8 1.926-1.266 3.12-1.266z' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;font-variation-settings:normal;inline-size:0;isolation:auto;mix-blend-mode:normal;shape-margin:0;shape-padding:0;solid-color:#000;text-decoration-color:#000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/></g></svg>",
- "powerpoint": "<svg viewBox='0 0 24 24'><path fill='#E64A19' d='M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5zM8 11v2h1v6H8v1h4v-1h-1v-2h2a3 3 0 0 0 3-3 3 3 0 0 0-3-3zm5 2a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-2v-2z'/></svg>",
+ "powerpoint": "<svg viewBox='0 0 24 24'><path fill='#e64a19' d='M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5zM8 11v2h1v6H8v1h4v-1h-1v-2h2a3 3 0 0 0 3-3 3 3 0 0 0-3-3zm5 2a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-2v-2z'/></svg>",
"powershell": "<svg viewBox='0 0 32 32'><path fill='#03a9f4' d='M29.07 6H7.677A1.535 1.535 0 0 0 6.24 7.113l-4.2 17.774A.852.852 0 0 0 2.93 26h21.393a1.535 1.535 0 0 0 1.436-1.113L29.96 7.112A.852.852 0 0 0 29.07 6M8.626 23.797a1.4 1.4 0 0 1-1.814-.31l-.007-.009a1.075 1.075 0 0 1 .315-1.599l9.6-6.061-6.102-5.852-.01-.01a1.068 1.068 0 0 1 .084-1.625l.037-.03a1.38 1.38 0 0 1 1.8.07l7.233 6.957a1.1 1.1 0 0 1 .236.739 1.08 1.08 0 0 1-.412.79c-.074.04-.146.119-10.951 6.935ZM24 22.94A1.135 1.135 0 0 1 22.803 24h-5.634a1.061 1.061 0 1 1 .001-2.112h5.633A1.134 1.134 0 0 1 24 22.938Z'/></svg>",
- "pre-commit": "<svg viewBox='0 0 2000 2000'><defs><clipPath id='a' clipPathUnits='userSpaceOnUse'><path d='M0 1500h1500V0H0z'/></clipPath></defs><g clip-path='url(#a)' transform='matrix(1.33333 0 0 -1.33333 0 2000)'><path fill='#FFB74D' d='M665.147 130.852 130.853 665.147c-46.863 46.862-46.863 122.842 0 169.705l534.294 534.295c46.863 46.864 122.843 46.864 169.706 0l534.294-534.294c46.863-46.863 46.863-122.843 0-169.706L834.853 130.852c-46.863-46.862-122.843-46.862-169.706 0'/><path fill='none' stroke='#212121' stroke-miterlimit='10' stroke-width='34' d='M687.774 233.226 233.225 687.775c-34.366 34.366-34.366 90.085 0 124.45l454.55 454.55c34.365 34.366 90.084 34.366 124.45 0l454.55-454.55c34.365-34.365 34.365-90.084 0-124.45l-454.55-454.55c-34.366-34.365-90.085-34.365-124.45 0z'/><path fill='#212121' d='M784.672 763.286c12.096 0 23.74.893 34.943 2.688 11.194 1.785 21.053 5.26 29.569 10.416 8.504 5.145 15.34 12.432 20.496 21.84 5.144 9.408 7.726 21.724 7.726 36.96 0 15.225-2.582 27.552-7.726 36.96-5.156 9.408-11.992 16.684-20.496 21.84-8.516 5.145-18.375 8.62-29.57 10.416-11.202 1.785-22.846 2.688-34.942 2.688h-81.985V763.286zm28.895 225.792q45.013 0 76.609-13.104c21.05-8.736 38.187-20.275 51.406-34.608 13.209-14.343 22.85-30.692 28.897-49.056 6.048-18.375 9.072-37.412 9.072-57.12 0-19.268-3.024-38.2-9.072-56.784-6.047-18.596-15.688-35.06-28.897-49.392-13.22-14.343-30.355-25.872-51.406-34.608q-31.596-13.104-76.61-13.104h-110.88V509.27H597.184v479.808z'/></g></svg>",
- "prettier": "<svg viewBox='0 0 16 16'><path fill='#F44336' d='M2 8h4v1H2zm0 6h4v1H2zm9-10h3v1h-3zM2 2h3v1H2z'/><path fill='#F9A825' d='M9 2h3v1H9zm1 4h4v1h-4zm-5 6h1v1H5zm-3-2h6v1H2z'/><path fill='#26A69A' d='M2 12h3v1H2zm7-4h5v1H9zM2 4h4v1H2zm3-2h4v1H5z'/><path fill='#BA68C8' d='M2 6h3v1H2zm7-2h2v1H9zm-1 6h4v1H8z'/></svg>",
+ "pre-commit": "<svg viewBox='0 0 2000 2000'><defs><clipPath id='a' clipPathUnits='userSpaceOnUse'><path d='M0 1500h1500V0H0z'/></clipPath></defs><g clip-path='url(#a)' transform='matrix(1.33333 0 0 -1.33333 0 2000)'><path fill='#ffb74d' d='M665.147 130.852 130.853 665.147c-46.863 46.862-46.863 122.842 0 169.705l534.294 534.295c46.863 46.864 122.843 46.864 169.706 0l534.294-534.294c46.863-46.863 46.863-122.843 0-169.706L834.853 130.852c-46.863-46.862-122.843-46.862-169.706 0'/><path fill='none' stroke='#212121' stroke-miterlimit='10' stroke-width='34' d='M687.774 233.226 233.225 687.775c-34.366 34.366-34.366 90.085 0 124.45l454.55 454.55c34.365 34.366 90.084 34.366 124.45 0l454.55-454.55c34.365-34.365 34.365-90.084 0-124.45l-454.55-454.55c-34.366-34.365-90.085-34.365-124.45 0z'/><path fill='#212121' d='M784.672 763.286c12.096 0 23.74.893 34.943 2.688 11.194 1.785 21.053 5.26 29.569 10.416 8.504 5.145 15.34 12.432 20.496 21.84 5.144 9.408 7.726 21.724 7.726 36.96 0 15.225-2.582 27.552-7.726 36.96-5.156 9.408-11.992 16.684-20.496 21.84-8.516 5.145-18.375 8.62-29.57 10.416-11.202 1.785-22.846 2.688-34.942 2.688h-81.985V763.286zm28.895 225.792q45.013 0 76.609-13.104c21.05-8.736 38.187-20.275 51.406-34.608 13.209-14.343 22.85-30.692 28.897-49.056 6.048-18.375 9.072-37.412 9.072-57.12 0-19.268-3.024-38.2-9.072-56.784-6.047-18.596-15.688-35.06-28.897-49.392-13.22-14.343-30.355-25.872-51.406-34.608q-31.596-13.104-76.61-13.104h-110.88V509.27H597.184v479.808z'/></g></svg>",
+ "prettier": "<svg viewBox='0 0 16 16'><path fill='#f44336' d='M2 8h4v1H2zm0 6h4v1H2zm9-10h3v1h-3zM2 2h3v1H2z'/><path fill='#f9a825' d='M9 2h3v1H9zm1 4h4v1h-4zm-5 6h1v1H5zm-3-2h6v1H2z'/><path fill='#26a69a' d='M2 12h3v1H2zm7-4h5v1H9zM2 4h4v1H2zm3-2h4v1H5z'/><path fill='#ba68c8' d='M2 6h3v1H2zm7-2h2v1H9zm-1 6h4v1H8z'/></svg>",
"prisma": "<svg viewBox='0 0 32 32'><path fill='#00bfa5' d='m27.777 22.617-.459-.946L18.43 3.26a2.25 2.25 0 0 0-1.914-1.256A2 2 0 0 0 16.379 2a2.23 2.23 0 0 0-1.891 1.042L4.348 19.056a2.2 2.2 0 0 0 .025 2.417l4.957 7.488A2.34 2.34 0 0 0 11.29 30a2.4 2.4 0 0 0 .655-.092l14.387-4.149a2.32 2.32 0 0 0 1.458-1.234 2.21 2.21 0 0 0-.013-1.908m-3.538.604-11.268 3.25 4.075-19.033 7.568 15.671-.376.098Z'/></svg>",
"processing": "<svg fill='none' viewBox='0 0 32 32'><path stroke='#536dfe' stroke-width='8' d='M10 26c16 0 16-20 0-20'/></svg>",
"prolog": "<svg viewBox='0 0 24 24'><path fill='#ef5350' d='M12 15.385a5.1 5.1 0 0 0 1.862 1.693L12 18.94l-1.862-1.862A5.04 5.04 0 0 0 12 15.385m4.232-4.063a1.693 1.693 0 0 0-1.693 1.693 1.693 1.693 0 0 0 1.693 1.693 1.693 1.693 0 0 0 1.693-1.693c0-.94-.762-1.693-1.693-1.693m-8.464 0a1.693 1.693 0 0 0-1.693 1.693 1.693 1.693 0 0 0 1.693 1.693 1.693 1.693 0 0 0 1.693-1.693c0-.94-.762-1.693-1.693-1.693m8.464-2.116a3.385 3.385 0 0 1 3.385 3.386 3.385 3.385 0 0 1-3.385 3.385 3.385 3.385 0 0 1-3.386-3.385 3.385 3.385 0 0 1 3.386-3.386m-8.464 0a3.385 3.385 0 0 1 3.386 3.386 3.385 3.385 0 0 1-3.386 3.385 3.385 3.385 0 0 1-3.385-3.385 3.385 3.385 0 0 1 3.385-3.386M3.74 2.69c1.49 3.132.415 5.468-.584 7.787a5.1 5.1 0 0 0-.465 2.116 5.08 5.08 0 0 0 5.078 5.078 6 6 0 0 0 .533-.042l2.506 2.505L12 21.31l1.194-1.177 2.505-2.505c.178.025.355.034.533.042a5.08 5.08 0 0 0 5.078-5.078 5.1 5.1 0 0 0-.465-2.116c-.999-2.319-2.074-4.655-.584-7.787-2.235 1.744-5.417 3.123-8.26 3.132-2.845-.008-6.027-1.388-8.261-3.132z'/></svg>",
"proto": "<svg viewBox='0 0 32 32'><path fill='#00bfa5' d='M16 27 2 19v-5l14 8z'/><path fill='#ffeb3b' d='m30 14-14 8v5l14-8z'/><path fill='#ff5722' d='M16 6 2 14v5l14-8z'/><path fill='#00e676' d='m30 19-14-8V6l14 8z'/><path fill='#03a9f4' d='M16 27 2 19v-5l14 8z'/></svg>",
- "protractor": "<svg viewBox='0 0 80 80'><defs><clipPath id='a'><path fill='#424242' d='M-2.983 69.251h69.412V2.143H-2.983z'/></clipPath></defs><g clip-path='url(#a)' transform='matrix(1.1304 0 0 -1.1304 5.714 82.137)'><g stroke-width='.1'><path fill='#e53935' d='M61.216 37.276C61.216 20.217 47.39 6.39 30.33 6.39S-.554 20.218-.554 37.276 13.27 68.161 30.33 68.161c17.059 0 30.885-13.827 30.885-30.885'/><path fill='#D32F2F' d='m46.274 52.172-10.504.096-6.142 6.142-7.21-4.789 1.245-1.243-2.92.026-9.913-16.682H6.94l2.44-2.44-2.458-4.137L29.67 6.398q.33-.007.66-.008c17.042 0 30.858 13.806 30.885 30.841L46.273 52.173'/><path fill='#f5f5f5' d='M15.114 35.02c0 8.404 6.813 15.214 15.217 15.214s15.214-6.81 15.214-15.215zm34.206-.702v1.404h4.401a23.3 23.3 0 0 1-6.353 15.342l-3.289-3.29-.992.995 3.287 3.289a23.3 23.3 0 0 1-15.34 6.352l-.002-4.4h-1.404v4.4a23.3 23.3 0 0 1-15.34-6.352l3.288-3.29-.995-.991-3.288 3.287A23.32 23.32 0 0 1 6.94 35.722h4.4V34.32H6.921v-5.151H53.74v5.15h-4.42'/></g></g></svg>",
- "pug": "<svg viewBox='0 0 128 128'><g transform='translate(-.25 -1.71)'><path fill='#FFE0B2' d='M107.4 50.9c-.2-4.4.4-8.3-1.6-11.6-4.8-8.2-16.8-13-40.8-13v.7h-.5.5v-.7c-24 0-36.6 4.8-41.4 13.1-1.9 3.4-1.7 7.2-2 11.6-.2 3.5-1.8 7.2-1.1 11.2.8 5.2 1.1 10.4 1.9 15.2.6 3.9 6 7.2 6.5 10.9 1.4 10.2 12 14.9 36 14.9v.8h-.6.7v-.8c24 0 34.2-4.7 35.5-14.9.5-3.8 5.5-7 6.1-10.9.8-4.8 1.1-10 1.9-15.2.7-4-.9-7.8-1.1-11.3'/><path fill='#FFE0B2' d='M64.6 54.5c4.3.1 7.3 2.8 10.1 5.3 3.3 2.9 8.9 4.9 11.2 7.4s5.3 5 6.4 8.9 1.4 8.9 1.4 10.2.7 1 2.7 0c4.7-2.3 9.9-8.5 9.9-8.5-.6 3.9-5.7 7.4-6.2 11.1C98.9 99.1 89 104 64.5 104h-.1.6'/><path fill='#FFE0B2' d='M80.4 46.7c.9 3.1 4.1 13.6-2.1 10.1 0 0 2.6 1.5 4.2 7.2 1.7 5.7 5.8 6.4 5.8 6.4s6.7 1.3 11.7-3c4.2-3.6 4.9-10 3.1-14.9-1.8-4.8-5-6.3-9.7-7.3-4.7-1.1-14.1-2-13 1.5'/><circle cx='92.3' cy='58.1' r='8.8'/><circle cx='90' cy='54.2' r='2.3' fill='#FAFAFA'/><path fill='#FFE0B2' d='M78.9 57.7s7.9 5.4 12.2 10.7 4.2 6.3 4.2 6.3l-3.1 1.4s-4.4-8.3-9.8-11.4c-5.5-3.1-6.1-5.7-6.1-5.7zm-14-3.2c-4.3.1-7.5 2.8-10.4 5.3-3.3 2.9-9.1 4.9-11.4 7.4s-5.4 5-6.5 8.9-1.5 8.9-1.5 10.2.2 1.4-2.7 0c-4.7-2.2-9.9-8.5-9.9-8.5.6 3.9 5.7 7.4 6.2 11.1C30.1 99.1 40 104 64.5 104h.5'/><path fill='#4E342E' d='M88.1 71.4C83.3 65.5 75.6 60 64.9 60h-.1c-10.7 0-18.4 5.5-23.2 11.4-5 6.1-4.6 8.5-4.6 14.3 0 21 7.4 15 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.7 12.3-17.3.1-5.8.4-8.4-4.6-14.5'/><path fill='#3E2723' d='M64.4 65.2s-.7 9.7-2.1 11.6l2.6-.6z'/><path fill='#3E2723' d='M65.1 65.2s.7 9.7 2.1 11.6l-2.6-.6z'/><path fill='#4E342E' d='M56.7 62.9c-1-2.3 2.6-6 8.3-6.1 5.7 0 9.3 3.7 8.3 6.1S68.7 66 65 66.1c-3.6-.1-7.3-.8-8.3-3.2'/><path d='M65 65.2c0-.4 3.4-.5 5.2-1.7 0 0-3.7 1.2-4.5.7-.8-.4-1-1.6-1-1.6s-.3 1.2-.9 1.6c-.7.4-4.9-.7-4.9-.7s5.6 1.4 5.6 1.7-.1 1.3-.1 2c0 2.5 0 8.7.4 9.2.6.9.4-6.7.4-9.2-.1-.8-.1-1.6-.2-2'/><path fill='#795548' d='M65.2 78.6c1.7 0 4.7 1.2 7.4 3.1-2.6-2.9-5.7-4.9-7.4-4.9-1.8 0-5.6 2.2-8.3 5.4 2.8-2.2 6.4-3.6 8.3-3.6'/><g fill='#3E2723'><path d='M64.5 96.3c-3.8 0-7.5-1.2-10.9-2.1-.7-.2-1.4.3-2.1.1-6.3-2-11.4-5.4-14.5-9.7v1c0 21 7.4 15.1 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.6 12.3-17.4 0-.8 0-1.6.1-2.3-2.9 4.7-8.2 8.4-14.8 10.6-.6.2-2-.3-2.6-.2-3.6 1.2-6.8 2.5-10.9 2.5'/><path d='M55 85s-2.5 7.5-.8 10.8l-2.3-1s1.7-7.6 3.1-9.8m19.8 0s2.5 7.5.8 10.8l2.3-1s-1.8-7.6-3.1-9.8'/></g><path fill='#FFE0B2' d='M48.6 46.7c-.9 3.1-4.1 13.6 2.1 10.1 0 0-2.6 1.5-4.2 7.2s-5.8 6.4-5.8 6.4-6.7 1.3-11.7-3c-4.2-3.6-4.9-10-3.1-14.9s5-6.3 9.7-7.3c4.7-1.1 14-2 13 1.5'/><path d='M64.9 76.8c2.7 0 11.1 5.8 11.2 12.9v-.4c0-7.4-6.8-13.3-11.2-13.3s-11.2 6-11.2 13.3v.4c.1-7.1 8.5-12.9 11.2-12.9'/><g fill='#3E2723'><ellipse cx='66.7' cy='61.5' rx='.8' ry='1.5' transform='rotate(-14.465 66.71 61.469)'/><ellipse cx='62.4' cy='61.5' rx='.8' ry='1.5' transform='rotate(17.235 62.372 61.463)'/></g><circle cx='37.2' cy='58.1' r='8.8'/><circle cx='39.5' cy='54.2' r='2.3' fill='#FAFAFA'/><path fill='#795548' d='M67.5 58.2c0-.1-2.3 1-2.9 1.1-.6-.1-2.9-1.2-2.9-1.1z'/><path fill='#FFE0B2' d='M50 57.7s-7.9 5.4-12.2 10.7-4.2 6.3-4.2 6.3l3.1 1.4s4.4-8.3 9.8-11.4 6.1-5.7 6.1-5.7z'/><path fill='#FFE0B2' d='M32.7 41.7S30 49.1 24 52.2c0 0 9.4-1.1 8.7-10.5m63.1 0s2.7 7.4 8.7 10.5c0 0-9.4-1.1-8.7-10.5M78.7 55.5s-5.9-6.2-13.8-6.4h.2c-8 .2-13.8 6.4-13.8 6.4 6.9-4.8 12.8-4.7 13.8-4.7-.1 0 6.7-.1 13.6 4.7m-6.9-13s-3-4.2-7-4.3h.2c-3 .1-6.9 4.3-6.9 4.3 3.4-3.3 6.9-3.2 6.9-3.2s3.3-.1 6.8 3.2M37.2 73.2s-4.7 2.3-8.1.9H29c-3-1.7-4.5-6.8-4.5-6.8s3 9 12.7 5.9m54.8 0s4.7 2.3 8.1.9c4-1.7 4.6-6.8 4.6-6.8s-3 9-12.7 5.9'/><path fill='#FFE0B2' d='M42.6 41.2c2.6-.5 6.9-.6 10.3.5 4.3 1.5.8 7 1.7 7.3s2.1-3.8 10.1-3.4c8.1.4 9 4 10.1 3.4s-1.1-10 11-7.8c0 0-12.7-3.4-12.1 5.8 0 0-7.3-5.6-17.5-.6.1 0 2.7-8.6-13.6-5.2m44.3 0c.2 0 .3.1.4.1s-.1-.1-.4-.1M39.1 28.9S28.3 42.5 26.7 47.7c-1.6 5.3-2.8 27-4.2 30.1l-5-21.4 9.2-22.3zm50.8 0s10.8 13.6 12.4 18.8c1.6 5.3 2.8 27 4.2 30.1l5-21.4-9.2-22.3z'/><path fill='#4E342E' d='M89.4 28.9s11.6 9.7 15 20.9 2 24.8 4.6 26.5c3.7 2.4 7.9-11.9 9.3-13.4 2.2-2.4 9.5-8.5 10-9.6s-14.8-17.8-21.5-21.1c-8.1-3.8-18.1-4.1-17.4-3.3'/><path fill='#3E2723' d='M99.3 34.9s13.7 17.5 13.5 39.3l5.5-11.2c-.1 0-4.9-14.3-19-28.1'/><path fill='#4E342E' d='M39.1 28.9s-11.6 9.7-15 20.9-2 24.8-4.6 26.5c-3.7 2.4-7.9-11.9-9.3-13.4C8 60.5.7 54.4.2 53.3S15 35.5 21.7 32.2c8.1-3.8 18.1-4.1 17.4-3.3'/><path fill='#3E2723' d='M29.2 34.9S15.5 52.4 15.7 74.2L10.3 63s4.8-14.3 18.9-28.1'/><path fill='#FFE0B2' d='M21.8 74.6s1 5.4 2.6 7.1.5-1.3.5-1.3-1.7-.9-1.4-7.8-1.7 2-1.7 2m85.3 0s-1 5.4-2.6 7.1-.5-1.3-.5-1.3 1.7-.9 1.4-7.8 1.7 2 1.7 2'/><g fill='#3E2723'><circle cx='54.5' cy='70.5' r='.8'/><circle cx='49.9' cy='75.3' r='.8'/><circle cx='48.4' cy='70.5' r='.8'/></g><g fill='#3E2723'><circle cx='74' cy='70.5' r='.8'/><circle cx='78.6' cy='75.3' r='.8'/><circle cx='80.1' cy='70.5' r='.8'/></g></g></svg>",
+ "protractor": "<svg viewBox='0 0 80 80'><defs><clipPath id='a'><path fill='#424242' d='M-2.983 69.251h69.412V2.143H-2.983z'/></clipPath></defs><g clip-path='url(#a)' transform='matrix(1.1304 0 0 -1.1304 5.714 82.137)'><g stroke-width='.1'><path fill='#e53935' d='M61.216 37.276C61.216 20.217 47.39 6.39 30.33 6.39S-.554 20.218-.554 37.276 13.27 68.161 30.33 68.161c17.059 0 30.885-13.827 30.885-30.885'/><path fill='#d32f2f' d='m46.274 52.172-10.504.096-6.142 6.142-7.21-4.789 1.245-1.243-2.92.026-9.913-16.682H6.94l2.44-2.44-2.458-4.137L29.67 6.398q.33-.007.66-.008c17.042 0 30.858 13.806 30.885 30.841L46.273 52.173'/><path fill='#f5f5f5' d='M15.114 35.02c0 8.404 6.813 15.214 15.217 15.214s15.214-6.81 15.214-15.215zm34.206-.702v1.404h4.401a23.3 23.3 0 0 1-6.353 15.342l-3.289-3.29-.992.995 3.287 3.289a23.3 23.3 0 0 1-15.34 6.352l-.002-4.4h-1.404v4.4a23.3 23.3 0 0 1-15.34-6.352l3.288-3.29-.995-.991-3.288 3.287A23.32 23.32 0 0 1 6.94 35.722h4.4V34.32H6.921v-5.151H53.74v5.15h-4.42'/></g></g></svg>",
+ "pug": "<svg viewBox='0 0 128 128'><g transform='translate(-.25 -1.71)'><path fill='#ffe0b2' d='M107.4 50.9c-.2-4.4.4-8.3-1.6-11.6-4.8-8.2-16.8-13-40.8-13v.7h-.5.5v-.7c-24 0-36.6 4.8-41.4 13.1-1.9 3.4-1.7 7.2-2 11.6-.2 3.5-1.8 7.2-1.1 11.2.8 5.2 1.1 10.4 1.9 15.2.6 3.9 6 7.2 6.5 10.9 1.4 10.2 12 14.9 36 14.9v.8h-.6.7v-.8c24 0 34.2-4.7 35.5-14.9.5-3.8 5.5-7 6.1-10.9.8-4.8 1.1-10 1.9-15.2.7-4-.9-7.8-1.1-11.3'/><path fill='#ffe0b2' d='M64.6 54.5c4.3.1 7.3 2.8 10.1 5.3 3.3 2.9 8.9 4.9 11.2 7.4s5.3 5 6.4 8.9 1.4 8.9 1.4 10.2.7 1 2.7 0c4.7-2.3 9.9-8.5 9.9-8.5-.6 3.9-5.7 7.4-6.2 11.1C98.9 99.1 89 104 64.5 104h-.1.6'/><path fill='#ffe0b2' d='M80.4 46.7c.9 3.1 4.1 13.6-2.1 10.1 0 0 2.6 1.5 4.2 7.2 1.7 5.7 5.8 6.4 5.8 6.4s6.7 1.3 11.7-3c4.2-3.6 4.9-10 3.1-14.9-1.8-4.8-5-6.3-9.7-7.3-4.7-1.1-14.1-2-13 1.5'/><circle cx='92.3' cy='58.1' r='8.8'/><circle cx='90' cy='54.2' r='2.3' fill='#fafafa'/><path fill='#ffe0b2' d='M78.9 57.7s7.9 5.4 12.2 10.7 4.2 6.3 4.2 6.3l-3.1 1.4s-4.4-8.3-9.8-11.4c-5.5-3.1-6.1-5.7-6.1-5.7zm-14-3.2c-4.3.1-7.5 2.8-10.4 5.3-3.3 2.9-9.1 4.9-11.4 7.4s-5.4 5-6.5 8.9-1.5 8.9-1.5 10.2.2 1.4-2.7 0c-4.7-2.2-9.9-8.5-9.9-8.5.6 3.9 5.7 7.4 6.2 11.1C30.1 99.1 40 104 64.5 104h.5'/><path fill='#4e342e' d='M88.1 71.4C83.3 65.5 75.6 60 64.9 60h-.1c-10.7 0-18.4 5.5-23.2 11.4-5 6.1-4.6 8.5-4.6 14.3 0 21 7.4 15 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.7 12.3-17.3.1-5.8.4-8.4-4.6-14.5'/><path fill='#3e2723' d='M64.4 65.2s-.7 9.7-2.1 11.6l2.6-.6z'/><path fill='#3e2723' d='M65.1 65.2s.7 9.7 2.1 11.6l-2.6-.6z'/><path fill='#4e342e' d='M56.7 62.9c-1-2.3 2.6-6 8.3-6.1 5.7 0 9.3 3.7 8.3 6.1S68.7 66 65 66.1c-3.6-.1-7.3-.8-8.3-3.2'/><path d='M65 65.2c0-.4 3.4-.5 5.2-1.7 0 0-3.7 1.2-4.5.7-.8-.4-1-1.6-1-1.6s-.3 1.2-.9 1.6c-.7.4-4.9-.7-4.9-.7s5.6 1.4 5.6 1.7-.1 1.3-.1 2c0 2.5 0 8.7.4 9.2.6.9.4-6.7.4-9.2-.1-.8-.1-1.6-.2-2'/><path fill='#795548' d='M65.2 78.6c1.7 0 4.7 1.2 7.4 3.1-2.6-2.9-5.7-4.9-7.4-4.9-1.8 0-5.6 2.2-8.3 5.4 2.8-2.2 6.4-3.6 8.3-3.6'/><g fill='#3e2723'><path d='M64.5 96.3c-3.8 0-7.5-1.2-10.9-2.1-.7-.2-1.4.3-2.1.1-6.3-2-11.4-5.4-14.5-9.7v1c0 21 7.4 15.1 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.6 12.3-17.4 0-.8 0-1.6.1-2.3-2.9 4.7-8.2 8.4-14.8 10.6-.6.2-2-.3-2.6-.2-3.6 1.2-6.8 2.5-10.9 2.5'/><path d='M55 85s-2.5 7.5-.8 10.8l-2.3-1s1.7-7.6 3.1-9.8m19.8 0s2.5 7.5.8 10.8l2.3-1s-1.8-7.6-3.1-9.8'/></g><path fill='#ffe0b2' d='M48.6 46.7c-.9 3.1-4.1 13.6 2.1 10.1 0 0-2.6 1.5-4.2 7.2s-5.8 6.4-5.8 6.4-6.7 1.3-11.7-3c-4.2-3.6-4.9-10-3.1-14.9s5-6.3 9.7-7.3c4.7-1.1 14-2 13 1.5'/><path d='M64.9 76.8c2.7 0 11.1 5.8 11.2 12.9v-.4c0-7.4-6.8-13.3-11.2-13.3s-11.2 6-11.2 13.3v.4c.1-7.1 8.5-12.9 11.2-12.9'/><g fill='#3e2723'><ellipse cx='66.7' cy='61.5' rx='.8' ry='1.5' transform='rotate(-14.465 66.71 61.469)'/><ellipse cx='62.4' cy='61.5' rx='.8' ry='1.5' transform='rotate(17.235 62.372 61.463)'/></g><circle cx='37.2' cy='58.1' r='8.8'/><circle cx='39.5' cy='54.2' r='2.3' fill='#fafafa'/><path fill='#795548' d='M67.5 58.2c0-.1-2.3 1-2.9 1.1-.6-.1-2.9-1.2-2.9-1.1z'/><path fill='#ffe0b2' d='M50 57.7s-7.9 5.4-12.2 10.7-4.2 6.3-4.2 6.3l3.1 1.4s4.4-8.3 9.8-11.4 6.1-5.7 6.1-5.7z'/><path fill='#ffe0b2' d='M32.7 41.7S30 49.1 24 52.2c0 0 9.4-1.1 8.7-10.5m63.1 0s2.7 7.4 8.7 10.5c0 0-9.4-1.1-8.7-10.5M78.7 55.5s-5.9-6.2-13.8-6.4h.2c-8 .2-13.8 6.4-13.8 6.4 6.9-4.8 12.8-4.7 13.8-4.7-.1 0 6.7-.1 13.6 4.7m-6.9-13s-3-4.2-7-4.3h.2c-3 .1-6.9 4.3-6.9 4.3 3.4-3.3 6.9-3.2 6.9-3.2s3.3-.1 6.8 3.2M37.2 73.2s-4.7 2.3-8.1.9H29c-3-1.7-4.5-6.8-4.5-6.8s3 9 12.7 5.9m54.8 0s4.7 2.3 8.1.9c4-1.7 4.6-6.8 4.6-6.8s-3 9-12.7 5.9'/><path fill='#ffe0b2' d='M42.6 41.2c2.6-.5 6.9-.6 10.3.5 4.3 1.5.8 7 1.7 7.3s2.1-3.8 10.1-3.4c8.1.4 9 4 10.1 3.4s-1.1-10 11-7.8c0 0-12.7-3.4-12.1 5.8 0 0-7.3-5.6-17.5-.6.1 0 2.7-8.6-13.6-5.2m44.3 0c.2 0 .3.1.4.1s-.1-.1-.4-.1M39.1 28.9S28.3 42.5 26.7 47.7c-1.6 5.3-2.8 27-4.2 30.1l-5-21.4 9.2-22.3zm50.8 0s10.8 13.6 12.4 18.8c1.6 5.3 2.8 27 4.2 30.1l5-21.4-9.2-22.3z'/><path fill='#4e342e' d='M89.4 28.9s11.6 9.7 15 20.9 2 24.8 4.6 26.5c3.7 2.4 7.9-11.9 9.3-13.4 2.2-2.4 9.5-8.5 10-9.6s-14.8-17.8-21.5-21.1c-8.1-3.8-18.1-4.1-17.4-3.3'/><path fill='#3e2723' d='M99.3 34.9s13.7 17.5 13.5 39.3l5.5-11.2c-.1 0-4.9-14.3-19-28.1'/><path fill='#4e342e' d='M39.1 28.9s-11.6 9.7-15 20.9-2 24.8-4.6 26.5c-3.7 2.4-7.9-11.9-9.3-13.4C8 60.5.7 54.4.2 53.3S15 35.5 21.7 32.2c8.1-3.8 18.1-4.1 17.4-3.3'/><path fill='#3e2723' d='M29.2 34.9S15.5 52.4 15.7 74.2L10.3 63s4.8-14.3 18.9-28.1'/><path fill='#ffe0b2' d='M21.8 74.6s1 5.4 2.6 7.1.5-1.3.5-1.3-1.7-.9-1.4-7.8-1.7 2-1.7 2m85.3 0s-1 5.4-2.6 7.1-.5-1.3-.5-1.3 1.7-.9 1.4-7.8 1.7 2 1.7 2'/><g fill='#3e2723'><circle cx='54.5' cy='70.5' r='.8'/><circle cx='49.9' cy='75.3' r='.8'/><circle cx='48.4' cy='70.5' r='.8'/></g><g fill='#3e2723'><circle cx='74' cy='70.5' r='.8'/><circle cx='78.6' cy='75.3' r='.8'/><circle cx='80.1' cy='70.5' r='.8'/></g></g></svg>",
"puppet": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M6 2h8v8H6zm12 10h8v8h-8zM6 22h8v8H6z'/><path fill='#fbc02d' d='m7.888 6.192 1.92-2.305 14.304 11.921-1.92 2.305z'/><path fill='#fbc02d' d='m7.888 25.808 14.303-11.92 1.921 2.304-14.303 11.92z'/></svg>",
"puppeteer": "<svg viewBox='0 0 24 24'><path fill='#00bfa5' d='M2.822 19.073q-.159 0-.159-.292l-.013-2.138q0-.16.013-.173.066-.2 3.307-2.298 3.24-2.085 3.254-2.152-.173-.239-3.413-2.35-3.148-2.033-3.148-2.232V5.233q0-.305.213-.305h1.912q.252 0 3.56 2.497 3.32 2.497 3.586 2.497.292 0 3.639-2.484t3.546-2.484h2.045q.186 0 .186.36 0 .37-.026 1.115t-.027 1.115q0 .067-.61.492-1.966 1.248-5.871 3.772-.093.066-.2.226.094.186 3.414 2.297 3.267 2.085 3.267 2.271v2.099q0 .345-.186.345h-2.045q-.332 0-3.626-2.484-3.294-2.47-3.466-2.47-.213 0-3.613 2.484-3.387 2.497-3.626 2.497z' aria-label='X'/></svg>",
"purescript": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='m31.447 12.569-6.035-6.038-1.627 1.628 5.22 5.223-5.22 5.223 1.627 1.628 6.035-6.036a1.15 1.15 0 0 0 0-1.628M8.216 13.928l-1.628-1.629L.55 18.335a1.155 1.155 0 0 0 0 1.628L6.588 26l1.628-1.628-5.223-5.222ZM10 22h12l2 2H12z'/><path fill='#42a5f5' d='M22 18H10l2-2h12zm0-6H10l-2-2h12z'/></svg>",
"python-misc": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h16a2 2 0 0 0 2-2V11Zm3 22H6v-2h12Zm0-4H6v-2h12Zm0-4H6v-2h12Zm-4-4V4l8 8Z'/><path fill='#fbc02d' d='M30.714 16H28v5h-9v7.714A1.286 1.286 0 0 0 20.286 30h6.428A1.286 1.286 0 0 0 28 28.714V26h-6v-1h8.714A1.286 1.286 0 0 0 32 23.714v-6.428A1.286 1.286 0 0 0 30.714 16M24 28h3v1h-3Z' style='isolation:isolate'/><path fill='#0288d1' d='M25.714 12h-6.428A1.286 1.286 0 0 0 18 13.286V16h6v1h-8.714A1.286 1.286 0 0 0 14 18.286v6.428A1.286 1.286 0 0 0 15.286 26H18v-6h9v-6.714A1.286 1.286 0 0 0 25.714 12M22 14h-3v-1h3Z' style='isolation:isolate'/></svg>",
- "python": "<svg viewBox='0 0 24 24'><path fill='#0288D1' d='M9.86 2A2.86 2.86 0 0 0 7 4.86v1.68h4.29c.39 0 .71.57.71.96H4.86A2.86 2.86 0 0 0 2 10.36v3.781a2.86 2.86 0 0 0 2.86 2.86h1.18v-2.68a2.85 2.85 0 0 1 2.85-2.86h5.25c1.58 0 2.86-1.271 2.86-2.851V4.86A2.86 2.86 0 0 0 14.14 2zm-.72 1.61c.4 0 .72.12.72.71s-.32.891-.72.891c-.39 0-.71-.3-.71-.89s.32-.711.71-.711'/><path fill='#fdd835' d='M17.959 7v2.68a2.85 2.85 0 0 1-2.85 2.859H9.86A2.85 2.85 0 0 0 7 15.389v3.75a2.86 2.86 0 0 0 2.86 2.86h4.28A2.86 2.86 0 0 0 17 19.14v-1.68h-4.291c-.39 0-.709-.57-.709-.96h7.14A2.86 2.86 0 0 0 22 13.64V9.86A2.86 2.86 0 0 0 19.14 7zM8.32 11.513l-.004.004.038-.004zm6.54 7.276c.39 0 .71.3.71.89a.71.71 0 0 1-.71.71c-.4 0-.72-.12-.72-.71s.32-.89.72-.89'/></svg>",
+ "python": "<svg viewBox='0 0 24 24'><path fill='#0288d1' d='M9.86 2A2.86 2.86 0 0 0 7 4.86v1.68h4.29c.39 0 .71.57.71.96H4.86A2.86 2.86 0 0 0 2 10.36v3.781a2.86 2.86 0 0 0 2.86 2.86h1.18v-2.68a2.85 2.85 0 0 1 2.85-2.86h5.25c1.58 0 2.86-1.271 2.86-2.851V4.86A2.86 2.86 0 0 0 14.14 2zm-.72 1.61c.4 0 .72.12.72.71s-.32.891-.72.891c-.39 0-.71-.3-.71-.89s.32-.711.71-.711'/><path fill='#fdd835' d='M17.959 7v2.68a2.85 2.85 0 0 1-2.85 2.859H9.86A2.85 2.85 0 0 0 7 15.389v3.75a2.86 2.86 0 0 0 2.86 2.86h4.28A2.86 2.86 0 0 0 17 19.14v-1.68h-4.291c-.39 0-.709-.57-.709-.96h7.14A2.86 2.86 0 0 0 22 13.64V9.86A2.86 2.86 0 0 0 19.14 7zM8.32 11.513l-.004.004.038-.004zm6.54 7.276c.39 0 .71.3.71.89a.71.71 0 0 1-.71.71c-.4 0-.72-.12-.72-.71s.32-.89.72-.89'/></svg>",
+ "pytorch": "<svg viewBox='0 0 32 32'><circle cx='20' cy='8' r='2' fill='#f4511e'/><path fill='#f4511e' d='M25.573 11.335a11.4 11.4 0 0 0-1.1-1.219l-2.825 2.832a9 9 0 0 1 .746.812 7.36 7.36 0 0 1 1.443 3.011 8 8 0 0 1 .164 1.23A8 8 0 0 1 8 18a5.76 5.76 0 0 1 .695-2.762 7.4 7.4 0 0 1 1.277-1.896c.18-.18 1.814-1.746 3.74-3.584L16 7.553V2l-5.057 4.873c-1.112 1.06-3.713 3.545-3.877 3.722a11.5 11.5 0 0 0-1.99 2.942A9.8 9.8 0 0 0 4 18a12 12 0 1 0 24 0 12.6 12.6 0 0 0-.254-2.074 11.26 11.26 0 0 0-2.173-4.591'/></svg>",
"qsharp": "<svg viewBox='0 0 24 24'><path fill='#fbc02d' d='M11.938 16.414c.9-1.101 1.468-2.936 1.468-4.735 0-1.963-.697-3.854-1.872-5.12-1.156-1.248-2.66-1.853-4.57-1.853s-3.413.605-4.569 1.853C1.22 7.825.523 9.716.523 11.716s.697 3.89 1.872 5.157c1.156 1.248 2.68 1.853 4.57 1.853 1.376 0 2.404-.275 3.468-.917l1.578 1.486 1.395-1.486zm-3.395-3.212-1.396 1.487 1.413 1.34c-.422.22-1.027.348-1.615.348-2.202 0-3.67-1.853-3.67-4.66 0-2.809 1.468-4.662 3.689-4.662 2.239 0 3.689 1.835 3.689 4.68 0 1.1-.202 2.092-.606 2.9z' aria-label='Q'/><path fill='#fbc02d' d='m17.589 4.728-.611 4h-1.5l-.34 2h1.5l-.32 2h-1.5l-.34 2h1.5l-.61 4h2l.61-4h1l-.61 4h2l.61-4h1.5l.34-2h-1.5l.32-2h1.5l.34-2h-1.5l.611-4h-2l-.611 4h-1l.611-4zm1.049 6h1l-.32 2h-1z'/></svg>",
"quasar": "<svg viewBox='0 0 50.843 50.843'><path fill='#1976d2' d='M29.585 25.422a4.16 4.16 0 0 1-4.16 4.159 4.16 4.16 0 0 1-4.159-4.16 4.16 4.16 0 0 1 4.16-4.159 4.16 4.16 0 0 1 4.159 4.16M43.888 14.76a21.3 21.3 0 0 0-3.267-4.272l-4.808 2.776a16 16 0 0 0-5.02-2.91c-1.642 1.664-2.946 3.523-3.886 5.545 5.352-.364 10.88 1.573 16.012 5.582l3.026-1.747a21.3 21.3 0 0 0-2.057-4.974m.001 21.32a21.3 21.3 0 0 0 2.066-4.966l-4.808-2.776c.359-1.938.356-3.904.01-5.803-2.262-.59-4.524-.79-6.745-.593 2.992 4.454 4.078 10.21 3.172 16.659l3.026 1.747a21.3 21.3 0 0 0 3.28-4.269zM25.427 46.74a21.3 21.3 0 0 0 5.333-.694v-5.552a16 16 0 0 0 5.03-2.893c-.62-2.253-1.578-4.312-2.859-6.137-2.36 4.817-6.802 8.636-12.84 11.076v3.494a21.3 21.3 0 0 0 5.336.706M6.963 36.082a21.3 21.3 0 0 0 3.267 4.272l4.809-2.777a16 16 0 0 0 5.02 2.91c1.642-1.664 2.946-3.523 3.886-5.544-5.353.364-10.88-1.573-16.012-5.583l-3.027 1.747a21.3 21.3 0 0 0 2.057 4.975m-.001-21.32a21.3 21.3 0 0 0-2.066 4.966l4.809 2.776a16 16 0 0 0-.01 5.802c2.262.59 4.524.79 6.744.593-2.991-4.453-4.078-10.209-3.171-16.658l-3.026-1.747a21.3 21.3 0 0 0-3.28 4.269zm18.462-10.66a21.3 21.3 0 0 0-5.333.694v5.552a16 16 0 0 0-5.03 2.893c.62 2.253 1.578 4.312 2.86 6.137 2.36-4.818 6.802-8.636 12.84-11.076V4.808a21.3 21.3 0 0 0-5.337-.706'/></svg>",
- "quokka": "<svg viewBox='0 0 16 16'><path fill='#FF6D00' d='M8 2v6H2v6h12V2z' paint-order='fill markers stroke'/></svg>",
+ "quokka": "<svg viewBox='0 0 16 16'><path fill='#ff6d00' d='M8 2v6H2v6h12V2z' paint-order='fill markers stroke'/></svg>",
"qwik": "<svg viewBox='0 0 24 24'><path fill='#29b6f6' d='m19.26 22.182-3.657-3.636-.056.008v-.04l-7.776-7.678 1.916-1.85-1.126-6.46-5.341 6.62c-.91.917-1.078 2.408-.423 3.508l3.337 5.534a2.8 2.8 0 0 0 2.435 1.356l1.653-.016z'/><path fill='#b388ff' d='m21.255 9.018-.734-1.356-.383-.693-.152-.272-.016.016-2.012-3.484a2.82 2.82 0 0 0-2.467-1.411l-1.765.047-5.261.016A2.82 2.82 0 0 0 6.054 3.27L2.852 9.616 8.577 2.51l7.505 8.245-1.334 1.347.799 6.451.008-.016v.016h-.016l.016.016.623.606 3.025 2.958c.128.12.336-.024.248-.175l-1.868-3.676 3.257-6.02.104-.12c.04-.048.08-.096.112-.143.638-.87.726-2.034.2-2.983z'/><path fill='#eceff1' d='M16.106 10.724 8.576 2.52l1.07 6.427-1.916 1.858 7.8 7.742-.702-6.426z'/></svg>",
"r": "<svg viewBox='0 0 24 24'><path fill='#1976d2' d='M11.956 4.05c-5.694 0-10.354 3.106-10.354 6.947 0 3.396 3.686 6.212 8.531 6.813v2.205h3.53V17.82c.88-.093 1.699-.259 2.475-.497l1.43 2.692h3.996l-2.402-4.048c1.936-1.263 3.147-3.034 3.147-4.97 0-3.841-4.659-6.947-10.354-6.947m1.584 2.712c4.349 0 7.558 1.45 7.558 4.753 0 1.77-.952 3.013-2.505 3.779a1 1 0 0 1-.228-.156c-.373-.165-.994-.352-.994-.352s3.085-.227 3.085-3.302-3.23-3.127-3.23-3.127h-7.092v7.413c-2.64-.766-4.462-2.392-4.462-4.255 0-2.63 3.52-4.753 7.868-4.753m.156 4.12h2.143s.983-.05.983.974c0 1.004-.983 1.004-.983 1.004h-2.143v-1.977m-.031 4.566h.952c.186 0 .28.052.445.207.135.103.28.3.404.476-.57.073-1.17.104-1.801.104z'/></svg>",
"racket": "<svg viewBox='0 0 511.875 511.824'><path fill='#0288d1' d='M416.17 381.6c27.189-34.614 43.405-78.256 43.405-125.685 0-112.466-91.168-203.634-203.634-203.634-24.464 0-47.92 4.319-69.65 12.228 82.671 43.366 192.023 184.854 229.88 317.097z'/><path fill='#e53935' d='M226.77 182.174c-31.766-34.221-67.34-61.4-105.015-79.425-42.569 37.324-69.453 92.102-69.453 153.162 0 51.344 19.009 98.24 50.365 134.06 27.641-83.049 79.607-163.09 124.108-207.8zm37.526 46.176c-44.085 47.512-88.014 130.681-103.913 207.415 28.498 15.172 61.02 23.783 95.561 23.783 35.508 0 68.888-9.097 97.951-25.074-16.752-77.405-48.375-148.294-89.591-206.127z'/></svg>",
"raml": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M6 18.087a2 2 0 0 0-2-2 2 2 0 0 0 2-2v-6h4v-4H6a2 2 0 0 0-2 2v8H2v4h1.5a.5.5 0 0 1 .5.5v7.5a2 2 0 0 0 2 2h4v-4H6Zm22-3.999v-8a2 2 0 0 0-2-2h-4v4h4v6a2 2 0 0 0 2 2 2 2 0 0 0-2 2v6h-4v4h4a2 2 0 0 0 2-2v-8h2v-4Z'/><rect width='4' height='4' x='10' y='14.088' fill='#42a5f5' rx='.5'/><rect width='4' height='4' x='18' y='14.088' fill='#42a5f5' rx='.5'/></svg>",
"razor": "<svg viewBox='0 0 24 24'><path fill='#42a5f5' d='M12 15c.81 0 1.5-.3 2.11-.89.59-.61.89-1.3.89-2.11s-.3-1.5-.89-2.11C13.5 9.3 12.81 9 12 9s-1.5.3-2.11.89C9.3 10.5 9 11.19 9 12s.3 1.5.89 2.11c.61.59 1.3.89 2.11.89m0-13c2.75 0 5.1 1 7.05 2.95S22 9.25 22 12v1.45c0 1-.35 1.85-1 2.55-.7.67-1.5 1-2.5 1-1.2 0-2.19-.5-2.94-1.5-1 1-2.18 1.5-3.56 1.5-1.37 0-2.55-.5-3.54-1.46C7.5 14.55 7 13.38 7 12c0-1.37.5-2.55 1.46-3.54C9.45 7.5 10.63 7 12 7c1.38 0 2.55.5 3.54 1.46C16.5 9.45 17 10.63 17 12v1.45c0 .41.16.77.46 1.08s.65.47 1.04.47c.42 0 .77-.16 1.07-.47s.43-.67.43-1.08V12c0-2.19-.77-4.07-2.35-5.65S14.19 4 12 4s-4.07.77-5.65 2.35S4 9.81 4 12s.77 4.07 2.35 5.65S9.81 20 12 20h5v2h-5c-2.75 0-5.1-1-7.05-2.95S2 14.75 2 12s1-5.1 2.95-7.05S9.25 2 12 2'/></svg>",
- "rbxmk": "<svg fill='none' viewBox='0 0 24 24'><path fill='#00C853' fill-rule='evenodd' d='m6.331 2 16.164 4.331-2.454 9.158-2.25-.603.94-3.508-.002-.001-1.35-.362-2.129-.57-1.354-.363-.364 1.356-.577 2.153-2.125-.57.94-3.509-1.354-.362-2.128-.57-1.35-.363-.94 3.507-2.118-.568-.364 1.357 2.118.568 1.35.362h.001l.94-3.509 2.128.57-.577 2.155-.363 1.354 1.354.363 2.125.57 1.355.363.94-3.507 2.13.57-.578 2.152-.363 1.355 1.353.362 2.249.603-1.514 5.65L2 18.165z' clip-rule='evenodd'/></svg>",
+ "rbxmk": "<svg fill='none' viewBox='0 0 24 24'><path fill='#00c853' fill-rule='evenodd' d='m6.331 2 16.164 4.331-2.454 9.158-2.25-.603.94-3.508-.002-.001-1.35-.362-2.129-.57-1.354-.363-.364 1.356-.577 2.153-2.125-.57.94-3.509-1.354-.362-2.128-.57-1.35-.363-.94 3.507-2.118-.568-.364 1.357 2.118.568 1.35.362h.001l.94-3.509 2.128.57-.577 2.155-.363 1.354 1.354.363 2.125.57 1.355.363.94-3.507 2.13.57-.578 2.152-.363 1.355 1.353.362 2.249.603-1.514 5.65L2 18.165z' clip-rule='evenodd'/></svg>",
"rc": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M4 28V8H2v21.5a.5.5 0 0 0 .5.5H24v-2Z'/><path fill='#0288d1' d='M29.5 2h-21a.5.5 0 0 0-.5.5v21a.5.5 0 0 0 .5.5h21a.5.5 0 0 0 .5-.5v-21a.5.5 0 0 0-.5-.5M26 8.04a3.4 3.4 0 0 0-.56-.04H21a5 5 0 0 0 0 10h4.44a3.4 3.4 0 0 0 .56-.04V22h-5a9 9 0 0 1 0-18h5Z'/></svg>",
"react": "<svg viewBox='0 0 32 32'><path fill='#00bcd4' d='M16 12c7.444 0 12 2.59 12 4s-4.556 4-12 4-12-2.59-12-4 4.556-4 12-4m0-2c-7.732 0-14 2.686-14 6s6.268 6 14 6 14-2.686 14-6-6.268-6-14-6'/><path fill='#00bcd4' d='M16 14a2 2 0 1 0 2 2 2 2 0 0 0-2-2'/><path fill='#00bcd4' d='M10.458 5.507c2.017 0 5.937 3.177 9.006 8.493 3.722 6.447 3.757 11.687 2.536 12.392a.9.9 0 0 1-.457.1c-2.017 0-5.938-3.176-9.007-8.492C8.814 11.553 8.779 6.313 10 5.608a.9.9 0 0 1 .458-.1m-.001-2A2.87 2.87 0 0 0 9 3.875C6.13 5.532 6.938 12.304 10.804 19c3.284 5.69 7.72 9.493 10.74 9.493A2.87 2.87 0 0 0 23 28.124c2.87-1.656 2.062-8.428-1.804-15.124-3.284-5.69-7.72-9.493-10.74-9.493Z'/><path fill='#00bcd4' d='M21.543 5.507a.9.9 0 0 1 .457.1c1.221.706 1.186 5.946-2.536 12.393-3.07 5.316-6.99 8.493-9.007 8.493a.9.9 0 0 1-.457-.1C8.779 25.686 8.814 20.446 12.536 14c3.07-5.316 6.99-8.493 9.007-8.493m0-2c-3.02 0-7.455 3.804-10.74 9.493C6.939 19.696 6.13 26.468 9 28.124a2.87 2.87 0 0 0 1.457.369c3.02 0 7.455-3.804 10.74-9.493C25.061 12.304 25.87 5.532 23 3.876a2.87 2.87 0 0 0-1.457-.369'/></svg>",
"react_ts": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M16 12c7.444 0 12 2.59 12 4s-4.556 4-12 4-12-2.59-12-4 4.556-4 12-4m0-2c-7.732 0-14 2.686-14 6s6.268 6 14 6 14-2.686 14-6-6.268-6-14-6'/><path fill='#0288d1' d='M16 14a2 2 0 1 0 2 2 2 2 0 0 0-2-2'/><path fill='#0288d1' d='M10.458 5.507c2.017 0 5.937 3.177 9.006 8.493 3.722 6.447 3.757 11.687 2.536 12.392a.9.9 0 0 1-.457.1c-2.017 0-5.938-3.176-9.007-8.492C8.814 11.553 8.779 6.313 10 5.608a.9.9 0 0 1 .458-.1m-.001-2A2.87 2.87 0 0 0 9 3.875C6.13 5.532 6.938 12.304 10.804 19c3.284 5.69 7.72 9.493 10.74 9.493A2.87 2.87 0 0 0 23 28.124c2.87-1.656 2.062-8.428-1.804-15.124-3.284-5.69-7.72-9.493-10.74-9.493Z'/><path fill='#0288d1' d='M21.543 5.507a.9.9 0 0 1 .457.1c1.221.706 1.186 5.946-2.536 12.393-3.07 5.316-6.99 8.493-9.007 8.493a.9.9 0 0 1-.457-.1C8.779 25.686 8.814 20.446 12.536 14c3.07-5.316 6.99-8.493 9.007-8.493m0-2c-3.02 0-7.455 3.804-10.74 9.493C6.939 19.696 6.13 26.468 9 28.124a2.87 2.87 0 0 0 1.457.369c3.02 0 7.455-3.804 10.74-9.493C25.061 12.304 25.87 5.532 23 3.876a2.87 2.87 0 0 0-1.457-.369'/></svg>",
@@ -936,25 +946,25 @@
"replit": "<svg viewBox='0 0 32 32'><path fill='#ff6d00' d='M8 4h8v8H8a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2m8 8h8a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-8zm-8 8h8v8H8a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2'/></svg>",
"rescript-interface": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M16 21.862A2.14 2.14 0 0 1 13.862 24h-1.724A2.14 2.14 0 0 1 10 21.862V10.138A2.14 2.14 0 0 1 12.138 8H16ZM21 14a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/><path fill='#ef5350' d='M24 4a4.005 4.005 0 0 1 4 4v16a4.005 4.005 0 0 1-4 4H8a4.005 4.005 0 0 1-4-4V8a4.005 4.005 0 0 1 4-4zm0-2H8a6 6 0 0 0-6 6v16a6 6 0 0 0 6 6h16a6 6 0 0 0 6-6V8a6 6 0 0 0-6-6'/></svg>",
"rescript": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M24 2H8a6 6 0 0 0-6 6v16a6 6 0 0 0 6 6h16a6 6 0 0 0 6-6V8a6 6 0 0 0-6-6m-8 19.862A2.14 2.14 0 0 1 13.862 24h-1.724A2.14 2.14 0 0 1 10 21.862V10.138A2.14 2.14 0 0 1 12.138 8H16ZM21 14a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/></svg>",
- "restql": "<svg viewBox='0 0 300 300'><path fill='#64FFDA' d='M266.377 94.665c-1.327-8.361-6.357-15.836-13.806-20.138L164.349 23.59c-4.303-2.483-9.203-3.795-14.169-3.795s-9.865 1.312-14.166 3.793L47.793 74.524c-8.742 5.05-14.171 14.452-14.171 24.542v101.866c0 10.09 5.429 19.493 14.168 24.54l88.221 50.935c4.305 2.485 9.205 3.796 14.17 3.796s9.863-1.311 14.166-3.795l88.22-50.933c7.453-4.306 12.485-11.78 13.81-20.142zm-22.465 115.817-88.219 50.931a11.02 11.02 0 0 1-11.028 0l-88.219-50.931a11.03 11.03 0 0 1-5.513-9.55V99.066a11.03 11.03 0 0 1 5.513-9.55l88.219-50.934c1.705-.986 3.61-1.477 5.514-1.477s3.808.491 5.514 1.477l88.219 50.934a11.03 11.03 0 0 1 5.513 9.55v101.866a11.03 11.03 0 0 1-5.513 9.55'/><path fill='#1A237E' d='M249.429 99.066a11.03 11.03 0 0 0-5.513-9.55l-88.219-50.934c-1.705-.986-3.61-1.477-5.514-1.477s-3.809.491-5.514 1.477L56.45 89.516a11.03 11.03 0 0 0-5.513 9.55v101.866a11.03 11.03 0 0 0 5.513 9.55l88.219 50.931a11.02 11.02 0 0 0 11.028 0l88.219-50.931a11.03 11.03 0 0 0 5.513-9.55zm-63.612 43.093q.245-.14.497-.203c.06-.016.12-.013.178-.024.104-.017.207-.04.31-.04l.02.002c.028.001.052.006.08.008q.19.011.372.054.067.019.136.041.156.051.304.13.058.026.114.058c.128.077.25.166.358.27q.013.018.028.033.143.146.256.32.035.06.068.122.08.146.135.312.024.066.043.134c.038.157.067.319.067.491v22.048l-4.459 2.575a2.1 2.1 0 0 0-.922 1.129 2.1 2.1 0 0 0-.134.699v5.148l-19.094 11.023-.014.006q-.238.133-.481.198c-.061.017-.123.015-.184.025-.1.016-.204.04-.306.04l-.019-.002-.08-.008a2 2 0 0 1-.371-.056q-.069-.018-.136-.041a2 2 0 0 1-.304-.128c-.04-.02-.077-.037-.114-.06a2 2 0 0 1-.358-.269c-.012-.01-.02-.026-.031-.037a2 2 0 0 1-.252-.315c-.026-.04-.046-.082-.07-.123a2 2 0 0 1-.132-.311 2 2 0 0 1-.045-.138 2 2 0 0 1-.066-.487v-27.278a1.97 1.97 0 0 1 .988-1.707zm-36.622-80.724a1.97 1.97 0 0 1 1.975 0l62.217 35.923c1.316.759 1.316 2.658 0 3.417l-23.62 13.639a1.98 1.98 0 0 1-1.976 0l-21.07-12.165 2.35-1.354c1.407-.814 1.407-2.845 0-3.657l-17.834-10.296c-.325-.19-.69-.282-1.054-.282s-.728.093-1.054.282l-4.459 2.574-19.094-11.023c-1.316-.76-1.316-2.657 0-3.418zm-37.61 22.02c.342 0 .682.089.988.264l21.069 12.163-2.35 1.356c-1.406.812-1.406 2.843 0 3.657l2.35 1.354-21.067 12.165a1.98 1.98 0 0 1-1.975 0l-23.621-13.64c-1.317-.758-1.317-2.657 0-3.416l23.62-13.639c.307-.175.647-.264.987-.264zM98.583 209.04a2 2 0 0 1-.984-.267l-23.62-13.637a1.97 1.97 0 0 1-.988-1.708v-71.843c0-1.153.944-1.977 1.975-1.977.33 0 .667.084.985.268l23.621 13.636c.611.354.988 1.004.988 1.71v24.328l-2.35-1.356c-.34-.197-.7-.285-1.05-.285a2.11 2.11 0 0 0-2.114 2.113v20.592c0 .755.4 1.452 1.054 1.829l4.458 2.576v22.047c.002 1.15-.944 1.974-1.975 1.974m40.572 20.306a1.975 1.975 0 0 1-2.96 1.71l-23.62-13.637a1.98 1.98 0 0 1-.988-1.71v-24.327l2.348 1.355c.34.197.7.287 1.051.287 1.103 0 2.114-.882 2.114-2.116v-2.71l21.068 12.163c.61.353.987 1.003.987 1.71zm0-44.57c0 .172-.027.334-.066.489q-.02.07-.044.137-.053.165-.134.312c-.024.04-.044.083-.068.122q-.114.174-.256.32c-.011.01-.02.024-.029.034a2 2 0 0 1-.359.27c-.035.022-.075.039-.111.06a2.2 2.2 0 0 1-.441.168q-.185.047-.378.056c-.025 0-.05.008-.075.008l-.02.001c-.105 0-.211-.024-.317-.04-.059-.01-.115-.008-.17-.024a2 2 0 0 1-.498-.203l-19.09-11.023v-5.147a2.1 2.1 0 0 0-1.056-1.827l-4.456-2.573v-22.043c0-.173.027-.335.067-.491q.02-.069.043-.136a2 2 0 0 1 .135-.312c.023-.04.043-.084.07-.124q.11-.172.253-.318c.011-.01.02-.024.029-.034q.163-.155.358-.27.056-.032.114-.059a2 2 0 0 1 .818-.226c.025 0 .048-.007.074-.007l.02-.001c.107 0 .214.025.322.04.054.01.11.008.166.024q.252.063.498.203l23.614 13.633a1.98 1.98 0 0 1 .988 1.712zm-13.58-61.717a1.97 1.97 0 0 1-.74-.748 2 2 0 0 1 0-1.922 1.96 1.96 0 0 1 .74-.748l19.095-11.023 4.457 2.573a2.12 2.12 0 0 0 2.11 0l4.458-2.575 19.095 11.025c1.317.759 1.317 2.658 0 3.418l-23.62 13.637a1.97 1.97 0 0 1-1.975 0zm101.8 70.367a1.97 1.97 0 0 1-.987 1.709l-62.217 35.921a1.95 1.95 0 0 1-.985.267c-1.03 0-1.976-.825-1.976-1.977V202.07c0-.706.377-1.356.989-1.71l21.066-12.161v2.71c0 1.232 1.011 2.115 2.113 2.115.353 0 .711-.09 1.053-.286l17.832-10.297a2.11 2.11 0 0 0 1.055-1.828v-5.147l19.095-11.024c.318-.184.655-.267.985-.267 1.03 0 1.976.824 1.976 1.976v27.275zm0-44.565c0 .706-.375 1.356-.987 1.708l-21.068 12.164v-2.71c0-1.233-1.012-2.114-2.114-2.114a2.07 2.07 0 0 0-1.052.285l-2.349 1.355v-24.327c0-.706.377-1.356.99-1.71l23.62-13.636a1.96 1.96 0 0 1 .984-.268c1.031 0 1.976.824 1.976 1.977z'/><path fill='#64FFDA' d='m138.166 200.367-21.069-12.163v2.711c0 1.233-1.01 2.115-2.113 2.115-.353 0-.712-.09-1.052-.287l-2.347-1.354v24.327c0 .706.375 1.356.987 1.71l23.62 13.637c.318.184.656.267.986.267a1.975 1.975 0 0 0 1.975-1.977v-27.276a1.98 1.98 0 0 0-.987-1.71m23.617-44.247a2 2 0 0 1 .412-.316l23.62-13.637-23.62 13.637a2 2 0 0 0-.412.316m-48.219-14.226-.02.001c.112-.001.226.02.34.04-.107-.017-.214-.041-.32-.041m22.624 44.599-19.09-11.031zm.985.259.02-.002c-.111.002-.226-.021-.337-.04.106.017.212.042.317.042m49.632-44.86c-.102 0-.205.025-.31.04.11-.02.22-.04.329-.038zm38.589-22.284c-.328 0-.665.084-.985.268l-23.62 13.636a1.98 1.98 0 0 0-.989 1.71v24.327l2.35-1.355c.34-.197.7-.287 1.05-.285 1.103 0 2.115.88 2.115 2.113v2.711l21.068-12.164c.61-.352.987-1.002.987-1.708v-27.276a1.975 1.975 0 0 0-1.976-1.977m-62.212 67.144c.101 0 .204-.025.306-.04-.108.018-.216.038-.323.038zm62.212-22.58a2 2 0 0 0-.985.267l-19.094 11.024v5.147a2.11 2.11 0 0 1-1.056 1.829l-17.832 10.296c-.342.196-.7.286-1.053.286a2.113 2.113 0 0 1-2.112-2.115v-2.71l-21.067 12.162a1.98 1.98 0 0 0-.989 1.71v27.276c0 1.152.945 1.976 1.976 1.976.33 0 .667-.084.985-.267l62.217-35.921c.61-.352.988-1.004.988-1.709v-27.276c-.002-1.149-.947-1.975-1.978-1.975M125.57 75.074c-1.315.76-1.315 2.658 0 3.418l19.095 11.023 4.458-2.575c.327-.189.691-.281 1.055-.281s.729.092 1.054.281l17.833 10.297c1.408.812 1.408 2.843 0 3.656l-2.349 1.355 21.07 12.164a1.98 1.98 0 0 0 1.975 0l23.62-13.639c1.316-.759 1.316-2.657 0-3.416l-62.216-35.923a1.97 1.97 0 0 0-1.975 0zM86.976 97.358c-1.317.759-1.317 2.658 0 3.417l23.621 13.639a1.98 1.98 0 0 0 1.975 0l21.067-12.165-2.35-1.354c-1.406-.814-1.406-2.845 0-3.657l2.35-1.356L112.57 83.72a1.98 1.98 0 0 0-1.974 0zm13.58 109.702v-22.048l-4.459-2.576a2.11 2.11 0 0 1-1.054-1.828v-20.592a2.11 2.11 0 0 1 2.114-2.114c.352 0 .711.088 1.051.286l2.35 1.356v-24.329c0-.706-.376-1.356-.988-1.71l-23.621-13.634a1.95 1.95 0 0 0-.985-.268 1.975 1.975 0 0 0-1.975 1.976v71.842c0 .704.375 1.356.987 1.708l23.62 13.638c.319.183.656.266.985.266 1.03.001 1.975-.823 1.975-1.974zm88.221-41.147-4.458 2.575zv-22.047zm-.245-22.983'/><path fill='#F44336' d='M161.783 156.12a1.97 1.97 0 0 0-.577 1.391v27.277l.001.002c0 .172.028.332.066.487q.02.068.045.138.053.162.133.311c.023.04.043.083.07.123q.11.172.25.315c.013.012.021.027.032.037q.164.156.358.27.055.031.114.059a2 2 0 0 0 .81.224l.081.009c.108 0 .215-.022.324-.039.061-.009.122-.008.183-.024q.244-.064.482-.199l.014-.005 19.094-11.023v-5.148c0-.245.054-.477.133-.7a2.1 2.1 0 0 1 .923-1.129l4.458-2.574v-22.048c0-.173-.028-.334-.066-.49q-.02-.068-.044-.136a1.7 1.7 0 0 0-.135-.311q-.032-.063-.068-.122a2 2 0 0 0-.255-.32l-.029-.033a2 2 0 0 0-.358-.27q-.055-.032-.114-.059a2 2 0 0 0-.44-.17 2 2 0 0 0-.37-.054l-.08-.009c-.11 0-.22.02-.33.039-.058.01-.118.008-.178.024a2 2 0 0 0-.497.203l-23.62 13.637a2 2 0 0 0-.41.317m-23.619-.326L114.55 142.16a2 2 0 0 0-.498-.202c-.056-.017-.112-.014-.166-.024-.114-.02-.228-.04-.341-.039-.026 0-.05.007-.075.007q-.195.011-.378.056c-.047.012-.089.027-.135.04a2 2 0 0 0-.305.13q-.058.027-.114.058a2 2 0 0 0-.358.27l-.028.035a2 2 0 0 0-.255.318q-.036.061-.069.124-.08.15-.135.311a2 2 0 0 0-.11.627v22.044l4.456 2.573a2.1 2.1 0 0 1 1.056 1.827v5.147l19.09 11.023q.245.139.498.203c.056.016.112.013.17.023.112.019.226.04.337.04.026 0 .05-.007.075-.009q.195-.01.378-.056c.047-.01.089-.025.135-.039q.157-.052.306-.13c.037-.02.076-.036.112-.058a2 2 0 0 0 .359-.27l.028-.034q.143-.146.256-.32l.068-.122q.08-.148.135-.312a2 2 0 0 0 .11-.627v-27.268a1.98 1.98 0 0 0-.988-1.712m13.002-19.098 23.62-13.637c1.317-.76 1.317-2.66 0-3.418l-19.094-11.025-4.459 2.575a2.12 2.12 0 0 1-2.11 0l-4.457-2.573-19.095 11.023a1.96 1.96 0 0 0-.74.748 2 2 0 0 0 0 1.922c.165.296.413.558.74.748l23.622 13.637a1.97 1.97 0 0 0 1.973 0'/></svg>",
+ "restql": "<svg viewBox='0 0 300 300'><path fill='#64ffda' d='M266.377 94.665c-1.327-8.361-6.357-15.836-13.806-20.138L164.349 23.59c-4.303-2.483-9.203-3.795-14.169-3.795s-9.865 1.312-14.166 3.793L47.793 74.524c-8.742 5.05-14.171 14.452-14.171 24.542v101.866c0 10.09 5.429 19.493 14.168 24.54l88.221 50.935c4.305 2.485 9.205 3.796 14.17 3.796s9.863-1.311 14.166-3.795l88.22-50.933c7.453-4.306 12.485-11.78 13.81-20.142zm-22.465 115.817-88.219 50.931a11.02 11.02 0 0 1-11.028 0l-88.219-50.931a11.03 11.03 0 0 1-5.513-9.55V99.066a11.03 11.03 0 0 1 5.513-9.55l88.219-50.934c1.705-.986 3.61-1.477 5.514-1.477s3.808.491 5.514 1.477l88.219 50.934a11.03 11.03 0 0 1 5.513 9.55v101.866a11.03 11.03 0 0 1-5.513 9.55'/><path fill='#1a237e' d='M249.429 99.066a11.03 11.03 0 0 0-5.513-9.55l-88.219-50.934c-1.705-.986-3.61-1.477-5.514-1.477s-3.809.491-5.514 1.477L56.45 89.516a11.03 11.03 0 0 0-5.513 9.55v101.866a11.03 11.03 0 0 0 5.513 9.55l88.219 50.931a11.02 11.02 0 0 0 11.028 0l88.219-50.931a11.03 11.03 0 0 0 5.513-9.55zm-63.612 43.093q.245-.14.497-.203c.06-.016.12-.013.178-.024.104-.017.207-.04.31-.04l.02.002c.028.001.052.006.08.008q.19.011.372.054.067.019.136.041.156.051.304.13.058.026.114.058c.128.077.25.166.358.27q.013.018.028.033.143.146.256.32.035.06.068.122.08.146.135.312.024.066.043.134c.038.157.067.319.067.491v22.048l-4.459 2.575a2.1 2.1 0 0 0-.922 1.129 2.1 2.1 0 0 0-.134.699v5.148l-19.094 11.023-.014.006q-.238.133-.481.198c-.061.017-.123.015-.184.025-.1.016-.204.04-.306.04l-.019-.002-.08-.008a2 2 0 0 1-.371-.056q-.069-.018-.136-.041a2 2 0 0 1-.304-.128c-.04-.02-.077-.037-.114-.06a2 2 0 0 1-.358-.269c-.012-.01-.02-.026-.031-.037a2 2 0 0 1-.252-.315c-.026-.04-.046-.082-.07-.123a2 2 0 0 1-.132-.311 2 2 0 0 1-.045-.138 2 2 0 0 1-.066-.487v-27.278a1.97 1.97 0 0 1 .988-1.707zm-36.622-80.724a1.97 1.97 0 0 1 1.975 0l62.217 35.923c1.316.759 1.316 2.658 0 3.417l-23.62 13.639a1.98 1.98 0 0 1-1.976 0l-21.07-12.165 2.35-1.354c1.407-.814 1.407-2.845 0-3.657l-17.834-10.296c-.325-.19-.69-.282-1.054-.282s-.728.093-1.054.282l-4.459 2.574-19.094-11.023c-1.316-.76-1.316-2.657 0-3.418zm-37.61 22.02c.342 0 .682.089.988.264l21.069 12.163-2.35 1.356c-1.406.812-1.406 2.843 0 3.657l2.35 1.354-21.067 12.165a1.98 1.98 0 0 1-1.975 0l-23.621-13.64c-1.317-.758-1.317-2.657 0-3.416l23.62-13.639c.307-.175.647-.264.987-.264zM98.583 209.04a2 2 0 0 1-.984-.267l-23.62-13.637a1.97 1.97 0 0 1-.988-1.708v-71.843c0-1.153.944-1.977 1.975-1.977.33 0 .667.084.985.268l23.621 13.636c.611.354.988 1.004.988 1.71v24.328l-2.35-1.356c-.34-.197-.7-.285-1.05-.285a2.11 2.11 0 0 0-2.114 2.113v20.592c0 .755.4 1.452 1.054 1.829l4.458 2.576v22.047c.002 1.15-.944 1.974-1.975 1.974m40.572 20.306a1.975 1.975 0 0 1-2.96 1.71l-23.62-13.637a1.98 1.98 0 0 1-.988-1.71v-24.327l2.348 1.355c.34.197.7.287 1.051.287 1.103 0 2.114-.882 2.114-2.116v-2.71l21.068 12.163c.61.353.987 1.003.987 1.71zm0-44.57c0 .172-.027.334-.066.489q-.02.07-.044.137-.053.165-.134.312c-.024.04-.044.083-.068.122q-.114.174-.256.32c-.011.01-.02.024-.029.034a2 2 0 0 1-.359.27c-.035.022-.075.039-.111.06a2.2 2.2 0 0 1-.441.168q-.185.047-.378.056c-.025 0-.05.008-.075.008l-.02.001c-.105 0-.211-.024-.317-.04-.059-.01-.115-.008-.17-.024a2 2 0 0 1-.498-.203l-19.09-11.023v-5.147a2.1 2.1 0 0 0-1.056-1.827l-4.456-2.573v-22.043c0-.173.027-.335.067-.491q.02-.069.043-.136a2 2 0 0 1 .135-.312c.023-.04.043-.084.07-.124q.11-.172.253-.318c.011-.01.02-.024.029-.034q.163-.155.358-.27.056-.032.114-.059a2 2 0 0 1 .818-.226c.025 0 .048-.007.074-.007l.02-.001c.107 0 .214.025.322.04.054.01.11.008.166.024q.252.063.498.203l23.614 13.633a1.98 1.98 0 0 1 .988 1.712zm-13.58-61.717a1.97 1.97 0 0 1-.74-.748 2 2 0 0 1 0-1.922 1.96 1.96 0 0 1 .74-.748l19.095-11.023 4.457 2.573a2.12 2.12 0 0 0 2.11 0l4.458-2.575 19.095 11.025c1.317.759 1.317 2.658 0 3.418l-23.62 13.637a1.97 1.97 0 0 1-1.975 0zm101.8 70.367a1.97 1.97 0 0 1-.987 1.709l-62.217 35.921a1.95 1.95 0 0 1-.985.267c-1.03 0-1.976-.825-1.976-1.977V202.07c0-.706.377-1.356.989-1.71l21.066-12.161v2.71c0 1.232 1.011 2.115 2.113 2.115.353 0 .711-.09 1.053-.286l17.832-10.297a2.11 2.11 0 0 0 1.055-1.828v-5.147l19.095-11.024c.318-.184.655-.267.985-.267 1.03 0 1.976.824 1.976 1.976v27.275zm0-44.565c0 .706-.375 1.356-.987 1.708l-21.068 12.164v-2.71c0-1.233-1.012-2.114-2.114-2.114a2.07 2.07 0 0 0-1.052.285l-2.349 1.355v-24.327c0-.706.377-1.356.99-1.71l23.62-13.636a1.96 1.96 0 0 1 .984-.268c1.031 0 1.976.824 1.976 1.977z'/><path fill='#64ffda' d='m138.166 200.367-21.069-12.163v2.711c0 1.233-1.01 2.115-2.113 2.115-.353 0-.712-.09-1.052-.287l-2.347-1.354v24.327c0 .706.375 1.356.987 1.71l23.62 13.637c.318.184.656.267.986.267a1.975 1.975 0 0 0 1.975-1.977v-27.276a1.98 1.98 0 0 0-.987-1.71m23.617-44.247a2 2 0 0 1 .412-.316l23.62-13.637-23.62 13.637a2 2 0 0 0-.412.316m-48.219-14.226-.02.001c.112-.001.226.02.34.04-.107-.017-.214-.041-.32-.041m22.624 44.599-19.09-11.031zm.985.259.02-.002c-.111.002-.226-.021-.337-.04.106.017.212.042.317.042m49.632-44.86c-.102 0-.205.025-.31.04.11-.02.22-.04.329-.038zm38.589-22.284c-.328 0-.665.084-.985.268l-23.62 13.636a1.98 1.98 0 0 0-.989 1.71v24.327l2.35-1.355c.34-.197.7-.287 1.05-.285 1.103 0 2.115.88 2.115 2.113v2.711l21.068-12.164c.61-.352.987-1.002.987-1.708v-27.276a1.975 1.975 0 0 0-1.976-1.977m-62.212 67.144c.101 0 .204-.025.306-.04-.108.018-.216.038-.323.038zm62.212-22.58a2 2 0 0 0-.985.267l-19.094 11.024v5.147a2.11 2.11 0 0 1-1.056 1.829l-17.832 10.296c-.342.196-.7.286-1.053.286a2.113 2.113 0 0 1-2.112-2.115v-2.71l-21.067 12.162a1.98 1.98 0 0 0-.989 1.71v27.276c0 1.152.945 1.976 1.976 1.976.33 0 .667-.084.985-.267l62.217-35.921c.61-.352.988-1.004.988-1.709v-27.276c-.002-1.149-.947-1.975-1.978-1.975M125.57 75.074c-1.315.76-1.315 2.658 0 3.418l19.095 11.023 4.458-2.575c.327-.189.691-.281 1.055-.281s.729.092 1.054.281l17.833 10.297c1.408.812 1.408 2.843 0 3.656l-2.349 1.355 21.07 12.164a1.98 1.98 0 0 0 1.975 0l23.62-13.639c1.316-.759 1.316-2.657 0-3.416l-62.216-35.923a1.97 1.97 0 0 0-1.975 0zM86.976 97.358c-1.317.759-1.317 2.658 0 3.417l23.621 13.639a1.98 1.98 0 0 0 1.975 0l21.067-12.165-2.35-1.354c-1.406-.814-1.406-2.845 0-3.657l2.35-1.356L112.57 83.72a1.98 1.98 0 0 0-1.974 0zm13.58 109.702v-22.048l-4.459-2.576a2.11 2.11 0 0 1-1.054-1.828v-20.592a2.11 2.11 0 0 1 2.114-2.114c.352 0 .711.088 1.051.286l2.35 1.356v-24.329c0-.706-.376-1.356-.988-1.71l-23.621-13.634a1.95 1.95 0 0 0-.985-.268 1.975 1.975 0 0 0-1.975 1.976v71.842c0 .704.375 1.356.987 1.708l23.62 13.638c.319.183.656.266.985.266 1.03.001 1.975-.823 1.975-1.974zm88.221-41.147-4.458 2.575zv-22.047zm-.245-22.983'/><path fill='#f44336' d='M161.783 156.12a1.97 1.97 0 0 0-.577 1.391v27.277l.001.002c0 .172.028.332.066.487q.02.068.045.138.053.162.133.311c.023.04.043.083.07.123q.11.172.25.315c.013.012.021.027.032.037q.164.156.358.27.055.031.114.059a2 2 0 0 0 .81.224l.081.009c.108 0 .215-.022.324-.039.061-.009.122-.008.183-.024q.244-.064.482-.199l.014-.005 19.094-11.023v-5.148c0-.245.054-.477.133-.7a2.1 2.1 0 0 1 .923-1.129l4.458-2.574v-22.048c0-.173-.028-.334-.066-.49q-.02-.068-.044-.136a1.7 1.7 0 0 0-.135-.311q-.032-.063-.068-.122a2 2 0 0 0-.255-.32l-.029-.033a2 2 0 0 0-.358-.27q-.055-.032-.114-.059a2 2 0 0 0-.44-.17 2 2 0 0 0-.37-.054l-.08-.009c-.11 0-.22.02-.33.039-.058.01-.118.008-.178.024a2 2 0 0 0-.497.203l-23.62 13.637a2 2 0 0 0-.41.317m-23.619-.326L114.55 142.16a2 2 0 0 0-.498-.202c-.056-.017-.112-.014-.166-.024-.114-.02-.228-.04-.341-.039-.026 0-.05.007-.075.007q-.195.011-.378.056c-.047.012-.089.027-.135.04a2 2 0 0 0-.305.13q-.058.027-.114.058a2 2 0 0 0-.358.27l-.028.035a2 2 0 0 0-.255.318q-.036.061-.069.124-.08.15-.135.311a2 2 0 0 0-.11.627v22.044l4.456 2.573a2.1 2.1 0 0 1 1.056 1.827v5.147l19.09 11.023q.245.139.498.203c.056.016.112.013.17.023.112.019.226.04.337.04.026 0 .05-.007.075-.009q.195-.01.378-.056c.047-.01.089-.025.135-.039q.157-.052.306-.13c.037-.02.076-.036.112-.058a2 2 0 0 0 .359-.27l.028-.034q.143-.146.256-.32l.068-.122q.08-.148.135-.312a2 2 0 0 0 .11-.627v-27.268a1.98 1.98 0 0 0-.988-1.712m13.002-19.098 23.62-13.637c1.317-.76 1.317-2.66 0-3.418l-19.094-11.025-4.459 2.575a2.12 2.12 0 0 1-2.11 0l-4.457-2.573-19.095 11.023a1.96 1.96 0 0 0-.74.748 2 2 0 0 0 0 1.922c.165.296.413.558.74.748l23.622 13.637a1.97 1.97 0 0 0 1.973 0'/></svg>",
"riot": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M20 4H4.5a.5.5 0 0 0-.5.5V22a6 6 0 0 0 6 6V10.5a.5.5 0 0 1 .5-.5H20a2 2 0 0 1 2 2v1.5a.5.5 0 0 0 .5.5h5a.5.5 0 0 0 .5-.5V12a8 8 0 0 0-8-8'/><path fill='#e53935' d='M24 16H14a6 6 0 0 0 6 6h1a1 1 0 0 1 1 1v5h2a4 4 0 0 0 4-4v-4a4 4 0 0 0-4-4'/></svg>",
"roadmap": "<svg viewBox='0 0 24 24'><path fill='#26a69a' d='M2 2h2v18h18v2H2zm5 8h10v3H7zm4 5h10v3H11zM6 4h16v4h-2V6H8v2H6z'/></svg>",
"roblox": "<svg viewBox='0 0 500 500'><path fill='#42a5f5' d='m127.87 38.084 334.05 89.432-36.055 135.03-199.37-53.377-10.251 38.177-134.68-36.056zm244.26 423.83L38.08 372.482l36.056-135.03 199.01 53.377 10.251-38.176 135.03 36.055z' clip-rule='evenodd'/></svg>",
"robot": "<svg viewBox='0 0 32 32'><path fill='#00bfa5' d='M25.172 6 28 8.828v14.344L25.172 26H6.828L4 23.172V8.828L6.828 6zM26 4H6L2 8v16l4 4h20l4-4V8z'/><path fill='#00bfa5' d='M8 20h16v2H8zm0-6v2h2v-2a2 2 0 0 1 2-2 2 2 0 0 1 2 2v2h2v-2a4 4 0 0 0-4-4 4 4 0 0 0-4 4m9.876.268 5.196-3 1 1.732-5.196 3z'/></svg>",
"robots": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='M28.586 18H28a8 8 0 0 0-8-8h-2V8.445a4 4 0 1 0-4 0V10h-2a8 8 0 0 0-8 8h-.586A1.414 1.414 0 0 0 2 19.414v3.172A1.414 1.414 0 0 0 3.414 24H4v1a3 3 0 0 0 3 3h18a3 3 0 0 0 3-3v-1h.586A1.414 1.414 0 0 0 30 22.586v-3.172A1.414 1.414 0 0 0 28.586 18M11 22a3 3 0 1 1 3-3 3 3 0 0 1-3 3m10 0a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/></svg>",
"rocket": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M12 26c-1.71 1.905-7.64 2.149-7.93 1.937l-.004.003-.002-.005-.005-.002.004-.003C3.85 27.64 4.1 21.715 6 20ZM29.64 7.908c-.655 2.69-1.681 5.64-5.64 10.092l-.789 4.749a4 4 0 0 1-1.116 2.171l-4.863 4.868A.72.72 0 0 1 16 29.277v-6.23L9 16H2.723a.721.721 0 0 1-.511-1.232L7.08 9.905A4 4 0 0 1 9.25 8.79L14 8c4.453-3.959 7.402-4.985 10.092-5.64a11.1 11.1 0 0 1 3.642-.329 3.1 3.1 0 0 1 1.72.515 3.1 3.1 0 0 1 .515 1.72 11.1 11.1 0 0 1-.33 3.642ZM26 10a4 4 0 1 0-4 4 4 4 0 0 0 4-4'/></svg>",
- "rojo": "<svg fill='none' viewBox='0 0 24 24'><path fill='#E53935' d='M9.586 2h.077l-.077.135v.29c.598-.116.908-.27.908-.425l.425.058A36 36 0 0 1 12.869 2h.077c.927 0 2.009.502 3.284 1.526.27.444.424.83.502 1.197v.695c-.155.966-.425 1.7-.773 2.183q-.319.26-1.39 1.738h-.078v-.135l.194-.29h-.116a17.6 17.6 0 0 1-5.601 3.226 6.2 6.2 0 0 1-1.545.212c.656.772 1.873 1.796 3.63 3.07.174.097 1.295.812 3.361 2.183 1.719 1.062 3.766 2.221 6.161 3.496v.077h-.29c-.463-.174-.695-.309-.695-.367l-.135.077h-.135c-.058 0-.078-.019-.078-.077-.173.039-.309.213-.424.502.29.078.424.174.424.27-.115.097-.193.155-.27.155l-.348-.077v.077c0 .078.058.135.194.135v.213h-.058c-.097 0-1.16-.618-3.148-1.835-1.41-.715-3.149-1.815-5.176-3.283-.676-.31-1.912-1.217-3.728-2.723h-.193c-.27.405-.618 1.41-1.062 3.013-.097.367-.56.907-1.39 1.602h-.155l-.135-.212v-.077c0-.097.077-.31.212-.618v-.077h-.077c0 .135-.29.347-.83.637H3v-.135c1.642-4.867 2.781-8.035 3.438-9.522q1.68-3.852 1.68-4.403c-.251 0-.811.309-1.68.965h-.135l.058-.135v-.135c-.252 0-.831.406-1.739 1.255h-.077v-.077l.908-1.043.154-.212v-.077h-.154l-.83.695c-.097 0-.136-.039-.136-.116A11.1 11.1 0 0 1 7.693 3.12c1.178-.309 1.758-.656 1.758-1.062zm6.373 1.95v.078c0 .077.058.135.135.135v-.077c0-.077-.038-.135-.135-.135M7.77 10.673h.058c.85-.174 1.275-.31 1.275-.425-.058-.097-.077-.155-.077-.213C10.803 9.436 12 8.837 12.599 8.22c.058 0 .425-.386 1.12-1.198h.077V7.1c-.096.154-.154.27-.154.347h.077c.599-.463.908-.965.908-1.525v-.425c-.136 0-.213-.058-.213-.155q.348-.057.348-.347 0-.348-1.39-.696c-.754-.193-1.565-.27-2.453-.27-.445 0-.889.811-1.333 2.453-.193.193-.792 1.583-1.816 4.19m.83-5.949V4.8c.078 0 .136-.077.213-.212v-.077c-.058 0-.135.077-.212.212m.078 1.333h.077c.136-.058.213-.193.213-.425-.077 0-.174.135-.29.425m6.586.27v.155c.058 0 .135-.078.193-.213v-.077H15.4c-.096.02-.135.058-.135.135m-7.57 4.693v.078h.134a.5.5 0 0 1 .213-.078v.078c1.14-.213 1.815-.445 2.047-.696h-.077c-1.024.232-1.796.444-2.318.618m-.271.696h.058l1.274-.27-.077-.136c-.83.097-1.255.251-1.255.406m2.78 4.345v.135c.735.56 1.179.85 1.334.85-.174-.135-.27-.251-.27-.348zm2.048 1.545c.039.174.174.27.406.27h.077v-.077c-.116 0-.251-.058-.425-.193zm4.539 2.51q.55.495 1.68.986l.077-.135c-.58-.348-1.12-.638-1.603-.85zm3.862 1.121h.213c.077.02.135.058.135.135-.097 0-.135.078-.135.213h-.155l.078-.136v-.077h-.136z'/></svg>",
+ "rojo": "<svg fill='none' viewBox='0 0 24 24'><path fill='#e53935' d='M9.586 2h.077l-.077.135v.29c.598-.116.908-.27.908-.425l.425.058A36 36 0 0 1 12.869 2h.077c.927 0 2.009.502 3.284 1.526.27.444.424.83.502 1.197v.695c-.155.966-.425 1.7-.773 2.183q-.319.26-1.39 1.738h-.078v-.135l.194-.29h-.116a17.6 17.6 0 0 1-5.601 3.226 6.2 6.2 0 0 1-1.545.212c.656.772 1.873 1.796 3.63 3.07.174.097 1.295.812 3.361 2.183 1.719 1.062 3.766 2.221 6.161 3.496v.077h-.29c-.463-.174-.695-.309-.695-.367l-.135.077h-.135c-.058 0-.078-.019-.078-.077-.173.039-.309.213-.424.502.29.078.424.174.424.27-.115.097-.193.155-.27.155l-.348-.077v.077c0 .078.058.135.194.135v.213h-.058c-.097 0-1.16-.618-3.148-1.835-1.41-.715-3.149-1.815-5.176-3.283-.676-.31-1.912-1.217-3.728-2.723h-.193c-.27.405-.618 1.41-1.062 3.013-.097.367-.56.907-1.39 1.602h-.155l-.135-.212v-.077c0-.097.077-.31.212-.618v-.077h-.077c0 .135-.29.347-.83.637H3v-.135c1.642-4.867 2.781-8.035 3.438-9.522q1.68-3.852 1.68-4.403c-.251 0-.811.309-1.68.965h-.135l.058-.135v-.135c-.252 0-.831.406-1.739 1.255h-.077v-.077l.908-1.043.154-.212v-.077h-.154l-.83.695c-.097 0-.136-.039-.136-.116A11.1 11.1 0 0 1 7.693 3.12c1.178-.309 1.758-.656 1.758-1.062zm6.373 1.95v.078c0 .077.058.135.135.135v-.077c0-.077-.038-.135-.135-.135M7.77 10.673h.058c.85-.174 1.275-.31 1.275-.425-.058-.097-.077-.155-.077-.213C10.803 9.436 12 8.837 12.599 8.22c.058 0 .425-.386 1.12-1.198h.077V7.1c-.096.154-.154.27-.154.347h.077c.599-.463.908-.965.908-1.525v-.425c-.136 0-.213-.058-.213-.155q.348-.057.348-.347 0-.348-1.39-.696c-.754-.193-1.565-.27-2.453-.27-.445 0-.889.811-1.333 2.453-.193.193-.792 1.583-1.816 4.19m.83-5.949V4.8c.078 0 .136-.077.213-.212v-.077c-.058 0-.135.077-.212.212m.078 1.333h.077c.136-.058.213-.193.213-.425-.077 0-.174.135-.29.425m6.586.27v.155c.058 0 .135-.078.193-.213v-.077H15.4c-.096.02-.135.058-.135.135m-7.57 4.693v.078h.134a.5.5 0 0 1 .213-.078v.078c1.14-.213 1.815-.445 2.047-.696h-.077c-1.024.232-1.796.444-2.318.618m-.271.696h.058l1.274-.27-.077-.136c-.83.097-1.255.251-1.255.406m2.78 4.345v.135c.735.56 1.179.85 1.334.85-.174-.135-.27-.251-.27-.348zm2.048 1.545c.039.174.174.27.406.27h.077v-.077c-.116 0-.251-.058-.425-.193zm4.539 2.51q.55.495 1.68.986l.077-.135c-.58-.348-1.12-.638-1.603-.85zm3.862 1.121h.213c.077.02.135.058.135.135-.097 0-.135.078-.135.213h-.155l.078-.136v-.077h-.136z'/></svg>",
"rollup": "<svg viewBox='100 100 800 800'><path fill='#f44336' d='M733.79 394.71c0 77.407-42.308 144.79-104.67 180.51-3.76 3.134-5.954 8.148-3.76 12.849l106.87 211.22c2.82 6.581-1.568 14.103-8.776 14.103h-408.35l2.194-1.254c15.356-8.774 121.91-219.06 225.95-318.72 104.05-99.658 117.21-66.439 59.857-174.87 0 0 44.188 86.182 6.581 92.763-29.459 5.328-97.15-60.17-72.08-119.09 25.071-57.664 123.79-46.695 169.23.314 17.236 30.085 26.952 64.872 26.952 102.17m-385.47 140.71c-41.367 76.154-67.692 131.62-82.108 170.48v-509.57c0-5.328 4.388-9.715 9.715-9.715h252.91c73.333 1.253 137.58 40.114 173.62 98.718-26.325-32.906-67.692-51.71-108.43-51.71-77.407 0-96.837 28.206-245.7 301.79z'/></svg>",
"rome": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M9.875 5.409A14.02 14.02 0 0 0 2.01 17.48a.51.51 0 0 0 .508.519H5.52a.495.495 0 0 0 .491-.481 10.01 10.01 0 0 1 5.273-8.337.494.494 0 0 0 .243-.592l-.958-2.882a.505.505 0 0 0-.694-.3Zm11.556.299-.958 2.882a.494.494 0 0 0 .243.592 10.01 10.01 0 0 1 5.273 8.337.495.495 0 0 0 .49.481h3.004a.51.51 0 0 0 .508-.519A14.02 14.02 0 0 0 22.125 5.41a.505.505 0 0 0-.694.299ZM26 20.5v9a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5m-24 0v9a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5'/><path fill='#ffc400' d='M16.13 10h-.26A7.87 7.87 0 0 0 8 17.87V29.5a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5V17.87A3.88 3.88 0 0 1 15.87 14h.26A3.88 3.88 0 0 1 20 17.87V29.5a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5V17.87A7.87 7.87 0 0 0 16.13 10m1.51-2h-3.28a.5.5 0 0 1-.474-.342l-1.667-5A.5.5 0 0 1 12.694 2h6.612a.5.5 0 0 1 .475.658l-1.667 5A.5.5 0 0 1 17.64 8'/></svg>",
"routing": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M18 14v-2h8l2-3-2-3h-8V4l-2-2-2 2v6H6l-2 3 2 3h8v10a4 4 0 0 0-4 4h12a4 4 0 0 0-4-4v-6h8l2-3-2-3Z'/></svg>",
- "rspec": "<svg viewBox='0 0 128 128'><path fill='#FF1744' d='M50.633 39.468h-2.267L33.824 54.661l.423 1.03 8.624 1.563-8.624-1.563 3.05 7.437 26.265 31.47 26.262-31.47 3.051-7.437-8.625 1.563-.096-.53-12.888.016 12.887-.017L76.49 39.5v-.032H54.147l1.71 17.27v.002h-.001v-.002l-1.709-17.27zm12.93 55.13L46.738 64.322 63.562 94.6Zm0 0L52.256 64.752l-.172-.457.172.457 11.305 29.847ZM84.18 57.267l-3.533 7.186zm-3.535 7.186-.13.262zm-5.61-.157-.171.457.172-.457ZM71.05 57.43l-3.638 9.198-.192-.024.192.025zm-11.34 9.2-2.316-5.858 2.315 5.856.196-.025-.196.026Zm3.852 27.97 3.655-27.997-3.654 27.996Zm-20.62-37.334 3.532 7.187zm35.813-17.797 14.543 15.193-.423 1.03-8.625 1.563-.095-.53L76.489 39.5v-.032zM56.073 56.74l7.49.012z' clip-rule='evenodd'/><path fill='#80DEEA' d='M120 64a55.9 55.9 0 0 1-3.96 20.723l-.013-.005-8.327 1.656-5.206-7.77A41 41 0 0 0 105.16 64c0-21.382-16.301-38.955-37.155-40.968l.005-.061 5.632-7.6-4.258-7.115C97.787 10.963 120 34.89 120 64'/><path fill='#4DD0E1' d='m106.557 89.43 8.141-1.618C105.749 106.834 86.412 120 64 120c-22.475 0-41.858-13.239-50.773-32.347l.012-.005 4.506-7.198 9.225 1.543h.003C33.646 95.71 47.72 105.16 64 105.16c16.457 0 30.657-9.658 37.243-23.613l.049.023'/><path fill='#26C6DA' d='M64.808 22.84v.009q-.402-.01-.808-.01c-22.732 0-41.16 18.428-41.16 41.161 0 5.317 1.01 10.395 2.843 15.062l-.049.02-9.33-1.56-4.407 7.037A55.8 55.8 0 0 1 8 64C8 33.072 33.072 8 64 8q1.014 0 2.019.035l4.36 7.289'/></svg>",
- "rubocop": "<svg viewBox='0 0 32 32'><path fill='#b0bec5' d='M22 24a2 2 0 0 1-2 2h-2l-1-2h-2l-1 2h-2a2 2 0 0 1-2-2v-2H8v2a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-2h-2Z'/><path fill='#b0bec5' d='M20 24v-2h-8v2h1v-1h6v1zm6.5-10H26a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2h-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2h.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5'/><path fill='#E53935' d='M8 15h16v2H8z' data-mit-no-recolor='true'/><path fill='#b0bec5' d='M23.738 10a7.99 7.99 0 0 0-15.476 0Z'/></svg>",
- "rubocop_light": "<svg viewBox='0 0 32 32'><path fill='#455a64' d='M22 24a2 2 0 0 1-2 2h-2l-1-2h-2l-1 2h-2a2 2 0 0 1-2-2v-2H8v2a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-2h-2Z'/><path fill='#455a64' d='M20 24v-2h-8v2h1v-1h6v1zm6.5-10H26a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2h-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2h.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5'/><path fill='#E53935' d='M8 15h16v2H8z' data-mit-no-recolor='true'/><path fill='#455a64' d='M23.738 10a7.99 7.99 0 0 0-15.476 0Z'/></svg>",
+ "rspec": "<svg viewBox='0 0 128 128'><path fill='#ff1744' d='M50.633 39.468h-2.267L33.824 54.661l.423 1.03 8.624 1.563-8.624-1.563 3.05 7.437 26.265 31.47 26.262-31.47 3.051-7.437-8.625 1.563-.096-.53-12.888.016 12.887-.017L76.49 39.5v-.032H54.147l1.71 17.27v.002h-.001v-.002l-1.709-17.27zm12.93 55.13L46.738 64.322 63.562 94.6Zm0 0L52.256 64.752l-.172-.457.172.457 11.305 29.847ZM84.18 57.267l-3.533 7.186zm-3.535 7.186-.13.262zm-5.61-.157-.171.457.172-.457ZM71.05 57.43l-3.638 9.198-.192-.024.192.025zm-11.34 9.2-2.316-5.858 2.315 5.856.196-.025-.196.026Zm3.852 27.97 3.655-27.997-3.654 27.996Zm-20.62-37.334 3.532 7.187zm35.813-17.797 14.543 15.193-.423 1.03-8.625 1.563-.095-.53L76.489 39.5v-.032zM56.073 56.74l7.49.012z' clip-rule='evenodd'/><path fill='#80deea' d='M120 64a55.9 55.9 0 0 1-3.96 20.723l-.013-.005-8.327 1.656-5.206-7.77A41 41 0 0 0 105.16 64c0-21.382-16.301-38.955-37.155-40.968l.005-.061 5.632-7.6-4.258-7.115C97.787 10.963 120 34.89 120 64'/><path fill='#4dd0e1' d='m106.557 89.43 8.141-1.618C105.749 106.834 86.412 120 64 120c-22.475 0-41.858-13.239-50.773-32.347l.012-.005 4.506-7.198 9.225 1.543h.003C33.646 95.71 47.72 105.16 64 105.16c16.457 0 30.657-9.658 37.243-23.613l.049.023'/><path fill='#26c6da' d='M64.808 22.84v.009q-.402-.01-.808-.01c-22.732 0-41.16 18.428-41.16 41.161 0 5.317 1.01 10.395 2.843 15.062l-.049.02-9.33-1.56-4.407 7.037A55.8 55.8 0 0 1 8 64C8 33.072 33.072 8 64 8q1.014 0 2.019.035l4.36 7.289'/></svg>",
+ "rubocop": "<svg viewBox='0 0 32 32'><path fill='#b0bec5' d='M22 24a2 2 0 0 1-2 2h-2l-1-2h-2l-1 2h-2a2 2 0 0 1-2-2v-2H8v2a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-2h-2Z'/><path fill='#b0bec5' d='M20 24v-2h-8v2h1v-1h6v1zm6.5-10H26a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2h-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2h.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5'/><path fill='#e53935' d='M8 15h16v2H8z' data-mit-no-recolor='true'/><path fill='#b0bec5' d='M23.738 10a7.99 7.99 0 0 0-15.476 0Z'/></svg>",
+ "rubocop_light": "<svg viewBox='0 0 32 32'><path fill='#455a64' d='M22 24a2 2 0 0 1-2 2h-2l-1-2h-2l-1 2h-2a2 2 0 0 1-2-2v-2H8v2a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-2h-2Z'/><path fill='#455a64' d='M20 24v-2h-8v2h1v-1h6v1zm6.5-10H26a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2h-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5H6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2h.5a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5'/><path fill='#e53935' d='M8 15h16v2H8z' data-mit-no-recolor='true'/><path fill='#455a64' d='M23.738 10a7.99 7.99 0 0 0-15.476 0Z'/></svg>",
"ruby": "<svg viewBox='0 0 24 24'><path fill='#f44336' d='M18.041 3.177c2.24.382 2.879 1.919 2.843 3.527V6.67l-1.013 13.266-13.132.897h.008c-1.093-.044-3.518-.151-3.634-3.545l1.217-2.222 2.462 5.74 2.097-6.77-.045.009.018-.018 6.85 2.186L13.945 9.3l6.53-.409-5.144-4.212 2.71-1.51v.009M3.113 17.252v.017zM6.916 6.874c2.63-2.622 6.033-4.168 7.34-2.844 1.297 1.306-.072 4.523-2.702 7.135-2.666 2.613-6.015 4.248-7.322 2.933-1.306-1.324.036-4.612 2.675-7.224z'/></svg>",
"ruff": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M26 16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4v24h10v-8h2v8h12V18h-6v-2Zm-8-2h-6v-2h6Z'/></svg>",
"rust": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='m30 12-4-2V6h-4l-2-4-4 2-4-2-2 4H6v4l-4 2 2 4-2 4 4 2v4h4l2 4 4-2 4 2 2-4h4v-4l4-2-2-4ZM6 16a9.9 9.9 0 0 1 .842-4H10v8H6.842A9.9 9.9 0 0 1 6 16m10 10a9.98 9.98 0 0 1-7.978-4H16v-2h-2v-2h4c.819.819.297 2.308 1.179 3.37a1.89 1.89 0 0 0 1.46.63h3.34A9.98 9.98 0 0 1 16 26m-2-12v-2h4a1 1 0 0 1 0 2Zm11.158 6H24a2.006 2.006 0 0 1-2-2 2 2 0 0 0-2-2 3 3 0 0 0 3-3q0-.08-.004-.161A3.115 3.115 0 0 0 19.83 10H8.022a9.986 9.986 0 0 1 17.136 10'/></svg>",
"salesforce": "<svg viewBox='0 0 24 24'><path fill='#039be5' d='M18.206 6.522c-.681 0-1.275.204-1.858.399-.681-1.177-1.956-1.956-3.328-1.956-1.07 0-2.043.487-2.734 1.168-.779-.973-1.956-1.664-3.318-1.664-2.267 0-4.213 1.858-4.213 4.126 0 .574.204 1.157.399 1.741a3.58 3.58 0 0 0-1.859 3.124c0 1.946 1.567 3.62 3.523 3.62.292 0 .583 0 .778-.098.39 1.469 1.858 2.55 3.62 2.55 1.654 0 3.026-.984 3.512-2.356.496.205.983.4 1.47.4 1.274 0 2.442-.71 3.025-1.762.302.078.613.078.886.078 2.54 0 4.592-2.034 4.592-4.67.098-2.627-1.946-4.7-4.495-4.7'/></svg>",
- "san": "<svg viewBox='0 0 32 32'><path fill='#01579B' d='M28 17.898 4 23.316V30l24-5.418Zm0-10.623L4 12.694v6.683l24-5.418Z'/><path fill='#B3E5FC' d='M28 13.926 4 8.684V2l24 5.242Zm0 10.623L4 19.307v-6.684l24 5.242Z'/></svg>",
+ "san": "<svg viewBox='0 0 32 32'><path fill='#01579b' d='M28 17.898 4 23.316V30l24-5.418Zm0-10.623L4 12.694v6.683l24-5.418Z'/><path fill='#b3e5fc' d='M28 13.926 4 8.684V2l24 5.242Zm0 10.623L4 19.307v-6.684l24 5.242Z'/></svg>",
"sas": "<svg viewBox='0 0 6.35 6.35'><path fill='#039be5' d='M3.16.546a1.9 1.9 0 0 0-.667.13c-.378.157-.678.4-.846.74-.255.452-.296 1.032.04 1.536.31.474.72.908 1.148 1.407.382.139.572-.272.515-.42-.276-.35-.57-.695-.804-1.06a1.1 1.1 0 0 1-.07-.906c.075-.198.503-.84 1.372-.836.345.04.658.059 1.086.53a1.6 1.6 0 0 0-.536-.753A2.05 2.05 0 0 0 3.159.546zm.245 1.4c-.277.002-.406.227-.348.437.253.365.578.748.869 1.122.316.609.092 1.077-.512 1.472-.82.458-1.576.116-2.026-.298.234.489.423.636.471.679.31.27.81.527 1.62.423.456-.102 1.06-.256 1.396-1.097.093-.265.125-.54.03-.843-.09-.338-.274-.58-.466-.829-.3-.351-.77-.979-.906-1.053a.6.6 0 0 0-.128-.013'/></svg>",
"sass": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M27.837 5.673a4.33 4.33 0 0 0-2.293-2.701c-2.362-1.261-6.11-1.298-9.548-.092a26.3 26.3 0 0 0-8.76 4.966c-2.752 2.542-3.438 4.925-3.189 6.194.523 2.668 3.274 4.539 5.485 6.042.418.284.822.559 1.175.816-1.429.76-4.261 2.444-5.088 4.248a3.88 3.88 0 0 0-.118 3.332A2.37 2.37 0 0 0 6.869 29.8a5.6 5.6 0 0 0 1.49.2 6.35 6.35 0 0 0 5.19-2.856 6.74 6.74 0 0 0 .864-5.382 7.3 7.3 0 0 1 2.044-.03 3.92 3.92 0 0 1 2.816 1.311 1.82 1.82 0 0 1 .423 1.262 1.55 1.55 0 0 1-.772 1.05c-.234.14-.586.355-.504.803.036.194.198.633.894.512a2.93 2.93 0 0 0 2.145-2.651 4 4 0 0 0-1.197-2.904 5.94 5.94 0 0 0-4.396-1.626 10.6 10.6 0 0 0-2.672.304 20 20 0 0 0-2.203-1.846c-1.712-1.3-3.33-2.529-3.235-4.26.125-2.263 2.468-4.532 6.964-6.744 4.016-1.976 7.254-2.037 8.944-1.438a2 2 0 0 1 1.204.883 2.77 2.77 0 0 1-.36 2.47 9.71 9.71 0 0 1-7.425 4.304 3.86 3.86 0 0 1-3.238-.757c-.278-.302-.593-.645-1.074-.383q-.565.31-.225 1.189a3.9 3.9 0 0 0 2.407 1.92 11.7 11.7 0 0 0 7.128-.671c3.527-1.35 6.681-5.202 5.756-8.787M11.895 24.475a4 4 0 0 1-.192.468 4.5 4.5 0 0 1-.753 1.081 2.83 2.83 0 0 1-2.533 1.107c-.056-.032-.078-.146-.085-.193a3.28 3.28 0 0 1 1.076-2.284 11.3 11.3 0 0 1 2.644-1.933 3.85 3.85 0 0 1-.157 1.754'/></svg>",
"sbt": "<svg viewBox='0 0 300 300'><path fill='#0277bd' d='M105.46 209.517c-7.875 0-13.452-7.521-13.452-15.37v-.327c0-7.848 5.578-13.735 13.452-13.735h164.05c1.476-4.905 2.625-11.446 3.281-17.986h-137.81c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h137.31c-.82-6.54-1.969-13.081-3.773-17.986h-104.01c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h91.87c-21.327-37.607-60.864-61.315-106.14-61.315-67.918 0-123.04 54.448-123.04 122.3 0 67.856 55.122 123.28 123.04 123.28 46.59 0 87.112-25.507 107.95-63.114h-152.73z'/></svg>",
@@ -968,7 +978,7 @@
"semantic-release_light": "<svg viewBox='0 0 300 300'><path fill='#455a64' d='M93.656 252.93c-30.929-17.946-57.303-33.656-58.608-34.91-2.197-2.111-2.372-7.168-2.352-68.012.01-36.152.469-66.413 1.017-67.248.84-1.278 114.93-68.32 116.26-68.32 1.335 0 115.42 67.042 116.26 68.32 1.697 2.584 1.253 132.52-.461 134.58-1.178 1.42-50.022 30.2-110.42 65.063l-5.466 3.155zm74.879 11.529c9.928-5.689 19.12-11.554 20.425-13.033 5.167-5.855 2.22-22.428-7.921-44.553-3.434-7.491-5.89-13.974-5.458-14.406 2.166-2.165 30 21.345 35.714 30.166 8.795 13.576 5.942 13.273 24.626 2.61 9.088-5.185 17.723-10.52 19.191-11.854 2.44-2.22 2.668-4.272 2.668-24.017 0-24.644.382-23.74-12.846-30.433-7.331-3.71-27.076-7.453-39.62-7.51-3.127-.015-6.045-.611-6.486-1.326-1.018-1.646 3.35-3.864 20.305-10.314 10.685-4.064 16.148-5.32 25.765-5.926l12.204-.768V87.052l-16.926-9.863c-9.31-5.425-18.078-9.863-19.485-9.863-4.587 0-18.396 9.537-24.283 16.77-3.164 3.888-8.499 11.144-11.855 16.125-3.357 4.982-6.648 9.076-7.314 9.1-1.699.06.055-16.855 3.282-31.654 1.676-7.688 4.235-14.848 6.755-18.906 2.231-3.592 4.057-7.32 4.057-8.283 0-1.574-38.81-25.189-41.396-25.189-1.803 0-38.26 21.675-40.016 23.79-1.114 1.343-1.456 5.6-1.056 13.167.52 9.845 1.444 13.13 7.728 27.456 3.926 8.95 7.463 17.035 7.86 17.968 1.606 3.762-2.84 1.171-15.601-9.094-15.36-12.355-19.89-17.167-24.644-26.181-1.863-3.533-4-6.376-4.746-6.318-.747.057-9.292 4.806-18.987 10.552l-17.63 10.448v47.348l7.44 4.445c9.78 5.844 13.866 6.917 33.242 8.725 8.95.835 16.8 1.982 17.445 2.548 1.506 1.321-3.334 3.76-20.158 10.16-10.685 4.064-16.148 5.32-25.765 5.926l-12.204.769-.372 21.952c-.44 25.928-2.375 22.64 21.282 36.139l16.252 9.273 7.144-3.85c9.02-4.859 15.585-11.571 25.982-26.562 4.526-6.526 8.717-11.865 9.314-11.865 1.68 0 1.312 5.155-1.614 22.579-2.03 12.092-3.88 18.4-7.474 25.496-4.046 7.985-4.493 9.712-2.918 11.287 1.926 1.926 37.46 23.046 39.11 23.245.513.062 9.056-4.542 18.985-10.231zm-28.054-89.313c-7.395-2.593-15.058-11.41-16.73-19.25-4.594-21.54 17.346-39.532 37.071-30.4 17.296 8.008 21.43 29.242 8.34 42.832-8.106 8.416-17.651 10.686-28.68 6.818zm16.04-7.965c2.435-1.017 6.077-3.81 8.093-6.206 7.074-8.407 3.726-22.934-6.392-27.735-12.968-6.154-26.556 2.647-26.556 17.2 0 7.107 4.494 13.649 11.459 16.683 5.324 2.319 7.957 2.33 13.396.058'/></svg>",
"semgrep": "<svg viewBox='0 0 24 24'><path fill='#00bfa5' d='M5.918 8.101a3.898 3.898 0 1 0 3.041 6.336l.004-.005.002.001q.055-.069.107-.142a2.7 2.7 0 0 0 .36-.602l.029-.064.03-.067h.13a2 2 0 0 1-.117.348 7 7 0 0 1-.38.705l-.007.01A3.9 3.9 0 0 0 12 15.898a3.9 3.9 0 0 0 2.883-1.277l-.006-.01a7 7 0 0 1-.383-.705 2 2 0 0 1-.117-.348h.13q.035.079.071.155a2.7 2.7 0 0 0 .348.578l.023.03.008.01q.038.052.078.1v.003l.002.002a3.898 3.898 0 1 0 .149-5.047c.238.385.416.677.537.974h-.137c-.238-.388-.319-.543-.545-.802a3.89 3.89 0 0 0-3.04-1.46 3.89 3.89 0 0 0-3.042 1.46c-.226.26-.309.414-.547.802h-.135c.121-.297.297-.589.536-.974a3.9 3.9 0 0 0-2.895-1.287zm0 1.715a2.184 2.184 0 1 1 0 4.368 2.184 2.184 0 0 1 0-4.368m6.082 0a2.184 2.184 0 1 1 0 4.368 2.184 2.184 0 0 1 0-4.368m6.082 0c1.206 0 2.182.978 2.182 2.184a2.182 2.182 0 1 1-4.366 0c0-1.206.978-2.184 2.184-2.184'/></svg>",
"sentry": "<svg viewBox='0 0 200 200'><path fill='#f06292' d='M181.58 148.3c3.8 6.649 4.206 13.637 1.018 19.2s-9.43 8.684-17.03 8.684h-14.45c.203-2.714.271-5.428.271-8.141 0-3.053-.136-6.106-.34-9.091h9.499c2.307 0 4.206-1.9 4.206-4.207 0-.678-.203-1.424-.475-2.035L103.694 47.35c-.746-1.357-2.17-2.171-3.663-2.171s-2.85.746-3.596 2.035l-9.634 16.69c29.241 22.05 48.848 56.175 51.561 94.914.204 2.985.34 6.038.34 9.091 0 2.714-.136 5.428-.272 8.142H91.278q.407-4.071.407-8.142c0-3.053-.203-6.106-.542-9.09-2.307-21.304-12.755-40.232-28.155-53.53l-6.65 11.533c11.602 10.787 19.54 25.51 21.71 42.063.408 2.985.611 6.038.611 9.091 0 2.782-.203 5.496-.475 8.142H34.425c-7.666 0-13.908-3.189-17.029-8.684-3.188-5.496-2.781-12.551 1.018-19.2l8.956-15.401a52.4 52.4 0 0 1 13.704 10.652l-5.36 9.09c-.34.611-.475 1.29-.543 2.036 0 2.307 1.9 4.206 4.206 4.206h21.235a52.26 52.26 0 0 0-13.162-26.595c-3.934-4.274-8.616-7.87-13.704-10.65L57.424 80.39c5.02 2.782 9.838 6.038 14.247 9.702 20.421 16.554 34.193 41.046 36.704 68.726h12.687c-2.578-32.362-18.86-60.924-43.013-79.852-4.545-3.528-9.362-6.784-14.383-9.566l20.217-35.143c3.8-6.649 9.702-10.448 16.011-10.448 6.378 0 12.212 3.8 16.011 10.448z'/></svg>",
- "sequelize": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 250 250'><g stroke-width='.725'><path fill='#01579b' d='M190.945 86.173v77.236l-65.63 38.829-.593.54v28.867l.593.56 92.43-53.39V71.194l-.873-.215-26.058 14.568.132.629'/><path fill='#0288d1' d='m59.477 164.043 65.84 38.196v29.966l-93.063-53.601v-107.2l.955-.144 25.983 15.106.285.865v76.814'/><path fill='#4fc3f7' d='M59.477 87.223 32.254 71.396l92.852-53.601 92.64 53.39-26.8 14.983-65.84-37.563-65.63 38.618'/><path fill='#283593' d='m124.186 155.203-.713-.727v-29.002l.713-.369.173-.715 24.864-14.504.76.17v29.884l-25.798 15.263'/><path fill='#0277BD' d='M97.959 140.873v-31.029l.72-.035 25.292 14.718.216.58v30.098z'/><path fill='#29B6F6' d='m123.756 94.583-25.798 15.264 26.228 15.263 25.798-15.049z'/><path fill='#01579b' d='m92.083 174.123-.713-.727v-29.003l.713-.368.173-.715 24.863-14.504.761.17v29.882l-25.798 15.264'/><path fill='#0288d1' d='M65.855 159.793v-31.03l.72-.036 25.292 14.72.215.58v30.098z'/><path fill='#4fc3f7' d='m91.653 113.503-25.799 15.263 26.229 15.264 25.798-15.049-26.228-15.479'/><path fill='#01579b' d='m158.585 174.843-.712-.727v-29.003l.713-.368.173-.715 24.864-14.504.76.17v29.882z'/><path fill='#0288d1' d='M132.356 160.513v-31.03l.72-.035 25.292 14.72.216.58v30.097z'/><path fill='#4fc3f7' d='m158.155 114.213-25.798 15.263 26.228 15.264 25.798-15.049z'/><path fill='#01579b' d='m126.476 193.763-.713-.727v-29.003l.713-.368.173-.716 24.864-14.503.76.17V178.5l-25.798 15.264'/><path fill='#0288d1' d='M100.256 179.433v-31.03l.72-.035 25.293 14.718.214.58v30.099l-26.228-14.332'/><path fill='#4fc3f7' d='m126.046 133.133-25.799 15.264 26.229 15.264 25.798-15.049z'/><path fill='#01579b' d='m124.186 114.073-.713-.728V84.343l.713-.368.173-.715 24.864-14.503.76.17v29.882l-25.798 15.264'/><path fill='#0288d1' d='M97.959 99.743v-31.03l.72-.035 25.292 14.718.216.58v30.099z'/><path fill='#4fc3f7' d='M123.756 53.443 97.958 68.707l26.228 15.264 25.798-15.049z'/><path fill='#01579b' d='m92.083 132.993-.713-.727v-29.002l.713-.369.173-.715 24.863-14.504.761.17v29.884l-25.798 15.263'/><path fill='#0288d1' d='M65.855 118.663V87.634l.72-.036 25.292 14.72.215.58v30.097z'/><path fill='#4fc3f7' d='m91.653 72.363-25.8 15.264 26.229 15.263 25.798-15.049z'/><path fill='#01579b' d='m158.585 133.713-.712-.727v-29.002l.713-.369.173-.715 24.864-14.504.76.17v29.884z'/><path fill='#0288d1' d='M132.356 119.373V88.344l.72-.035 25.292 14.718.216.58v30.098z'/><path fill='#4fc3f7' d='m158.155 73.083-25.798 15.264 26.228 15.263 25.798-15.049z'/><path fill='#01579b' d='m126.476 152.623-.713-.727v-29.003l.713-.368.173-.715 24.864-14.504.76.17v29.882l-25.798 15.264'/><path fill='#0288d1' d='M100.256 138.293v-31.03l.72-.036 25.293 14.72.214.58v30.098l-26.228-14.332'/><path fill='#4fc3f7' d='m126.046 92.003-25.799 15.264 26.229 15.264 25.798-15.049z'/></g></svg>",
+ "sequelize": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 250 250'><g stroke-width='.725'><path fill='#01579b' d='M190.945 86.173v77.236l-65.63 38.829-.593.54v28.867l.593.56 92.43-53.39V71.194l-.873-.215-26.058 14.568.132.629'/><path fill='#0288d1' d='m59.477 164.043 65.84 38.196v29.966l-93.063-53.601v-107.2l.955-.144 25.983 15.106.285.865v76.814'/><path fill='#4fc3f7' d='M59.477 87.223 32.254 71.396l92.852-53.601 92.64 53.39-26.8 14.983-65.84-37.563-65.63 38.618'/><path fill='#283593' d='m124.186 155.203-.713-.727v-29.002l.713-.369.173-.715 24.864-14.504.76.17v29.884l-25.798 15.263'/><path fill='#0277bd' d='M97.959 140.873v-31.029l.72-.035 25.292 14.718.216.58v30.098z'/><path fill='#29b6f6' d='m123.756 94.583-25.798 15.264 26.228 15.263 25.798-15.049z'/><path fill='#01579b' d='m92.083 174.123-.713-.727v-29.003l.713-.368.173-.715 24.863-14.504.761.17v29.882l-25.798 15.264'/><path fill='#0288d1' d='M65.855 159.793v-31.03l.72-.036 25.292 14.72.215.58v30.098z'/><path fill='#4fc3f7' d='m91.653 113.503-25.799 15.263 26.229 15.264 25.798-15.049-26.228-15.479'/><path fill='#01579b' d='m158.585 174.843-.712-.727v-29.003l.713-.368.173-.715 24.864-14.504.76.17v29.882z'/><path fill='#0288d1' d='M132.356 160.513v-31.03l.72-.035 25.292 14.72.216.58v30.097z'/><path fill='#4fc3f7' d='m158.155 114.213-25.798 15.263 26.228 15.264 25.798-15.049z'/><path fill='#01579b' d='m126.476 193.763-.713-.727v-29.003l.713-.368.173-.716 24.864-14.503.76.17V178.5l-25.798 15.264'/><path fill='#0288d1' d='M100.256 179.433v-31.03l.72-.035 25.293 14.718.214.58v30.099l-26.228-14.332'/><path fill='#4fc3f7' d='m126.046 133.133-25.799 15.264 26.229 15.264 25.798-15.049z'/><path fill='#01579b' d='m124.186 114.073-.713-.728V84.343l.713-.368.173-.715 24.864-14.503.76.17v29.882l-25.798 15.264'/><path fill='#0288d1' d='M97.959 99.743v-31.03l.72-.035 25.292 14.718.216.58v30.099z'/><path fill='#4fc3f7' d='M123.756 53.443 97.958 68.707l26.228 15.264 25.798-15.049z'/><path fill='#01579b' d='m92.083 132.993-.713-.727v-29.002l.713-.369.173-.715 24.863-14.504.761.17v29.884l-25.798 15.263'/><path fill='#0288d1' d='M65.855 118.663V87.634l.72-.036 25.292 14.72.215.58v30.097z'/><path fill='#4fc3f7' d='m91.653 72.363-25.8 15.264 26.229 15.263 25.798-15.049z'/><path fill='#01579b' d='m158.585 133.713-.712-.727v-29.002l.713-.369.173-.715 24.864-14.504.76.17v29.884z'/><path fill='#0288d1' d='M132.356 119.373V88.344l.72-.035 25.292 14.718.216.58v30.098z'/><path fill='#4fc3f7' d='m158.155 73.083-25.798 15.264 26.228 15.263 25.798-15.049z'/><path fill='#01579b' d='m126.476 152.623-.713-.727v-29.003l.713-.368.173-.715 24.864-14.504.76.17v29.882l-25.798 15.264'/><path fill='#0288d1' d='M100.256 138.293v-31.03l.72-.036 25.293 14.72.214.58v30.098l-26.228-14.332'/><path fill='#4fc3f7' d='m126.046 92.003-25.799 15.264 26.229 15.264 25.798-15.049z'/></g></svg>",
"serverless": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M12.897 6H2v4h9.613zm4.201 0-1.284 4H30V6zm-2.568 8-1.283 4H30v-4zm-4.2 0H2v4h7.046zm1.633 8-1.283 4H30v-4zm-4.201 0H2v4h4.479z'/></svg>",
"settings": "<svg fill='none' viewBox='0 0 24 24'><path d='M0 0h24v24H0z'/><path fill='#42a5f5' d='M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.49.49 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1a.6.6 0 0 0-.18-.03c-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46a.5.5 0 0 0 .61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1q.09.03.18.03c.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64zm-1.98-1.71c.04.31.05.52.05.73s-.02.43-.05.73l-.14 1.13.89.7 1.08.84-.7 1.21-1.27-.51-1.04-.42-.9.68c-.43.32-.84.56-1.25.73l-1.06.43-.16 1.13-.2 1.35h-1.4l-.19-1.35-.16-1.13-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7-1.06.43-1.27.51-.7-1.21 1.08-.84.89-.7-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13-.89-.7-1.08-.84.7-1.21 1.27.51 1.04.42.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43.16-1.13.2-1.35h1.39l.19 1.35.16 1.13 1.06.43c.43.18.83.41 1.23.71l.91.7 1.06-.43 1.27-.51.7 1.21-1.07.85-.89.7zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2'/></svg>",
"shader": "<svg viewBox='0 0 24 24'><path fill='#ab47bc' d='M10.469 17.102a4.083 4.083 0 1 1-8.166 0 4.083 4.083 0 0 1 8.166 0m5.069-9.666a4.083 4.083 0 1 0-5.446 3.296c3.13 1.231 3.867 2.474 3.475 5.828a4.083 4.083 0 1 0 5.446-3.295c-3.13-1.232-3.869-2.475-3.475-5.829'/></svg>",
@@ -979,13 +989,13 @@
"slim": "<svg viewBox='0 0 32 32'><path fill='#f57f17' d='M23 2H9a7 7 0 0 0-7 7v14a7 7 0 0 0 7 7h14a7 7 0 0 0 7-7V9a7 7 0 0 0-7-7m-5 14-4-6v6H6a10 10 0 0 1 20 0Z'/></svg>",
"slint": "<svg viewBox='0 0 16 16'><path fill='#2979ff' d='M12 1 3 7l5 2-2-2Z'/><path fill='#2979ff' d='m4 15 9-6-5-2 2 2Z'/></svg>",
"slug": "<svg viewBox='0 0 24 24'><path fill='#f9a825' d='m9.164 21.221-.983-.056c-3.402-.19-5.654-.714-6.125-1.427-.136-.205-.146-.515-.022-.681.115-.154.377-.28.744-.355l.313-.064.373.122c1.568.517 2.903.589 4.875.263.82-.135 1.26-.29 1.736-.613.183-.124.26-.152.413-.152.62-.002 1.581-.168 2.066-.357 1.392-.544 2.655-2.023 2.979-3.49.159-.717.072-1.83-.211-2.693l-.13-.397.028-.747c.023-.606.047-.818.126-1.123.29-1.108.991-1.878 1.957-2.145.374-.103 1.17-.093 1.589.02a3.34 3.34 0 0 1 1.595.941c.505.548.707 1.025.735 1.738.021.55-.034.809-.283 1.316-.211.43-.542.833-.909 1.107-.15.113-.302.23-.336.26-.052.046-.067.192-.087.837-.014.43-.053.986-.086 1.235-.458 3.354-2.234 5.421-5.307 6.174-1.023.25-1.37.283-3.183.292-.908.005-1.748.002-1.867-.005m-3.967-2.877-.45-.07-.3-.322c-1.514-1.613-2.085-4.002-1.43-5.98.646-1.953 2.32-3.39 4.496-3.86.557-.121 1.912-.133 2.437-.023a6.7 6.7 0 0 1 1.631.56c1.828.904 2.982 2.495 3.205 4.421.111.955-.13 1.793-.763 2.651-.712.966-1.618 1.525-2.681 1.654l-.233.028.267-.277c.475-.495.596-.93.596-2.15 0-.683-.011-.831-.087-1.134-.182-.721-.476-1.239-.967-1.705-.643-.611-1.44-.923-2.349-.92-1.295.005-2.196.828-2.196 2.006 0 .398.069.695.24 1.033.135.269.446.62.68.769.368.233.984.35 1.195.225a.387.387 0 0 0 .096-.61c-.09-.101-.15-.126-.364-.154-.337-.043-.437-.086-.642-.275-.26-.239-.395-.57-.397-.964-.002-.269.014-.348.11-.543.133-.27.396-.497.703-.603.327-.114.98-.096 1.386.038.854.281 1.456.918 1.699 1.797.054.196.07.401.07.903 0 .57-.011.683-.092.941-.103.327-.31.758-.488 1.019-.295.429-.853.927-1.283 1.144-.691.348-2.98.573-4.09.4zM19.199 6.578l-.244-.078.14-.4c.537-1.544 1.334-2.521 2.057-2.521.567 0 1.043.588.862 1.067-.045.12-.244.345-.37.419-.044.025-.31.054-.597.065-.497.019-.524.024-.688.139-.221.154-.503.575-.69 1.03-.081.2-.166.361-.188.36a3 3 0 0 1-.282-.081m-2.185-.06c-.068-.115-.127-.837-.126-1.545.001-.639.015-.863.07-1.08.194-.765.529-1.121 1.055-1.121.588 0 .932.42.782.957-.072.258-.183.365-.591.568-.312.155-.394.216-.496.369-.176.262-.228.59-.2 1.253l.023.538-.165.027c-.09.014-.202.035-.249.047s-.092.006-.103-.012z'/></svg>",
- "smarty": "<svg viewBox='0 0 32 32'><path fill='#FFCA28' d='M16 5a7 7 0 0 1 4.198 12.601l-1.198.9V21h-6v-2.498l-1.198-.9a7 7 0 0 1 3.362-12.554A7 7 0 0 1 16 5m0-3a10 10 0 0 0-1.176.067A10 10 0 0 0 10 20.001V22a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2a10 10 0 0 0-6-18m-4 24h8v2a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2z'/></svg>",
+ "smarty": "<svg viewBox='0 0 32 32'><path fill='#ffca28' d='M16 5a7 7 0 0 1 4.198 12.601l-1.198.9V21h-6v-2.498l-1.198-.9a7 7 0 0 1 3.362-12.554A7 7 0 0 1 16 5m0-3a10 10 0 0 0-1.176.067A10 10 0 0 0 10 20.001V22a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2a10 10 0 0 0-6-18m-4 24h8v2a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2z'/></svg>",
"sml": "<svg viewBox='0 0 24 24'><path fill='#f44336' d='M4.747 6.562h6.437c1.202 0 2.176.974 2.176 2.176v8.702h-2.176V8.738H9.008v7.614H6.833V8.738H4.746v8.702H2.481V8.738a2.176 2.176 0 0 1 2.266-2.176m10.244 0h2.176v8.702h4.352v2.176H14.99z'/></svg>",
"snakemake": "<svg viewBox='0 0 16 16'><path fill='#00897b' d='M6.635 11.014c1.237 0 2.473-.006 3.71.002 1.273.009 2.127 1.282 1.76 2.608-.208.754-.864 1.353-1.586 1.36-1.711.019-3.426.07-5.133-.02-1.558-.084-2.674-1.028-3.369-2.538-.313-.681.121-1.403.828-1.408 1.263-.008 2.527-.002 3.79-.002z'/><path fill='#00897b' d='M8.19 6.022c.744 0 1.489-.003 2.233 0 2.266.01 4.026 1.934 4.061 4.41.018 1.236-.35 2.298-1.118 3.198-.061.072-.157.11-.236.163-.013-.097-.047-.196-.036-.29.217-1.871-1.058-3.473-2.82-3.498-1.506-.022-3.013-.002-4.52-.01-.88-.004-1.587-.639-1.771-1.569-.243-1.227.607-2.392 1.759-2.402.816-.007 1.632-.001 2.448-.002'/><path fill='#00897b' d='M11.242 3.292c.296-.006.518-.264.507-.589-.011-.312-.237-.536-.532-.525-.282.01-.512.27-.504.567.007.29.261.553.529.547m-8.3 5.473c-.457-.45-.784-1.005-1.007-1.627-.9-2.52.466-5.374 2.874-5.996a4 4 0 0 1 .985-.114c1.534-.012 3.067-.02 4.6-.001 1.697.02 2.933.888 3.721 2.516.342.706-.071 1.449-.806 1.456-1.058.011-2.116.003-3.174.003-1.417 0-2.833-.006-4.25.001-1.442.008-2.65 1.145-2.85 2.691-.034.257.008.525.01.788.002.088-.01.177-.017.266z'/></svg>",
"snapcraft": "<svg viewBox='0 0 1.28 1.28'><path fill='#81c784' d='M.76.36 1 .48.76.72zm-.48.8.44-.4-.2-.2zM.12.12l.6.6V.36z'/><path fill='#ff6e40' d='M1.12.36.76.32l.4.2z'/></svg>",
"snowpack": "<svg viewBox='-30 -94 700 700'><path fill='#cfd8dc' d='M600.53 440.27 344.04 41.29a28.5 28.5 0 0 0-48.092 0L39.458 440.27a28.499 28.499 0 0 0 24.046 43.639h512.98a28.499 28.499 0 0 0 24.046-43.639M320 108.97l75.7 118.45H320l-56.998 56.998-33.842-33.842z'/></svg>",
"snowpack_light": "<svg viewBox='-30 -94 700 700'><path fill='#607d8b' d='M600.53 440.27 344.04 41.29a28.5 28.5 0 0 0-48.092 0L39.458 440.27a28.499 28.499 0 0 0 24.046 43.639h512.98a28.499 28.499 0 0 0 24.046-43.639M320 108.97l75.7 118.45H320l-56.998 56.998-33.842-33.842z'/></svg>",
- "snyk": "<svg viewBox='0 0 32 32'><path fill='#607d8b' d='M25.338 20.622c-.337-.62.142-2.09.142-2.09-2.745-2.478-3.432-7.645-3.432-7.645-.512 1.628-1.626 6.428-1.626 6.428a16 16 0 0 0-4.38-.674h-.017q-.192 0-.38.007v11.351h10.323c.034-2.228-.63-7.377-.63-7.377M21.288 24h-3.205a4.2 4.2 0 0 1 2.926-1.409c1.695 0 .428 1.265.278 1.409Z'/><path fill='#90a4ae' d='M11.666 17.316s-1.115-4.8-1.627-6.43c0 .004-.687 5.169-3.432 7.646 0 0 .48 1.47.142 2.09 0 0-.665 5.15-.63 7.377H16V16.64a15.3 15.3 0 0 0-4.334.676M10.77 24c-.149-.144-1.416-1.409.278-1.409A4.2 4.2 0 0 1 13.975 24Z'/><path fill='#455a64' d='M21.555 25.209H18v.033a2.196 2.196 0 0 0 2.333 1.977 2.196 2.196 0 0 0 2.333-1.977v-.033Z'/><path fill='#FAFAFA' d='M18.888 25.209v.033a1.344 1.344 0 0 0 2.667 0v-.033Z'/><path fill='#37474f' d='M20.633 25.209h-.625l-.016.003c.077.015.12.17.103.272a.346.346 0 0 1-.351.27.56.56 0 0 0 .494.401.58.58 0 0 0 .668-.439c.033-.209-.081-.507-.273-.507'/><path fill='#455a64' d='M12.972 25.209H9.417v.033a2.196 2.196 0 0 0 2.333 1.977 2.196 2.196 0 0 0 2.334-1.977v-.033Z'/><path fill='#FAFAFA' d='M10.306 25.209v.033a1.26 1.26 0 0 0 1.333 1.176 1.26 1.26 0 0 0 1.333-1.176v-.033Z'/><path fill='#37474f' d='M12.051 25.209h-.626l-.016.003c.077.015.12.17.104.272a.346.346 0 0 1-.352.27.565.565 0 0 0 .494.401.58.58 0 0 0 .668-.439c.033-.209-.08-.507-.272-.507'/><path fill='#607d8b' d='m9.775 4-1.3 2.098C8.017 6.846 4 13.468 4 15.86V16l1.84 4.31A54.4 54.4 0 0 0 5.288 28h1.775a56 56 0 0 1 .588-7.7l.024-.184-.333-.788a10.7 10.7 0 0 0 2.5-3.887l.053.337 1.817 2.624.71-.268a11.8 11.8 0 0 1 3.603-.687 12.1 12.1 0 0 1 3.623.688l.708.266 1.814-2.624.072-.438a10.9 10.9 0 0 0 2.498 3.95l-.349.826.024.185a56.5 56.5 0 0 1 .593 7.7h1.757a55 55 0 0 0-.572-7.688L28 16.001v-.14c0-2.393-3.986-9.015-4.441-9.763L22.284 4 20.46 15.237l-.843 1.22a13.3 13.3 0 0 0-3.592-.575 13.2 13.2 0 0 0-3.578.575l-.842-1.22-.641-3.939.002-.019-.005-.001Zm-.98 5.013.374 2.29a14.9 14.9 0 0 1-2.538 6.335l-.805-1.905c.101-1.241 1.569-4.215 2.97-6.72Zm14.476 0c1.4 2.505 2.869 5.479 2.97 6.72l-.786 1.86a15.3 15.3 0 0 1-2.528-6.468Z'/></svg>",
+ "snyk": "<svg viewBox='0 0 32 32'><path fill='#607d8b' d='M25.338 20.622c-.337-.62.142-2.09.142-2.09-2.745-2.478-3.432-7.645-3.432-7.645-.512 1.628-1.626 6.428-1.626 6.428a16 16 0 0 0-4.38-.674h-.017q-.192 0-.38.007v11.351h10.323c.034-2.228-.63-7.377-.63-7.377M21.288 24h-3.205a4.2 4.2 0 0 1 2.926-1.409c1.695 0 .428 1.265.278 1.409Z'/><path fill='#90a4ae' d='M11.666 17.316s-1.115-4.8-1.627-6.43c0 .004-.687 5.169-3.432 7.646 0 0 .48 1.47.142 2.09 0 0-.665 5.15-.63 7.377H16V16.64a15.3 15.3 0 0 0-4.334.676M10.77 24c-.149-.144-1.416-1.409.278-1.409A4.2 4.2 0 0 1 13.975 24Z'/><path fill='#455a64' d='M21.555 25.209H18v.033a2.196 2.196 0 0 0 2.333 1.977 2.196 2.196 0 0 0 2.333-1.977v-.033Z'/><path fill='#fafafa' d='M18.888 25.209v.033a1.344 1.344 0 0 0 2.667 0v-.033Z'/><path fill='#37474f' d='M20.633 25.209h-.625l-.016.003c.077.015.12.17.103.272a.346.346 0 0 1-.351.27.56.56 0 0 0 .494.401.58.58 0 0 0 .668-.439c.033-.209-.081-.507-.273-.507'/><path fill='#455a64' d='M12.972 25.209H9.417v.033a2.196 2.196 0 0 0 2.333 1.977 2.196 2.196 0 0 0 2.334-1.977v-.033Z'/><path fill='#fafafa' d='M10.306 25.209v.033a1.26 1.26 0 0 0 1.333 1.176 1.26 1.26 0 0 0 1.333-1.176v-.033Z'/><path fill='#37474f' d='M12.051 25.209h-.626l-.016.003c.077.015.12.17.104.272a.346.346 0 0 1-.352.27.565.565 0 0 0 .494.401.58.58 0 0 0 .668-.439c.033-.209-.08-.507-.272-.507'/><path fill='#607d8b' d='m9.775 4-1.3 2.098C8.017 6.846 4 13.468 4 15.86V16l1.84 4.31A54.4 54.4 0 0 0 5.288 28h1.775a56 56 0 0 1 .588-7.7l.024-.184-.333-.788a10.7 10.7 0 0 0 2.5-3.887l.053.337 1.817 2.624.71-.268a11.8 11.8 0 0 1 3.603-.687 12.1 12.1 0 0 1 3.623.688l.708.266 1.814-2.624.072-.438a10.9 10.9 0 0 0 2.498 3.95l-.349.826.024.185a56.5 56.5 0 0 1 .593 7.7h1.757a55 55 0 0 0-.572-7.688L28 16.001v-.14c0-2.393-3.986-9.015-4.441-9.763L22.284 4 20.46 15.237l-.843 1.22a13.3 13.3 0 0 0-3.592-.575 13.2 13.2 0 0 0-3.578.575l-.842-1.22-.641-3.939.002-.019-.005-.001Zm-.98 5.013.374 2.29a14.9 14.9 0 0 1-2.538 6.335l-.805-1.905c.101-1.241 1.569-4.215 2.97-6.72Zm14.476 0c1.4 2.505 2.869 5.479 2.97 6.72l-.786 1.86a15.3 15.3 0 0 1-2.528-6.468Z'/></svg>",
"solidity": "<svg viewBox='0 0 24 24'><g fill='#0288d1'><path d='m5.747 14.046 6.254 8.61 6.252-8.61-6.254 3.807z'/><path d='M11.999 1.343 5.747 11.83l6.252 3.807 6.253-3.807z'/></g></svg>",
"sonarcloud": "<svg viewBox='0 0 24 24'><path fill='#ef6c00' d='M11.985 2.949c-3.269 0-5.909 2.745-5.936 6.12-2.332.834-4.022 3.116-4.022 5.813 0 3.392 2.663 6.169 5.948 6.169 1.513-.003 2.943-.625 4.025-1.675 1.081 1.052 2.513 1.673 4.026 1.675 3.278 0 5.947-2.77 5.947-6.17v-.001c0-1.145-.314-2.26-.891-3.237a8.3 8.3 0 0 0-1.192-1.379l-.089-.081a5 5 0 0 0-.163-.14l-.02-.016-.037-.03a5.7 5.7 0 0 0-1.666-.945c-.036-3.36-2.669-6.103-5.93-6.103m.007 1.937c2.242 0 4.072 1.894 4.072 4.238v.002a4.32 4.32 0 0 1-1.717 3.46h-.002a.985.985 0 0 0-.218 1.33l.002.002c.179.262.47.41.766.41a.9.9 0 0 0 .546-.182c1.04-.78 1.769-1.882 2.16-3.115a4.24 4.24 0 0 1 2.51 3.855c-.006 2.337-1.836 4.234-4.085 4.234-2.24 0-4.07-1.895-4.071-4.238v-.002a.954.954 0 0 0-.932-.964h-.007a.95.95 0 0 0-.936.966v.002c0 1.08.317 2.077.788 2.961a3.97 3.97 0 0 1-2.894 1.28c-2.242 0-4.075-1.897-4.075-4.24 0-2.341 1.833-4.238 4.075-4.238.487 0 .957.09 1.412.258l.007.004.016.004.005.002.008.004c.07.025.154.061.23.098.08.04.156.09.155.09a.913.913 0 0 0 1.32-.11.98.98 0 0 0-.102-1.347l-.002-.002c-.362-.318-.864-.504-.994-.552h-.002a5.8 5.8 0 0 0-2.047-.374h-.01c.206-2.15 1.91-3.836 4.023-3.836z'/></svg>",
"spwn": "<svg viewBox='0 0 24 24'><g transform='translate(13.512 10.42)scale(.06153)'><circle cx='30.125' cy='-29' r='110.12' fill='#e040fb'/><circle cx='-79.266' cy='80.375' r='110.12' fill='#00bfa5'/><path fill='#f5f5f5' d='m30.875-29.75-55.437-.001-55.437.001 27.624 27.624-57.45 57.45 56.063 56.063 57.45-57.45 27.188 27.188V25.686z' data-mit-no-recolor='true'/></g></svg>",
@@ -995,9 +1005,9 @@
"stencil": "<svg viewBox='0 0 32 32'><path fill='#448aff' d='m8 12-6 8h22l6-8zm6.5-8L10 10h11l4.5-6zm3 24 4.5-6H11l-4.5 6z'/></svg>",
"stitches": "<svg viewBox='0 0 64 64'><g fill='#cfd8dc' clip-rule='evenodd'><path d='M32 8.812C19.193 8.812 8.81 19.193 8.81 32S19.193 55.189 32 55.189 55.188 44.807 55.188 32 44.807 8.812 32 8.812M5.27 32C5.27 17.238 17.239 5.27 32 5.27S58.73 17.239 58.73 32 46.761 58.73 32 58.73 5.27 46.761 5.27 32'/><path d='M57.179 37.624 24.384 56.558l-.886-1.533L56.294 36.09zM40.826 9.3 8.031 28.236l-.885-1.533L39.941 7.767zm6.527 25.024a.887.887 0 0 1-.324 1.21L17.214 52.747a.885.885 0 0 1-.885-1.534L46.143 34a.885.885 0 0 1 1.21.324m.967-22.422c.245.424.1.965-.323 1.21L18.183 30.325a.885.885 0 0 1-.886-1.534l29.814-17.213a.885.885 0 0 1 1.21.324z'/><path d='M23.944 25.844a.885.885 0 0 1 1.239-.184L41.15 37.499a.885.885 0 1 1-1.054 1.422L24.128 27.08a.885.885 0 0 1-.184-1.238zm5.963-3.442a.885.885 0 0 1 1.238-.184l15.967 11.838a.885.885 0 0 1-1.054 1.422L30.09 23.64a.885.885 0 0 1-.183-1.239zM17.02 29.043a.885.885 0 0 1 1.235-.205l16.92 12.094a.885.885 0 1 1-1.03 1.44l-16.92-12.094a.885.885 0 0 1-.205-1.235'/></g></svg>",
"stitches_light": "<svg viewBox='0 0 64 64'><g fill='#546e7a' clip-rule='evenodd'><path d='M32 8.812C19.193 8.812 8.81 19.193 8.81 32S19.193 55.189 32 55.189 55.188 44.807 55.188 32 44.807 8.812 32 8.812M5.27 32C5.27 17.238 17.239 5.27 32 5.27S58.73 17.239 58.73 32 46.761 58.73 32 58.73 5.27 46.761 5.27 32'/><path d='M57.179 37.624 24.384 56.558l-.886-1.533L56.294 36.09zM40.826 9.3 8.031 28.236l-.885-1.533L39.941 7.767zm6.527 25.024a.887.887 0 0 1-.324 1.21L17.214 52.747a.885.885 0 0 1-.885-1.534L46.143 34a.885.885 0 0 1 1.21.324m.967-22.422c.245.424.1.965-.323 1.21L18.183 30.325a.885.885 0 0 1-.886-1.534l29.814-17.213a.885.885 0 0 1 1.21.324z'/><path d='M23.944 25.844a.885.885 0 0 1 1.239-.184L41.15 37.499a.885.885 0 1 1-1.054 1.422L24.128 27.08a.885.885 0 0 1-.184-1.238zm5.963-3.442a.885.885 0 0 1 1.238-.184l15.967 11.838a.885.885 0 0 1-1.054 1.422L30.09 23.64a.885.885 0 0 1-.183-1.239zM17.02 29.043a.885.885 0 0 1 1.235-.205l16.92 12.094a.885.885 0 1 1-1.03 1.44l-16.92-12.094a.885.885 0 0 1-.205-1.235'/></g></svg>",
- "storybook": "<svg viewBox='0 0 32 32'><path fill='#ff4081' d='m24.95 28.948-18-.9a1 1 0 0 1-.95-1V6.906a1 1 0 0 1 .9-.995l18-1.905A1 1 0 0 1 26 5v22.949a1 1 0 0 1-1.05.998Z'/><path fill='#FAFAFA' d='m20 8.52.19-4.242 3.649-.275.16 4.37a.28.28 0 0 1-.276.283.3.3 0 0 1-.188-.063L22.123 7.52l-1.668 1.23a.29.29 0 0 1-.398-.055A.27.27 0 0 1 20 8.52m-2.128 6.647c0 .487 3.448.25 3.912-.094 0-3.324-1.87-5.073-5.298-5.073-3.421 0-5.345 1.774-5.345 4.436 0 4.642 6.561 4.735 6.561 7.266a1.022 1.022 0 0 1-1.164 1.13c-1.047 0-1.459-.512-1.413-2.242 0-.375-3.984-.494-4.101 0-.308 4.198 2.426 5.41 5.56 5.41C19.619 26 22 24.45 22 21.658c0-4.973-6.653-4.842-6.653-7.31a1.08 1.08 0 0 1 1.243-1.13c.478 0 1.354.08 1.282 1.949'/></svg>",
+ "storybook": "<svg viewBox='0 0 32 32'><path fill='#ff4081' d='m24.95 28.948-18-.9a1 1 0 0 1-.95-1V6.906a1 1 0 0 1 .9-.995l18-1.905A1 1 0 0 1 26 5v22.949a1 1 0 0 1-1.05.998Z'/><path fill='#fafafa' d='m20 8.52.19-4.242 3.649-.275.16 4.37a.28.28 0 0 1-.276.283.3.3 0 0 1-.188-.063L22.123 7.52l-1.668 1.23a.29.29 0 0 1-.398-.055A.27.27 0 0 1 20 8.52m-2.128 6.647c0 .487 3.448.25 3.912-.094 0-3.324-1.87-5.073-5.298-5.073-3.421 0-5.345 1.774-5.345 4.436 0 4.642 6.561 4.735 6.561 7.266a1.022 1.022 0 0 1-1.164 1.13c-1.047 0-1.459-.512-1.413-2.242 0-.375-3.984-.494-4.101 0-.308 4.198 2.426 5.41 5.56 5.41C19.619 26 22 24.45 22 21.658c0-4.973-6.653-4.842-6.653-7.31a1.08 1.08 0 0 1 1.243-1.13c.478 0 1.354.08 1.282 1.949'/></svg>",
"stryker": "<svg viewBox='0 0 24 24'><g fill='#ef5350'><path d='M11.095 2.498v1.404a8.14 8.14 0 0 0-7.194 7.193H2.499v1.81h1.404a8.14 8.14 0 0 0 7.193 7.194v1.403h1.81v-1.403a8.14 8.14 0 0 0 7.193-7.194h1.404v-1.81h-1.404a8.14 8.14 0 0 0-7.193-7.193V2.498zm0 3.231v1.294h1.81V5.729a6.315 6.315 0 0 1 5.366 5.366h-1.294v1.81h1.294a6.315 6.315 0 0 1-5.366 5.366v-1.294h-1.81v1.294a6.315 6.315 0 0 1-5.366-5.366h1.294v-1.81H5.729a6.315 6.315 0 0 1 5.366-5.366' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;font-variation-settings:normal;inline-size:0;isolation:auto;mix-blend-mode:normal;shape-margin:0;shape-padding:0;text-decoration-color:#000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/><path d='M10.945 2.348V3.79a8.26 8.26 0 0 0-7.154 7.154H2.347v2.11h1.444a8.26 8.26 0 0 0 7.154 7.154v1.443h2.11V20.21a8.26 8.26 0 0 0 7.154-7.154h1.443v-2.11H20.21a8.26 8.26 0 0 0-7.155-7.154V2.348zm.299.3h1.512v1.39l.132.013a7.99 7.99 0 0 1 7.06 7.06l.015.133h1.388v1.512h-1.388l-.014.133a7.99 7.99 0 0 1-7.06 7.06l-.133.014v1.389h-1.512v-1.39l-.133-.013a7.99 7.99 0 0 1-7.06-7.06l-.014-.133H2.648v-1.512h1.389l.014-.133a7.99 7.99 0 0 1 7.06-7.06l.133-.014zm0 2.909-.17.023a6.466 6.466 0 0 0-5.494 5.494l-.024.17h1.317v1.512H5.556l.024.17a6.466 6.466 0 0 0 5.494 5.494l.17.023v-1.316h1.512v1.316l.17-.023a6.466 6.466 0 0 0 5.494-5.494l.023-.17h-1.316v-1.512h1.316l-.023-.17a6.466 6.466 0 0 0-5.495-5.494l-.17-.023v1.316h-1.511zm-.299.373v1.244h2.11V5.93a6.13 6.13 0 0 1 5.015 5.015h-1.244v2.11h1.244a6.13 6.13 0 0 1-5.016 5.015v-1.244h-2.109v1.244a6.13 6.13 0 0 1-5.016-5.015h1.244v-2.11H5.93a6.13 6.13 0 0 1 5.016-5.015z'/></g></svg>",
- "sty.clone": "<svg viewBox='0 0 1024 1024'><path fill='#7C4DFF' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "sty.clone": "<svg viewBox='0 0 1024 1024'><path fill='#b388ff' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
"stylable": "<svg viewBox='0 0 30 30'><g stroke-width='.247'><path fill='#66bb6a' d='M10.942 3.615c-1.944.827-6.386 5.085-6.401 8.44.026 3.467 4.457 7.612 6.401 8.439.446.19 1.137.451 1.868.451.666 0 1.23-.328 1.803-1.019.965-1.16.85-2.828-.436-3.775-1.539-1.133-2.798-2.149-2.824-4.097.026-1.947 1.285-3.307 2.824-4.44 1.285-.946 1.536-2.382.571-3.543-.574-.69-1.272-.908-1.938-.908-.73 0-1.422.262-1.868.452'/><path fill='#01579b' d='M12.711 8.934q-.532.61-.984 1.23c-.502.64-.934 1.314-1.284 1.992l-.004.004-.015.032-.026.052c-.44.874-.746 1.754-.892 2.582l-.002.01c-.568 2.648.237 4.923 2.625 6.025.944.329 1.753-.052 2.49-.89.853-.97.958-2.896-.38-3.82-1.878-1.296-2.836-2.532-2.825-4.116.011-1.57.887-2.627 1.297-3.101'/><path fill='#ff1744' d='M17.23 26.837c-.663 0-1.358-.218-1.929-.908-.96-1.16-.709-2.595.57-3.541 1.531-1.132 2.783-2.49 2.809-4.437-.026-1.947-1.28-3.24-2.809-4.377-1.13-.84-1.53-2.442-.57-3.602.571-.69 1.266-.908 1.93-.908.726 0 1.414.262 1.858.452 1.934.826 6.336 4.958 6.37 8.435-.01 3.277-4.436 7.608-6.37 8.435-.444.189-1.132.451-1.859.451'/><path fill='#b71c1c' d='M17.219 9.02c-.717-.011-1.419.324-1.975.953-.857.97-.713 2.711.633 3.636 1.887 1.296 2.787 2.782 2.776 4.366-.013 1.795-1.161 2.919-1.448 3.269a8.7 8.7 0 0 0 1.7-1.656 1.3 1.3 0 0 1-.106.172 8.3 8.3 0 0 0 1.248-2.026c.026-.056.058-.128.09-.207l.025-.06a9.3 9.3 0 0 0 .516-1.75c.135-.655.207-1.352.119-1.936q-.007-.089-.016-.176c-.158-1.946-1.09-3.65-2.845-4.457a2.3 2.3 0 0 0-.717-.128' style='mix-blend-mode:multiply'/></g></svg>",
"stylelint": "<svg viewBox='0 0 412 395'><g fill='#cfd8dc' transform='translate(31.478 29.499)scale(.84775)'><path d='M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z'/><path d='m288.26 14.688-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768M175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z'/><path d='M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z'/><circle cx='204.57' cy='122.54' r='14.231'/><circle cx='204.57' cy='207.16' r='14.231'/><circle cx='204.57' cy='291.78' r='14.23'/></g></svg>",
"stylelint_light": "<svg viewBox='0 0 412 395'><g fill='#546e7a' transform='translate(31.478 29.499)scale(.84775)'><path d='M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z'/><path d='m288.26 14.688-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768M175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z'/><path d='M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z'/><circle cx='204.57' cy='122.54' r='14.231'/><circle cx='204.57' cy='207.16' r='14.231'/><circle cx='204.57' cy='291.78' r='14.23'/></g></svg>",
@@ -1006,24 +1016,24 @@
"subtitles": "<svg fill='#ff9800' viewBox='0 0 32 32'><path d='M4.625 26q-1.083 0-1.854-.735C2 24.53 2 24.187 2 23.5V8.49q0-1.032.771-1.76A2.6 2.6 0 0 1 4.625 6h22.75q1.083 0 1.854.735C30 7.47 30 7.813 30 8.5v15.01q0 1.032-.771 1.76a2.6 2.6 0 0 1-1.854.73zM6 22h13v-4H6zm15 0h5v-4h-5zM6 16h5v-4H6zm7 0h13v-4H13z'/></svg>",
"supabase": "<svg viewBox='0 0 32 32'><path fill='#66bb6a' d='M29.92 12H16V2.85a1 1 0 0 0-1.78-.624L1.3 18.376A1 1 0 0 0 2.08 20H16v9.15a1 1 0 0 0 1.78.624l12.92-16.15A1 1 0 0 0 29.92 12'/></svg>",
"svelte": "<svg viewBox='0 0 300 300'><path fill='#ff5722' d='M175.94 24.328c-13.037.252-26.009 3.872-37.471 11.174L79.912 72.818a67.13 67.13 0 0 0-30.355 44.906 70.8 70.8 0 0 0 6.959 45.445 67.2 67.2 0 0 0-10.035 25.102 71.54 71.54 0 0 0 12.236 54.156c23.351 33.41 69.468 43.311 102.81 22.07l58.559-37.158a67.36 67.36 0 0 0 30.355-44.906 70.77 70.77 0 0 0-6.982-45.422 67.65 67.65 0 0 0 10.059-25.102 71.63 71.63 0 0 0-12.236-54.156v-.18c-15.324-21.925-40.453-33.727-65.342-33.246zm5.137 28.68a46.5 46.5 0 0 1 36.09 19.969 42.98 42.98 0 0 1 7.365 32.557 45 45 0 0 1-1.393 5.455l-1.123 3.37-2.986-2.247a75.9 75.9 0 0 0-22.902-11.45l-2.244-.651.201-2.246a13.16 13.16 0 0 0-2.379-8.711 13.99 13.99 0 0 0-14.953-5.412 12.8 12.8 0 0 0-3.594 1.572l-58.578 37.25a12.24 12.24 0 0 0-5.502 8.15 13.1 13.1 0 0 0 2.246 9.834 14.03 14.03 0 0 0 14.93 5.569 13.5 13.5 0 0 0 3.594-1.573l22.453-14.234a41.8 41.8 0 0 1 11.898-5.232 46.48 46.48 0 0 1 49.914 18.502 43.02 43.02 0 0 1 7.363 32.557 40.42 40.42 0 0 1-18.254 27.078l-58.58 37.316a43 43 0 0 1-11.898 5.23A46.545 46.545 0 0 1 82.81 227.14a42.98 42.98 0 0 1-7.341-32.557 38 38 0 0 1 1.39-5.41l1.102-3.37 3.008 2.246a75.9 75.9 0 0 0 22.836 11.361l2.244.65-.201 2.247a13.25 13.25 0 0 0 2.447 8.644 14.03 14.03 0 0 0 15.043 5.569 13.1 13.1 0 0 0 3.592-1.573l58.467-37.316a12.17 12.17 0 0 0 5.502-8.173 12.96 12.96 0 0 0-2.246-9.811 14.03 14.03 0 0 0-15.043-5.568 12.8 12.8 0 0 0-3.592 1.57l-22.453 14.258a42.9 42.9 0 0 1-11.877 5.209 46.52 46.52 0 0 1-49.846-18.5 43.02 43.02 0 0 1-7.297-32.557A40.42 40.42 0 0 1 96.798 96.98l58.646-37.316a42.8 42.8 0 0 1 11.811-5.21 46.5 46.5 0 0 1 13.822-1.444z'/></svg>",
- "svelte_js.clone": "<svg viewBox='0 0 300 300'><path fill='#FFCA28' d='M175.94 24.328c-13.037.252-26.009 3.872-37.471 11.174L79.912 72.818a67.13 67.13 0 0 0-30.355 44.906 70.8 70.8 0 0 0 6.959 45.445 67.2 67.2 0 0 0-10.035 25.102 71.54 71.54 0 0 0 12.236 54.156c23.351 33.41 69.468 43.311 102.81 22.07l58.559-37.158a67.36 67.36 0 0 0 30.355-44.906 70.77 70.77 0 0 0-6.982-45.422 67.65 67.65 0 0 0 10.059-25.102 71.63 71.63 0 0 0-12.236-54.156v-.18c-15.324-21.925-40.453-33.727-65.342-33.246zm5.137 28.68a46.5 46.5 0 0 1 36.09 19.969 42.98 42.98 0 0 1 7.365 32.557 45 45 0 0 1-1.393 5.455l-1.123 3.37-2.986-2.247a75.9 75.9 0 0 0-22.902-11.45l-2.244-.651.201-2.246a13.16 13.16 0 0 0-2.379-8.711 13.99 13.99 0 0 0-14.953-5.412 12.8 12.8 0 0 0-3.594 1.572l-58.578 37.25a12.24 12.24 0 0 0-5.502 8.15 13.1 13.1 0 0 0 2.246 9.834 14.03 14.03 0 0 0 14.93 5.569 13.5 13.5 0 0 0 3.594-1.573l22.453-14.234a41.8 41.8 0 0 1 11.898-5.232 46.48 46.48 0 0 1 49.914 18.502 43.02 43.02 0 0 1 7.363 32.557 40.42 40.42 0 0 1-18.254 27.078l-58.58 37.316a43 43 0 0 1-11.898 5.23A46.545 46.545 0 0 1 82.81 227.14a42.98 42.98 0 0 1-7.341-32.557 38 38 0 0 1 1.39-5.41l1.102-3.37 3.008 2.246a75.9 75.9 0 0 0 22.836 11.361l2.244.65-.201 2.247a13.25 13.25 0 0 0 2.447 8.644 14.03 14.03 0 0 0 15.043 5.569 13.1 13.1 0 0 0 3.592-1.573l58.467-37.316a12.17 12.17 0 0 0 5.502-8.173 12.96 12.96 0 0 0-2.246-9.811 14.03 14.03 0 0 0-15.043-5.568 12.8 12.8 0 0 0-3.592 1.57l-22.453 14.258a42.9 42.9 0 0 1-11.877 5.209 46.52 46.52 0 0 1-49.846-18.5 43.02 43.02 0 0 1-7.297-32.557A40.42 40.42 0 0 1 96.798 96.98l58.646-37.316a42.8 42.8 0 0 1 11.811-5.21 46.5 46.5 0 0 1 13.822-1.444z'/></svg>",
- "svelte_ts.clone": "<svg viewBox='0 0 300 300'><path fill='#0288D1' d='M175.94 24.328c-13.037.252-26.009 3.872-37.471 11.174L79.912 72.818a67.13 67.13 0 0 0-30.355 44.906 70.8 70.8 0 0 0 6.959 45.445 67.2 67.2 0 0 0-10.035 25.102 71.54 71.54 0 0 0 12.236 54.156c23.351 33.41 69.468 43.311 102.81 22.07l58.559-37.158a67.36 67.36 0 0 0 30.355-44.906 70.77 70.77 0 0 0-6.982-45.422 67.65 67.65 0 0 0 10.059-25.102 71.63 71.63 0 0 0-12.236-54.156v-.18c-15.324-21.925-40.453-33.727-65.342-33.246zm5.137 28.68a46.5 46.5 0 0 1 36.09 19.969 42.98 42.98 0 0 1 7.365 32.557 45 45 0 0 1-1.393 5.455l-1.123 3.37-2.986-2.247a75.9 75.9 0 0 0-22.902-11.45l-2.244-.651.201-2.246a13.16 13.16 0 0 0-2.379-8.711 13.99 13.99 0 0 0-14.953-5.412 12.8 12.8 0 0 0-3.594 1.572l-58.578 37.25a12.24 12.24 0 0 0-5.502 8.15 13.1 13.1 0 0 0 2.246 9.834 14.03 14.03 0 0 0 14.93 5.569 13.5 13.5 0 0 0 3.594-1.573l22.453-14.234a41.8 41.8 0 0 1 11.898-5.232 46.48 46.48 0 0 1 49.914 18.502 43.02 43.02 0 0 1 7.363 32.557 40.42 40.42 0 0 1-18.254 27.078l-58.58 37.316a43 43 0 0 1-11.898 5.23A46.545 46.545 0 0 1 82.81 227.14a42.98 42.98 0 0 1-7.341-32.557 38 38 0 0 1 1.39-5.41l1.102-3.37 3.008 2.246a75.9 75.9 0 0 0 22.836 11.361l2.244.65-.201 2.247a13.25 13.25 0 0 0 2.447 8.644 14.03 14.03 0 0 0 15.043 5.569 13.1 13.1 0 0 0 3.592-1.573l58.467-37.316a12.17 12.17 0 0 0 5.502-8.173 12.96 12.96 0 0 0-2.246-9.811 14.03 14.03 0 0 0-15.043-5.568 12.8 12.8 0 0 0-3.592 1.57l-22.453 14.258a42.9 42.9 0 0 1-11.877 5.209 46.52 46.52 0 0 1-49.846-18.5 43.02 43.02 0 0 1-7.297-32.557A40.42 40.42 0 0 1 96.798 96.98l58.646-37.316a42.8 42.8 0 0 1 11.811-5.21 46.5 46.5 0 0 1 13.822-1.444z'/></svg>",
+ "svelte_js.clone": "<svg viewBox='0 0 300 300'><path fill='#ffca28' d='M175.94 24.328c-13.037.252-26.009 3.872-37.471 11.174L79.912 72.818a67.13 67.13 0 0 0-30.355 44.906 70.8 70.8 0 0 0 6.959 45.445 67.2 67.2 0 0 0-10.035 25.102 71.54 71.54 0 0 0 12.236 54.156c23.351 33.41 69.468 43.311 102.81 22.07l58.559-37.158a67.36 67.36 0 0 0 30.355-44.906 70.77 70.77 0 0 0-6.982-45.422 67.65 67.65 0 0 0 10.059-25.102 71.63 71.63 0 0 0-12.236-54.156v-.18c-15.324-21.925-40.453-33.727-65.342-33.246zm5.137 28.68a46.5 46.5 0 0 1 36.09 19.969 42.98 42.98 0 0 1 7.365 32.557 45 45 0 0 1-1.393 5.455l-1.123 3.37-2.986-2.247a75.9 75.9 0 0 0-22.902-11.45l-2.244-.651.201-2.246a13.16 13.16 0 0 0-2.379-8.711 13.99 13.99 0 0 0-14.953-5.412 12.8 12.8 0 0 0-3.594 1.572l-58.578 37.25a12.24 12.24 0 0 0-5.502 8.15 13.1 13.1 0 0 0 2.246 9.834 14.03 14.03 0 0 0 14.93 5.569 13.5 13.5 0 0 0 3.594-1.573l22.453-14.234a41.8 41.8 0 0 1 11.898-5.232 46.48 46.48 0 0 1 49.914 18.502 43.02 43.02 0 0 1 7.363 32.557 40.42 40.42 0 0 1-18.254 27.078l-58.58 37.316a43 43 0 0 1-11.898 5.23A46.545 46.545 0 0 1 82.81 227.14a42.98 42.98 0 0 1-7.341-32.557 38 38 0 0 1 1.39-5.41l1.102-3.37 3.008 2.246a75.9 75.9 0 0 0 22.836 11.361l2.244.65-.201 2.247a13.25 13.25 0 0 0 2.447 8.644 14.03 14.03 0 0 0 15.043 5.569 13.1 13.1 0 0 0 3.592-1.573l58.467-37.316a12.17 12.17 0 0 0 5.502-8.173 12.96 12.96 0 0 0-2.246-9.811 14.03 14.03 0 0 0-15.043-5.568 12.8 12.8 0 0 0-3.592 1.57l-22.453 14.258a42.9 42.9 0 0 1-11.877 5.209 46.52 46.52 0 0 1-49.846-18.5 43.02 43.02 0 0 1-7.297-32.557A40.42 40.42 0 0 1 96.798 96.98l58.646-37.316a42.8 42.8 0 0 1 11.811-5.21 46.5 46.5 0 0 1 13.822-1.444z'/></svg>",
+ "svelte_ts.clone": "<svg viewBox='0 0 300 300'><path fill='#0288d1' d='M175.94 24.328c-13.037.252-26.009 3.872-37.471 11.174L79.912 72.818a67.13 67.13 0 0 0-30.355 44.906 70.8 70.8 0 0 0 6.959 45.445 67.2 67.2 0 0 0-10.035 25.102 71.54 71.54 0 0 0 12.236 54.156c23.351 33.41 69.468 43.311 102.81 22.07l58.559-37.158a67.36 67.36 0 0 0 30.355-44.906 70.77 70.77 0 0 0-6.982-45.422 67.65 67.65 0 0 0 10.059-25.102 71.63 71.63 0 0 0-12.236-54.156v-.18c-15.324-21.925-40.453-33.727-65.342-33.246zm5.137 28.68a46.5 46.5 0 0 1 36.09 19.969 42.98 42.98 0 0 1 7.365 32.557 45 45 0 0 1-1.393 5.455l-1.123 3.37-2.986-2.247a75.9 75.9 0 0 0-22.902-11.45l-2.244-.651.201-2.246a13.16 13.16 0 0 0-2.379-8.711 13.99 13.99 0 0 0-14.953-5.412 12.8 12.8 0 0 0-3.594 1.572l-58.578 37.25a12.24 12.24 0 0 0-5.502 8.15 13.1 13.1 0 0 0 2.246 9.834 14.03 14.03 0 0 0 14.93 5.569 13.5 13.5 0 0 0 3.594-1.573l22.453-14.234a41.8 41.8 0 0 1 11.898-5.232 46.48 46.48 0 0 1 49.914 18.502 43.02 43.02 0 0 1 7.363 32.557 40.42 40.42 0 0 1-18.254 27.078l-58.58 37.316a43 43 0 0 1-11.898 5.23A46.545 46.545 0 0 1 82.81 227.14a42.98 42.98 0 0 1-7.341-32.557 38 38 0 0 1 1.39-5.41l1.102-3.37 3.008 2.246a75.9 75.9 0 0 0 22.836 11.361l2.244.65-.201 2.247a13.25 13.25 0 0 0 2.447 8.644 14.03 14.03 0 0 0 15.043 5.569 13.1 13.1 0 0 0 3.592-1.573l58.467-37.316a12.17 12.17 0 0 0 5.502-8.173 12.96 12.96 0 0 0-2.246-9.811 14.03 14.03 0 0 0-15.043-5.568 12.8 12.8 0 0 0-3.592 1.57l-22.453 14.258a42.9 42.9 0 0 1-11.877 5.209 46.52 46.52 0 0 1-49.846-18.5 43.02 43.02 0 0 1-7.297-32.557A40.42 40.42 0 0 1 96.798 96.98l58.646-37.316a42.8 42.8 0 0 1 11.811-5.21 46.5 46.5 0 0 1 13.822-1.444z'/></svg>",
"svg": "<svg viewBox='0 0 32 32'><path fill='#ffb300' d='M29.168 14.03a2.7 2.7 0 0 0-1.968-.83 2.51 2.51 0 0 0-1.929.8h-4.443l3.078-3.078a2.835 2.835 0 0 0 2.857-2.842 2.6 2.6 0 0 0-.831-1.969 2.82 2.82 0 0 0-2.014-.788 2.67 2.67 0 0 0-1.968.788 2.36 2.36 0 0 0-.812 1.922L18 11.17V6.726a2.51 2.51 0 0 0 .8-1.929 2.7 2.7 0 0 0-.832-1.968 2.745 2.745 0 0 0-3.936 0 2.7 2.7 0 0 0-.832 1.968 2.51 2.51 0 0 0 .8 1.93v4.443l-3.138-3.138a2.36 2.36 0 0 0-.812-1.922 2.66 2.66 0 0 0-1.968-.788 2.83 2.83 0 0 0-2.014.788 2.6 2.6 0 0 0-.831 1.969 2.74 2.74 0 0 0 .831 2.013 2.8 2.8 0 0 0 2.026.829l3.078 3.078H6.729a2.51 2.51 0 0 0-1.929-.8 2.7 2.7 0 0 0-1.968.831 2.745 2.745 0 0 0 0 3.937 2.7 2.7 0 0 0 1.968.832 2.51 2.51 0 0 0 1.929-.8h4.443l-3.078 3.077a2.835 2.835 0 0 0-2.857 2.842 2.6 2.6 0 0 0 .831 1.969 2.82 2.82 0 0 0 2.014.788 2.67 2.67 0 0 0 1.968-.788 2.36 2.36 0 0 0 .812-1.922L14 20.827v4.444a2.51 2.51 0 0 0-.8 1.929 2.784 2.784 0 0 0 4.768 1.968A2.7 2.7 0 0 0 18.8 27.2a2.51 2.51 0 0 0-.8-1.929v-4.444l3.138 3.138a2.36 2.36 0 0 0 .812 1.922 2.66 2.66 0 0 0 1.968.788 2.83 2.83 0 0 0 2.014-.788 2.6 2.6 0 0 0 .831-1.969 2.74 2.74 0 0 0-.831-2.013 2.8 2.8 0 0 0-2.026-.829L20.828 18h4.443a2.51 2.51 0 0 0 1.93.8 2.784 2.784 0 0 0 1.967-4.769Z'/></svg>",
"svgo": "<svg viewBox='0 0 100 100'><path fill='#0288d1' d='M42.951 7.647c-.659 2.54-1.157 5.016-1.581 7.52a36.3 36.3 0 0 0-10.041 3.98c-2.023-1.43-4.084-2.814-6.268-4.084l-9.994 9.928a83 83 0 0 0 3.84 5.929 36.5 36.5 0 0 0-4.668 10.568c-2.202.395-4.385.847-6.587 1.43v14.107c2.051.546 4.084.97 6.135 1.336a36.4 36.4 0 0 0 4.527 11.5 70 70 0 0 0-3.237 5.091l9.985 9.985c1.693-.978 3.303-2.032 4.874-3.115a36.2 36.2 0 0 0 11.792 4.913 74 74 0 0 0 1.233 5.618h14.173c.499-1.92.904-3.84 1.261-5.75a36.5 36.5 0 0 0 11.443-4.988c1.694 1.148 3.388 2.259 5.138 3.303l9.976-9.994a82.09 82.081 0 0 0-3.548-5.486 36.5 36.5 0 0 0 4.235-11.011c2.24-.414 4.47-.857 6.71-1.45V42.908c-2.362-.63-4.725-1.11-7.143-1.515a36.8 36.8 0 0 0-4.357-10.069 86 86 0 0 0 4.14-6.342l-10.013-9.966c-2.268 1.336-4.423 2.776-6.53 4.281a36 36 0 0 0-9.76-4.027c-.423-2.541-.931-5.082-1.609-7.623zm6.7 16.544h.076a26.4 26.4 0 0 1 9.137 1.619c.669.244 1.318.508 1.967.846a26.8 26.8 0 0 1 12.855 12.545c.254.518.49 1.045.716 1.572a26.6 26.6 0 0 1 .498 18.925c-.103.301-.207.593-.32.847a26.85 26.85 0 0 1-13.947 14.653l-.178.122a26.73 26.73 0 0 1-20.986.198.1.1 0 0 0 0-.047A26.83 26.83 0 0 1 24.9 60.517l-.132-.358a26.66 26.66 0 0 1 .527-19.913c.15-.357.31-.715.49-1.063a26.9 26.9 0 0 1 13.456-12.846 26 26 0 0 1 1.779-.687 26.7 26.7 0 0 1 8.63-1.459zm8.686 6.474a3.68 3.68 0 0 0-3.52 4.894 3.6 3.6 0 0 0 .914 1.402l-3.388 5.873a7.44 7.44 0 0 0-3.341-.612 7.33 7.33 0 0 0-6.098 3.943l-7.265-2.851v-.076a3.388 3.388 0 1 0-.64 1.657l7.265 2.851a7.34 7.34 0 0 0 2.475 7.491l-3.388 5.289a4.4 4.4 0 0 0-1.92-.32 4.517 4.517 0 1 0 3.388 1.233l3.388-5.27a7.34 7.34 0 0 0 8.526-1.544l4.235 3.162a5.364 5.364 0 1 0 10.004 1.402 5.364 5.364 0 0 0-9.006-2.757L55.74 53.29a7.31 7.31 0 0 0-1.929-9.57l3.388-5.873a3.68 3.68 0 1 0 1.148-7.18z'/></svg>",
"svgr": "<svg viewBox='0 0 32 32'><path fill='#ffb300' d='M16 12c7.444 0 12 2.59 12 4s-4.556 4-12 4-12-2.59-12-4 4.556-4 12-4m0-2c-7.732 0-14 2.686-14 6s6.268 6 14 6 14-2.686 14-6-6.268-6-14-6'/><path fill='#ffb300' d='M16 14a2 2 0 1 0 2 2 2 2 0 0 0-2-2'/><path fill='#ffb300' d='M10.458 5.507c2.017 0 5.937 3.177 9.006 8.493 3.722 6.447 3.757 11.687 2.536 12.392a.9.9 0 0 1-.457.1c-2.017 0-5.938-3.176-9.007-8.492C8.814 11.553 8.779 6.313 10 5.608a.9.9 0 0 1 .458-.1m-.001-2A2.87 2.87 0 0 0 9 3.875C6.13 5.532 6.938 12.304 10.804 19c3.284 5.69 7.72 9.493 10.74 9.493A2.87 2.87 0 0 0 23 28.124c2.87-1.656 2.062-8.428-1.804-15.124-3.284-5.69-7.72-9.493-10.74-9.493Z'/><path fill='#ffb300' d='M21.543 5.507a.9.9 0 0 1 .457.1c1.221.706 1.186 5.946-2.536 12.393-3.07 5.316-6.99 8.493-9.007 8.493a.9.9 0 0 1-.457-.1C8.779 25.686 8.814 20.446 12.536 14c3.07-5.316 6.99-8.493 9.007-8.493m0-2c-3.02 0-7.455 3.804-10.74 9.493C6.939 19.696 6.13 26.468 9 28.124a2.87 2.87 0 0 0 1.457.369c3.02 0 7.455-3.804 10.74-9.493C25.061 12.304 25.87 5.532 23 3.876a2.87 2.87 0 0 0-1.457-.369'/></svg>",
"swagger": "<svg preserveAspectRatio='xMidYMid' viewBox='0 0 256 256'><path fill='#43a047' d='M128.963 17.002a112 112 0 0 0-11.422.51 110 110 0 0 0-11.09 1.642 110 110 0 0 0-25.906 8.668 111.6 111.6 0 0 0-14.024 8.022 113 113 0 0 0-16.535 13.564A113.3 113.3 0 0 0 33.41 70.29a112 112 0 0 0-7.43 14.26 110 110 0 0 0-5.318 15.305 108.7 108.7 0 0 0-3.66 27.277 112 112 0 0 0 .521 11.53 109.3 109.3 0 0 0 8.21 32.204A111.3 111.3 0 0 0 42.546 198.4a113 113 0 0 0 15.129 15.073c2.747 2.264 5.6 4.398 8.547 6.394a113 113 0 0 0 13.867 8.022 110.7 110.7 0 0 0 20.101 7.486 108.6 108.6 0 0 0 16.004 2.984c3.626.398 7.294.614 10.99.639a110 110 0 0 0 22.471-2.162c3.636-.733 7.21-1.646 10.713-2.729a111 111 0 0 0 24.781-11.146 113 113 0 0 0 21.174-16.555 113 113 0 0 0 7.252-7.924 112 112 0 0 0 12.086-17.744 110 110 0 0 0 4.717-9.697 108 108 0 0 0 3.75-10.166 106.8 106.8 0 0 0 4.37-21.498 107 107 0 0 0 .498-11.207 107.6 107.6 0 0 0-2.115-22.271 107.5 107.5 0 0 0-6.42-20.772 110 110 0 0 0-7.344-14.328c-1.854-3.048-3.853-6-5.985-8.85a113 113 0 0 0-18.336-19.263 113 113 0 0 0-17.631-12.008 112 112 0 0 0-9.635-4.713 110.5 110.5 0 0 0-15.302-5.305 109 109 0 0 0-27.266-3.658zm-39.49 45.004c1.55.014 3.15.049 4.847.049v14.09c-1.397.099-2.677.301-3.949.263-8.58-.263-9.024 2.661-9.65 9.764-.391 4.454.15 8.984-.155 13.453-.317 4.447-.912 8.87-1.78 13.244-1.239 6.339-5.135 11.054-10.536 15.055 10.484 6.822 11.677 17.423 12.357 28.187.367 5.785.199 11.61.786 17.366.457 4.467 2.195 5.608 6.808 5.744 1.902.056 3.806.013 6 .013v13.787c-13.635 2.306-24.867-1.566-27.623-13.091a76.5 76.5 0 0 1-1.736-12.887c-.293-4.592.215-9.235-.135-13.818-.97-12.612-2.604-16.918-14.707-17.514v-15.695a23 23 0 0 1 2.633-.454c6.635-.326 9.432-2.36 10.916-8.896a74.6 74.6 0 0 0 1.193-11.123c.526-7.217.34-14.55 1.543-21.65 1.738-10.268 8.112-15.257 18.64-15.815 1.499-.08 2.998-.086 4.548-.072m81.525.088c11.709.34 19.4 6.373 20.26 19.207.444 6.633.378 13.297.803 19.93a55 55 0 0 0 1.613 10.693c1.458 5.447 4.297 7.361 10.031 7.623.94.043 1.874.203 3.162.346v15.691q-1.046.344-2.137.512c-7.684.478-11.186 3.631-11.962 11.336-.496 4.918-.455 9.892-.795 14.828a126 126 0 0 1-1.477 16.18c-1.96 9.703-8.02 14.543-18.03 15.134-3.221.19-6.465.03-9.939.03v-14.026c1.87-.116 3.52-.275 5.174-.314 5.98-.143 8.096-2.072 8.389-8.012.324-6.525.464-13.057.756-19.584.423-9.434 3.008-17.862 11.797-23.746-5.03-3.585-9.068-7.928-10.114-13.783-1.265-7.098-1.672-14.35-2.353-21.547-.337-3.598-.32-7.228-.672-10.822-.379-3.881-3.044-5.224-6.576-5.311-2.024-.049-4.056-.01-6.643-.01V62.754c3.096-.514 6.011-.739 8.713-.66m-73.96 56.695h.161a9.08 9.08 0 0 1 8.928 9.227 8.884 8.884 0 0 1-9.396 8.852 9.045 9.045 0 1 1 .307-18.078zm31.235 0c5.483-.042 9.124 3.51 9.153 8.93.03 5.565-3.423 9.127-8.87 9.15-5.539.025-9.186-3.48-9.216-8.867a8.67 8.67 0 0 1 8.934-9.213zm31.807 0c5.045.025 9.52 4.29 9.248 9.168-.284 5.29-4.906 9.683-9.46 8.916h-.07a9.134 9.134 0 0 1-9.146-9.123 9.277 9.277 0 0 1 9.428-8.96z'/></svg>",
"sway": "<svg viewBox='0 0 32 32'><path fill='#00e676' d='M4 6v22h19a2.4 2.4 157.5 0 0 1.707-.707l2.586-2.586A2.4 2.4 112.5 0 0 28 23V4H6a2 2 135 0 0-2 2'/><path fill='#263238' d='m18 8-6.69 7.244c-.171.186-.44.506-.656.633-.224.131-.457.157-.681.06-.23-.1-.432-.47-.517-.708-.602-1.696-1.04-3.489-1.415-5.268C7.814 8.88 8.543 8 9.647 8zm-8 15.436c-.8.855-1.997.755-2-.416V19c-.019-.47.53-.987 1-1h6zM16.045 16h-2.82L20 8.765c.373-.42 1.438-.76 2-.765h2l-6.486 7.33a2 2 0 0 1-1.469.67'/></svg>",
- "swc": "<svg fill='none' viewBox='0 0 16 16'><path fill='#FFCA28' d='m2.327 5.5-.07.01a1 1 0 0 0-.15.023 1 1 0 0 0-.158.044l-.098.04a1.4 1.4 0 0 0-.564.456l-.07.096c-.052.07-.164.348-.185.461-.038.2-.042.421-.012.569.05.246.123.411.263.602.035.048.288.316.563.595.358.365.503.523.52.559a.266.266 0 0 1-.134.346c-.041.018-.14.022-.367.023-.145 0-.288.006-.317.012a.58.58 0 0 0-.453.552.6.6 0 0 0 .259.514c.117.082.202.097.55.097.26 0 .41-.007.52-.032l.064-.013c.106-.02.414-.165.465-.22a1.6 1.6 0 0 0 .318-.323 1.6 1.6 0 0 0 .264-.683c.01-.084.01-.298.001-.347-.003-.017-.011-.064-.02-.106a1.5 1.5 0 0 0-.266-.592c-.037-.048-.29-.314-.563-.593-.542-.554-.543-.554-.53-.673.022-.184.198-.29.359-.215.038.016.466.448 1.721 1.726.918.938 1.689 1.72 1.711 1.738q.065.052.134.098c.494.33 1.132.31 1.614-.046.076-.056.223-.206.282-.285l.041-.056.135.136c.119.119.15.149.206.194.007.005.045.033.087.06.467.316 1.124.31 1.593-.017.082-.057.11-.08.197-.166a1.5 1.5 0 0 0 .398-.778c.018-.091.02-.408.005-.485a1.6 1.6 0 0 0-.262-.618 12 12 0 0 0-.553-.588 20 20 0 0 1-.519-.54.3.3 0 0 1-.02-.23.3.3 0 0 1 .116-.14c.033-.019.078-.021.388-.025l.35-.003-.01.064c-.011.075-.01.358 0 .416l.014.078c.016.082.058.21.101.304.108.236.094.22 1.29 1.442a53 53 0 0 0 1.204 1.21c.201.16.384.243.634.289a1.6 1.6 0 0 0 .388.012 1.42 1.42 0 0 0 1.233-1.26c.029-.281-.042-.476-.222-.62a.56.56 0 0 0-.635-.04.6.6 0 0 0-.29.495c-.009.18-.144.286-.31.244-.056-.013-.098-.055-1.13-1.105-.59-.6-1.086-1.115-1.103-1.142a.27.27 0 0 1 .047-.315c.064-.062.103-.07.355-.07.283 0 .365-.017.487-.102a.592.592 0 0 0-.06-1.014c-.12-.063-.104-.062-1.425-.063-1.31 0-1.376.002-1.526.043l-.065.016a2 2 0 0 0-.223.09c-.156.08-.366.255-.478.396-.077.1-.18.297-.225.429-.146.433-.086.888.165 1.271.06.092.165.206.588.64.283.29.524.542.535.562.042.084.02.214-.05.291-.058.065-.095.079-.2.079-.077 0-.103-.005-.135-.026a167 167 0 0 1-1.826-1.845c-1.345-1.371-1.803-1.83-1.854-1.862a.58.58 0 0 0-.432-.067.61.61 0 0 0-.429.455.63.63 0 0 0 .092.449c.017.026.573.6 1.235 1.275 1.096 1.12 1.205 1.232 1.218 1.281a.28.28 0 0 1-.049.254c-.058.082-.212.114-.31.062-.022-.013-.803-.8-1.735-1.748-1.344-1.37-1.712-1.738-1.785-1.787a1.4 1.4 0 0 0-.775-.257c-.063 0-.116-.002-.117 0z'/></svg>",
- "swift": "<svg viewBox='0 0 24 24'><path fill='#FF6E40' d='M17.087 19.721c-2.36 1.36-5.59 1.5-8.86.1a13.8 13.8 0 0 1-6.23-5.32c.67.55 1.46 1 2.3 1.4 3.37 1.57 6.73 1.46 9.1 0-3.37-2.59-6.24-5.96-8.37-8.71-.45-.45-.78-1.01-1.12-1.51 8.28 6.05 7.92 7.59 2.41-1.01 4.89 4.94 9.43 7.74 9.43 7.74.16.09.25.16.36.22.1-.25.19-.51.26-.78.79-2.85-.11-6.12-2.08-8.81 4.55 2.75 7.25 7.91 6.12 12.24-.03.11-.06.22-.05.39 2.24 2.83 1.64 5.78 1.35 5.22-1.21-2.39-3.48-1.65-4.62-1.17'/></svg>",
+ "swc": "<svg fill='none' viewBox='0 0 16 16'><path fill='#ffca28' d='m2.327 5.5-.07.01a1 1 0 0 0-.15.023 1 1 0 0 0-.158.044l-.098.04a1.4 1.4 0 0 0-.564.456l-.07.096c-.052.07-.164.348-.185.461-.038.2-.042.421-.012.569.05.246.123.411.263.602.035.048.288.316.563.595.358.365.503.523.52.559a.266.266 0 0 1-.134.346c-.041.018-.14.022-.367.023-.145 0-.288.006-.317.012a.58.58 0 0 0-.453.552.6.6 0 0 0 .259.514c.117.082.202.097.55.097.26 0 .41-.007.52-.032l.064-.013c.106-.02.414-.165.465-.22a1.6 1.6 0 0 0 .318-.323 1.6 1.6 0 0 0 .264-.683c.01-.084.01-.298.001-.347-.003-.017-.011-.064-.02-.106a1.5 1.5 0 0 0-.266-.592c-.037-.048-.29-.314-.563-.593-.542-.554-.543-.554-.53-.673.022-.184.198-.29.359-.215.038.016.466.448 1.721 1.726.918.938 1.689 1.72 1.711 1.738q.065.052.134.098c.494.33 1.132.31 1.614-.046.076-.056.223-.206.282-.285l.041-.056.135.136c.119.119.15.149.206.194.007.005.045.033.087.06.467.316 1.124.31 1.593-.017.082-.057.11-.08.197-.166a1.5 1.5 0 0 0 .398-.778c.018-.091.02-.408.005-.485a1.6 1.6 0 0 0-.262-.618 12 12 0 0 0-.553-.588 20 20 0 0 1-.519-.54.3.3 0 0 1-.02-.23.3.3 0 0 1 .116-.14c.033-.019.078-.021.388-.025l.35-.003-.01.064c-.011.075-.01.358 0 .416l.014.078c.016.082.058.21.101.304.108.236.094.22 1.29 1.442a53 53 0 0 0 1.204 1.21c.201.16.384.243.634.289a1.6 1.6 0 0 0 .388.012 1.42 1.42 0 0 0 1.233-1.26c.029-.281-.042-.476-.222-.62a.56.56 0 0 0-.635-.04.6.6 0 0 0-.29.495c-.009.18-.144.286-.31.244-.056-.013-.098-.055-1.13-1.105-.59-.6-1.086-1.115-1.103-1.142a.27.27 0 0 1 .047-.315c.064-.062.103-.07.355-.07.283 0 .365-.017.487-.102a.592.592 0 0 0-.06-1.014c-.12-.063-.104-.062-1.425-.063-1.31 0-1.376.002-1.526.043l-.065.016a2 2 0 0 0-.223.09c-.156.08-.366.255-.478.396-.077.1-.18.297-.225.429-.146.433-.086.888.165 1.271.06.092.165.206.588.64.283.29.524.542.535.562.042.084.02.214-.05.291-.058.065-.095.079-.2.079-.077 0-.103-.005-.135-.026a167 167 0 0 1-1.826-1.845c-1.345-1.371-1.803-1.83-1.854-1.862a.58.58 0 0 0-.432-.067.61.61 0 0 0-.429.455.63.63 0 0 0 .092.449c.017.026.573.6 1.235 1.275 1.096 1.12 1.205 1.232 1.218 1.281a.28.28 0 0 1-.049.254c-.058.082-.212.114-.31.062-.022-.013-.803-.8-1.735-1.748-1.344-1.37-1.712-1.738-1.785-1.787a1.4 1.4 0 0 0-.775-.257c-.063 0-.116-.002-.117 0z'/></svg>",
+ "swift": "<svg viewBox='0 0 24 24'><path fill='#ff6e40' d='M17.087 19.721c-2.36 1.36-5.59 1.5-8.86.1a13.8 13.8 0 0 1-6.23-5.32c.67.55 1.46 1 2.3 1.4 3.37 1.57 6.73 1.46 9.1 0-3.37-2.59-6.24-5.96-8.37-8.71-.45-.45-.78-1.01-1.12-1.51 8.28 6.05 7.92 7.59 2.41-1.01 4.89 4.94 9.43 7.74 9.43 7.74.16.09.25.16.36.22.1-.25.19-.51.26-.78.79-2.85-.11-6.12-2.08-8.81 4.55 2.75 7.25 7.91 6.12 12.24-.03.11-.06.22-.05.39 2.24 2.83 1.64 5.78 1.35 5.22-1.21-2.39-3.48-1.65-4.62-1.17'/></svg>",
"syncpack": "<svg viewBox='0 0 100 100'><path fill='#8bc34a' d='M46.677 7.772a38.8 38.8 0 0 1 10.83.571 45 45 0 0 1 7.226 2.046 53 53 0 0 1 4.89 2.263c3.022 1.662 5.888 3.666 8.338 6.106a51 51 0 0 1 3.572 3.893 47 47 0 0 1 3.354 4.953 38.2 38.2 0 0 1 3.935 9.47 20 20 0 0 1 .862 5.18c-.478-.353-.852-.82-1.257-1.256-2.523-2.845-4.942-5.794-7.413-8.701-1.111-1.267-2.18-2.596-3.395-3.769-.956.83-1.662 1.89-2.482 2.845-2.326 2.783-4.651 5.565-7.008 8.317a10.2 10.2 0 0 0-1.392-2.866 22.4 22.4 0 0 0-3.177-3.883 18.75 18.75 0 0 0-12.21-5.244 18.9 18.9 0 0 0-5.607.468 16.9 16.9 0 0 0-4.496 1.599 18.2 18.2 0 0 0-6.822 5.939 15.3 15.3 0 0 0-2.284 4.506c3.24.01 6.48-.02 9.719 0-.924 1.318-1.994 2.523-3.011 3.769-2.607 3.115-5.275 6.209-7.891 9.345a100 100 0 0 1-3.136 3.81c-1.734 2.18-3.572 4.278-5.4 6.375a9 9 0 0 1-.83-.727c-1.267-1.266-2.44-2.616-3.624-3.955-3.83-4.413-7.538-8.93-11.286-13.405-1.017-1.204-2.014-2.43-3.084-3.572-.415-.467-.81-.945-1.142-1.474 2.856 0 5.71-.042 8.556-.083a36.1 36.1 0 0 1 3.956-11.889 42 42 0 0 1 5.243-7.527 39.3 39.3 0 0 1 6.126-5.514 40.6 40.6 0 0 1 20.35-7.58z'/><path fill='#2e7d32' d='M77.567 36.502c.727.55 1.318 1.246 1.952 1.89 2.544 2.762 4.953 5.648 7.372 8.514 2.637 3.136 5.243 6.302 7.89 9.428.904 1.11 1.974 2.087 2.763 3.291-2.856.02-5.71.042-8.566.093a36.3 36.3 0 0 1-5.752 14.827c-.758 1.163-1.558 2.305-2.44 3.385A40.48 40.48 0 0 1 48.13 92.342a45 45 0 0 1-7.715-1.142 50 50 0 0 1-5.534-1.745 45 45 0 0 1-6.157-3.063 35 35 0 0 1-6.956-5.42c-1.143-1.142-2.254-2.325-3.281-3.582a47 47 0 0 1-3.354-4.932 38.3 38.3 0 0 1-3.998-9.635c-.446-1.661-.83-3.343-.861-5.067.415.239.706.633 1.038.976 2.73 3.053 5.316 6.23 7.995 9.345.997 1.142 1.941 2.346 3.052 3.385.841-.727 1.485-1.62 2.18-2.471a829 829 0 0 1 7.31-8.691c.291 1.11.893 2.108 1.506 3.063a19.74 19.74 0 0 0 7.278 6.749 18 18 0 0 0 4.89 1.734c1.911.405 3.884.612 5.826.436 1.35-.104 2.689-.384 3.997-.685a19.1 19.1 0 0 0 7.912-4.569 20.4 20.4 0 0 0 3.188-4.08c.591-.997 1.1-2.046 1.412-3.157H58.14c.665-.976 1.433-1.869 2.18-2.762 2.617-3.167 5.296-6.271 7.933-9.417 1.205-1.391 2.285-2.887 3.52-4.267.976-1.163 1.91-2.378 2.907-3.53.956-1.122 1.942-2.212 2.908-3.323z'/></svg>",
"systemd": "<svg viewBox='0 0 16 16'><path fill='#00e676' d='m9 8 3-2v4z'/><circle cx='6' cy='8' r='2' fill='#00e676'/><path fill='#eceff1' d='M3 5H1v6h2v-1H2V6h1zm10 0h2v6h-2v-1h1V6h-1z'/></svg>",
"systemd_light": "<svg viewBox='0 0 16 16'><path fill='#00c853' d='m9 8 3-2v4z'/><circle cx='6' cy='8' r='2' fill='#00c853'/><path fill='#455a64' d='M3 5H1v6h2v-1H2V6h1zm10 0h2v6h-2v-1h1V6h-1z'/></svg>",
"table": "<svg viewBox='0 0 24 24'><path fill='#8bc34a' d='M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5zm4 7.5h-4v2h1l-2 1.67L10 13h1v-2H7v2h1l3 2.5L8 18H7v2h4v-2h-1l2-1.67L14 18h-1v2h4v-2h-1l-3-2.5 3-2.5h1z'/></svg>",
"tailwindcss": "<svg viewBox='0 0 32 32'><path fill='#4db6ac' d='M23.5 12H8c.89-2.3 4.02-4 7.75-4s6.86 1.7 7.75 4M14 12h15.5c-.89 2.3-4.02 4-7.75 4s-6.86-1.7-7.75-4m3.5 8H2c.89-2.3 4.02-4 7.75-4s6.86 1.7 7.75 4M8 20h15.5c-.89 2.3-4.02 4-7.75 4S8.89 22.3 8 20'/></svg>",
- "tape.clone": "<svg viewBox='0 0 32 32'><path fill='#BA68C8' d='m24 6 2 6h-4l-2-6h-3l2 6h-4l-2-6h-3l2 6H8L6 6H5a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h22a3 3 0 0 0 3-3V6Z'/></svg>",
+ "tape.clone": "<svg viewBox='0 0 32 32'><path fill='#ba68c8' d='m24 6 2 6h-4l-2-6h-3l2 6h-4l-2-6h-3l2 6H8L6 6H5a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h22a3 3 0 0 0 3-3V6Z'/></svg>",
"taskfile": "<svg viewBox='0 0 32 32'><path fill='#4db6ac' d='M4 9v14l12 6V15z'/><path fill='#b2dfdb' d='M16 3 4 9l12 6 12-6z'/><path fill='#80cbc4' d='M16 15v14l12-6V9z'/></svg>",
"tauri": "<svg viewBox='0 0 256 256'><path fill='#ffca28' d='M165.09 99.183a20.153 20.153 0 0 1-34.401 14.25 20.152 20.152 0 1 1 34.4-14.25z'/><path fill='#26c6da' d='M111.04 136.74c-11.13 0-20.152 9.022-20.152 20.151 0 11.13 9.022 20.152 20.152 20.152 11.129 0 20.152-9.022 20.152-20.152 0-11.129-9.023-20.15-20.152-20.15z'/><path fill='#ffca28' d='M186.7 163.76a77 77 0 0 1-26.564 10.81 54.04 54.04 0 0 0 2.657-24.366 54.05 54.05 0 0 0 33.856-35.246 54.04 54.04 0 0 0-27.73-64.21 54.044 54.044 0 0 0-67.863 16.927 89.8 89.8 0 0 0-29.495 8.61 76.94 76.94 0 0 1 86.49-53.058 76.94 76.94 0 0 1 63.888 78.829 76.94 76.94 0 0 1-35.24 61.706zM72.48 90.298l18.869 2.29a54 54 0 0 1 2.381-10.717 77 77 0 0 0-21.25 8.427' clip-rule='evenodd'/><path fill='#26c6da' d='M69.182 92.314a76.9 76.9 0 0 1 26.747-10.9 53.95 53.95 0 0 0-3.023 24.457 54.05 54.05 0 0 0-33.64 35.358 54.04 54.04 0 0 0 52.354 69.6 54.04 54.04 0 0 0 43.298-22.52 89.8 89.8 0 0 0 29.495-8.518 76.94 76.94 0 0 1-50.002 50.423 76.94 76.94 0 0 1-69.992-11.992A76.944 76.944 0 0 1 69.18 92.315zm114.22 73.462-.366.183z' clip-rule='evenodd'/></svg>",
- "taze": "<svg viewBox='0 0 16 16'><path fill='#00C853' d='M9.196 10.555a.465.465 0 0 0-.654-.14l-2.417 1.62 1.038-2.108a.5.5 0 0 0-.195-.657.47.47 0 0 0-.643.209l-1.049 2.123-.165-2.95a.477.477 0 0 0-.497-.454.48.48 0 0 0-.444.508l.093 1.665c.039.698-.103 1.4-.41 2.023l-.473.962a.66.66 0 0 0 .283.877l1.355.698c.312.16.697.035.858-.29l.473-.961a3.9 3.9 0 0 1 1.345-1.54l1.365-.917a.484.484 0 0 0 .137-.668'/><path fill='#00695C' d='M12.9 6.175c.454-1.85-.385-3.832-2.12-4.73-1.735-.892-3.792-.398-4.977 1.072a3.066 3.066 0 0 0-3.47 1.654c-.77 1.56-.157 3.459 1.37 4.246a3.05 3.05 0 0 0 1.666.324 1.96 1.96 0 0 0 1.048 1.575 1.88 1.88 0 0 0 1.857-.075c.288.489.712.902 1.248 1.176 1.526.788 3.383.16 4.153-1.4a3.234 3.234 0 0 0-.775-3.842'/></svg>",
+ "taze": "<svg viewBox='0 0 16 16'><path fill='#00c853' d='M9.196 10.555a.465.465 0 0 0-.654-.14l-2.417 1.62 1.038-2.108a.5.5 0 0 0-.195-.657.47.47 0 0 0-.643.209l-1.049 2.123-.165-2.95a.477.477 0 0 0-.497-.454.48.48 0 0 0-.444.508l.093 1.665c.039.698-.103 1.4-.41 2.023l-.473.962a.66.66 0 0 0 .283.877l1.355.698c.312.16.697.035.858-.29l.473-.961a3.9 3.9 0 0 1 1.345-1.54l1.365-.917a.484.484 0 0 0 .137-.668'/><path fill='#00695c' d='M12.9 6.175c.454-1.85-.385-3.832-2.12-4.73-1.735-.892-3.792-.398-4.977 1.072a3.066 3.066 0 0 0-3.47 1.654c-.77 1.56-.157 3.459 1.37 4.246a3.05 3.05 0 0 0 1.666.324 1.96 1.96 0 0 0 1.048 1.575 1.88 1.88 0 0 0 1.857-.075c.288.489.712.902 1.248 1.176 1.526.788 3.383.16 4.153-1.4a3.234 3.234 0 0 0-.775-3.842'/></svg>",
"tcl": "<svg viewBox='0 0 24 24'><path fill='#ef5350' d='M21.492 2.51S14.24 2.157 8.526 9.988c-4.385 6.008-6.018 11.504-6.018 11.504l1.842-.95c1.366-2.372 2.078-3.35 3.417-4.745 2.401.702 4.907.617 7.08-1.899-1.898-.53-3.416-.408-5.657-.18C11.706 12 13.424 11.62 15.797 12l.949-1.898c-1.709-.323-2.848-.352-4.537.038 1.87-1.32 3.17-2.06 5.486-1.937l1.148-1.832c-1.48-.104-2.372.057-4.072.475C16.3 5.46 17.695 4.834 19.726 4.71c0 0 .997-1.793 1.766-2.202z'/></svg>",
"teal": "<svg viewBox='0 0 32 32'><path fill='#00acc1' d='M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2m8 14h-8V8h8Z'/></svg>",
"templ": "<svg viewBox='0 0 32 32'><path fill='#ffd54f' d='M8.125 24.313 1 16l7.125-8.313 3 2.625L6.25 16l4.875 5.688ZM24 7.687 31.125 16 24 24.313l-3-2.625L25.875 16 21 10.312ZM16 4l-4 24h4l4-24z'/></svg>",
@@ -1032,7 +1042,7 @@
"test-js": "<svg viewBox='0 0 32 32'><path fill='#ffca28' d='M20 4v2h-2v4.531l.264.461 7.473 13.078a2 2 0 0 1 .263.992V26a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-.938a2 2 0 0 1 .264-.992l7.473-13.078.263-.46V6h-2V4zm0-2h-8a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2v2L4.527 23.078A4 4 0 0 0 4 25.062V26a4 4 0 0 0 4 4h16a4 4 0 0 0 4-4v-.938a4 4 0 0 0-.527-1.984L20 10V8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2'/><circle cx='17' cy='17' r='1' fill='#ffca28'/><path fill='#ffca28' d='M19.72 20.715a1 1 0 0 0-1.134-.318 5 5 0 0 1-1.18.262 3.95 3.95 0 0 1-1.862-.292 2.74 2.74 0 0 0-3.371.489 2 2 0 0 0-.237.35L10 24h12Z'/></svg>",
"test-jsx": "<svg viewBox='0 0 32 32'><path fill='#00bcd4' d='M20 4v2h-2v4.531l.264.461 7.473 13.078a2 2 0 0 1 .263.992V26a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-.938a2 2 0 0 1 .264-.992l7.473-13.078.263-.46V6h-2V4zm0-2h-8a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2v2L4.527 23.078A4 4 0 0 0 4 25.062V26a4 4 0 0 0 4 4h16a4 4 0 0 0 4-4v-.938a4 4 0 0 0-.527-1.984L20 10V8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2'/><circle cx='17' cy='17' r='1' fill='#00bcd4'/><path fill='#00bcd4' d='M19.72 20.715a1 1 0 0 0-1.134-.318 5 5 0 0 1-1.18.262 3.95 3.95 0 0 1-1.862-.292 2.74 2.74 0 0 0-3.371.489 2 2 0 0 0-.237.35L10 24h12Z'/></svg>",
"test-ts": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M20 4v2h-2v4.531l.264.461 7.473 13.078a2 2 0 0 1 .263.992V26a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-.938a2 2 0 0 1 .264-.992l7.473-13.078.263-.46V6h-2V4zm0-2h-8a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2v2L4.527 23.078A4 4 0 0 0 4 25.062V26a4 4 0 0 0 4 4h16a4 4 0 0 0 4-4v-.938a4 4 0 0 0-.527-1.984L20 10V8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2'/><circle cx='17' cy='17' r='1' fill='#0288d1'/><path fill='#0288d1' d='M19.72 20.715a1 1 0 0 0-1.134-.318 5 5 0 0 1-1.18.262 3.95 3.95 0 0 1-1.862-.292 2.74 2.74 0 0 0-3.371.489 2 2 0 0 0-.237.35L10 24h12Z'/></svg>",
- "tex": "<svg viewBox='0 0 1024 1024'><path fill='#42a5f5' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
+ "tex": "<svg viewBox='0 0 1024 1024'><path fill='#2196f3' d='M80 192 64 320h32c16-80 16-96 63.242-96H176c8.837 0 16 7.163 16 16v352c0 8.837 0 16-32 16h-32v32h192v-32h-32c-32 0-32-7.163-32-16V240c0-8.837 7.163-16 16-16h16c48 0 48 16 64 96h32l-16-128zm560 0v32c16 0 45.713 0 52.57 16L776 434.666 708.57 592c-6.857 16-52.57 16-68.57 16v32h128v-32s-34.285 0-27.428-16L792 472l51.428 120c3.103 7.24-1.52 16-11.428 16v32h128v-32c-16 0-45.713 0-52.57-16L824 397.334 891.43 240c6.857-16 52.57-16 68.57-16v-32H832v32s34.285 0 27.428 16L808 360l-51.428-120c-3.103-7.24 1.52-16 11.428-16v-32zM320 384v32h32c32 0 32 7.163 32 16v352c0 8.837 0 16-32 16h-32v32h304l16-128h-32c-16 80-16 96-64 96h-64c-32 0-32-7.163-32-16V624h80c8.837 0 16 0 16 32v16h32V544h-32v16c0 32-7.163 32-16 32h-80V432c0-8.837 0-16 32-16h64c48 0 48 16 64 96h32l-16-128z'/></svg>",
"textlint": "<svg viewBox='0 0 32 32'><path fill='#f06292' d='M10 22V8H4v20h24v-6z'/><path fill='#00e5ff' d='M14 8h4v20h-4z'/><path fill='#00e5ff' d='M4 4h24v6H4z'/></svg>",
"tilt": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='M14 5v22a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V13a1 1 0 0 1 1-1h4a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H15a1 1 0 0 0-1 1M4.47 7.706l5.694-3.559A1.2 1.2 0 0 1 12 5.165V11a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8.554a1 1 0 0 1 .47-.848'/></svg>",
"tldraw": "<svg viewBox='0 0 24 24'><path fill='#b0bec5' d='M14.69 5.75q0 1.058-.808 1.796c-.808.738-1.194.738-1.966.738q-1.195 0-2.002-.738-.808-.737-.808-1.797 0-1.058.808-1.796c.808-.738 1.206-.738 2.002-.738q1.159 0 1.966.738.808.738.808 1.796zm-5.619 8.883q0-1.059.808-1.797.842-.77 2.037-.77a2.82 2.82 0 0 1 1.966.77q.843.738.984 1.668.28 1.733-.703 3.434-.948 1.7-2.74 2.598-.982.514-1.615-.032-.596-.513.352-1.219.525-.353.878-.898.351-.546.456-1.123.036-.257-.246-.257-.702-.032-1.44-.706a2.17 2.17 0 0 1-.737-1.668'/></svg>",
@@ -1044,13 +1054,13 @@
"toml_light": "<svg viewBox='0 0 16 16'><path fill='#455a64' d='M4 6V4h8v2H9v7H7V6z'/><path fill='#ef5350' d='M4 1v1H2v12h2v1H1V1zm8 0v1h2v12h-2v1h3V1z'/></svg>",
"travis": "<svg viewBox='0 0 200 200'><path fill='#cb3349' d='M53.341 89.17s-27.564 19.4-28.641 26.791l2.068-.425S59.776 93.66 85.942 90.991l.592-3.321zm43.27-2.773-21.712 15.4 1.174.941c.885-.714 38.567-12.222 38.567-12.222l7.944-4.981c-5.33.664-25.973.862-25.973.862m18.869 18.627c15.417 0 38.527-15.1 38.527-15.1l-7.47-1.422c-.37.37-12.047-.409-12.047-.409l-5.544-1.54-13.243 15.091-1.078 2.619c.952-.387.854.76.854.76zm50.313 48.152-4.763 1.34-22.006-.475-13.766-10.705-16.64 4.163-19.244-1.665-10.777 11.776-20.22 6.04-10.097-3.13-.509-.438 4.532 11.097s10.265 11.005 15.999 12.299c5.733 1.294 16.09-.093 23.859-1.296 7.767-1.202 13.963-3.976 16.46-8.506 2.496-4.533 2.866-5.827 2.866-5.827s7.4 10.45 13.78 11.653c6.38 1.202 25.338-5.273 25.338-5.273s11.56-3.143 13.593-7.12c2.035-3.976 7.4-17.015 7.4-17.015zM50.4 162.216l-.32-.147c-.758-.655-1.378-1.19-1.783-1.538z'/><path fill='#f4edae' d='M184.483 74.565a43 43 0 0 0-.186-1.78 24 24 0 0 0-.19-1.26c-1.588-1.337-3.394-2.526-5.273-3.607-2.036-1.19-4.166-2.255-6.341-3.244-2.16-1.02-4.378-1.941-6.609-2.841q-1.672-.68-3.364-1.309c-1.124-.438-2.259-.846-3.397-1.26 2.389.395 4.753.937 7.1 1.541 1.526.395 3.044.837 4.552 1.309-10.728-28.04-36.38-46.301-64.386-46.301-28.01 0-53.659 18.26-64.385 46.3a97 97 0 0 1 4.55-1.308c2.35-.604 4.716-1.146 7.103-1.542-1.142.415-2.274.823-3.402 1.261q-1.685.63-3.364 1.309c-2.231.9-4.443 1.822-6.608 2.84-2.17.99-4.302 2.056-6.339 3.245-1.879 1.08-3.682 2.27-5.277 3.606-.066.405-.133.843-.186 1.262-.071.59-.137 1.185-.186 1.78a46 46 0 0 0-.156 3.597 52.5 52.5 0 0 0 .443 7.183c.327 2.385.765 4.754 1.412 7.043.32 1.147.68 2.279 1.099 3.373a29 29 0 0 0 1.042 2.374l.153.291c.176.09.361.18.537.266l1.067.509c.594.27 1.417.66 2.097.966a2 2 0 0 0 .11-.101l-1.98-7.541c.42-.115 4.179-1.085 10.658-2.318a32 32 0 0 1-5.91-1.723c-.7-.295-1.388-.618-2.045-.988-.657-.377-1.304-.804-1.804-1.371 7.118 2.322 21.826 1.27 34.9-.08 11.957-1.233 23.942-2.032 35.99-2.056 12.048.024 24.037.823 35.99 2.055 13.08 1.351 27.787 2.403 34.904.08-.5.568-1.145.996-1.802 1.372a20 20 0 0 1-2.045.988 32 32 0 0 1-4.34 1.385q-.328.077-.657.147c7.104 1.318 11.24 2.39 11.682 2.508l-1.894 7.184c.38-.172.765-.338 1.142-.509l1.065-.51q.266-.129.538-.265l.147-.29q.194-.372.371-.766c.24-.523.468-1.062.677-1.608a37 37 0 0 0 1.1-3.373c.647-2.29 1.084-4.659 1.412-7.043.306-2.379.466-4.782.444-7.184-.01-1.201-.053-2.4-.159-3.596'/><path fill='#e6ccad' d='M114.47 174.284c-.943.142-1.885.262-2.823.332-.604.03-1.243.09-1.823.09l-.215.005a516 516 0 0 0 1.7-3.844c.853.957 1.938 2.141 3.16 3.417zm2.046 2.091c2.926 2.898 6.553 4.558 10.544 4.853-6.704 2.723-13.023 3.774-17.89 4.101-4.853.325-9.772-.247-14.593-1.568.404-.073.7-.125 1.057-.168.396-.048 9.04-1.175 12.937-6.97l.366.009.957-.008c.647 0 1.235-.053 1.854-.077a39 39 0 0 0 4.321-.623q.22.226.447.45z'/><path fill='#656c67' d='M124.807 89.611c-.28.366-.595.77-.928 1.19-1.713 2.165-4.088 4.934-6.904 7.642a327 327 0 0 0-10.102-.157c-5.722 0-11.21.152-16.42.405 7.362-3.055 16.206-6.04 25.785-7.832 2.797-.53 5.66-.953 8.569-1.248m-43.085 3.297c-2.627 1.846-5.595 4.202-8.473 7.06a319 319 0 0 0-19.104 2.49c8.155-3.75 17.462-7.2 27.577-9.55m90.05 15.216-2.998 20.975-14.36 10.058-37.454-4.258-5.639-18.567a1.23 1.23 0 0 0-.981-.86c-1.388-.225-2.616-.339-3.639-.339-1.027 0-2.25.114-3.645.339a1.24 1.24 0 0 0-.98.86l-5.5 18.114-37.245 8.289-14.759-10.34-2.908-23.537q.95-.537 1.927-1.07 1.372-.296 3.079-.624l2.74 22.21c.043.347.234.656.52.856l10.152 7.115a1.26 1.26 0 0 0 .976.188l30.622-6.813a1.23 1.23 0 0 0 .909-.842l5.514-18.158c1.479-.38 5.268-1.266 8.597-1.266 3.321 0 7.113.885 8.593 1.266l5.514 18.157c.143.472.553.808 1.042.866l30.622 3.478c.297.033.6-.044.844-.214l10.159-7.109c.274-.195.46-.501.507-.838l2.713-18.974c2.038.393 3.741.75 5.078 1.038m3.782-13.155-2.636 10c-5.39-1.188-17.234-3.52-33.354-5.09 5.267-2.246 10.63-5.063 15.53-8.584 9.592 1.356 16.635 2.807 20.46 3.674'/><path fill='#e5caa3' d='M67.567 126.256c2.088-1.128 4.448-.96 8.14-.956.397.01.805.005 1.232-.004.363-.004.728-.01 1.114-.02 3.806-.037 6.97.325 6.915-5.652-.058-5.975-2.712-10.805-6.514-10.767-3.806.033-7.489 4.929-7.261 10.9.062 1.556.29 2.679.657 3.488-3.317.831-4.231 2.883-4.283 3.011m45.758-24.58a41 41 0 0 1-4.65 3.288l-5.4 3.26a46 46 0 0 0-6.494 1.3 1.25 1.25 0 0 0-.838.827l-5.5 18.123-29.395 6.537-9.259-6.485-2.693-21.82c5.258-.966 12.29-2.084 20.736-3.03a50 50 0 0 0-2.617 3.415l-5.029 7.218 7.322-4.881c.11-.076 4.772-3.144 12.4-6.875a334 334 0 0 1 31.417-.877m32.192 22.135c-.037-.141-.837-2.545-4.378-2.123.409-.888.59-2.087.462-3.73-.468-5.961-3.936-10.7-7.738-10.568-3.802.135-6.238 5.077-6.028 11.053.21 5.972 3.402 5.93 7.204 5.796 5.11-.095 7.918-1.665 10.478-.428m18.761-17.17-2.65 18.551-9.336 6.537-29.356-3.336-5.477-18.038a1.25 1.25 0 0 0-.837-.827c-.182-.053-3.816-1.09-7.67-1.424l.704-.047c.609-.043 10.734-1.285 22.825-5.453 13.388 1.028 24.322 2.675 31.797 4.036z'/><path fill='#c7b39a' d='M67.567 126.256c2.088-1.128 4.448-.96 8.14-.956.397.01.805.005 1.232-.004.363-.004.728-.01 1.114-.02 3.806-.037 6.97.325 6.915-5.652-.058-5.975-2.712-10.805-6.514-10.767-3.806.033-7.489 4.929-7.261 10.9.062 1.556.29 2.679.657 3.488-3.317.831-4.231 2.883-4.283 3.011m45.758-24.58a41 41 0 0 1-4.65 3.288l-5.4 3.26a46 46 0 0 0-6.494 1.3 1.25 1.25 0 0 0-.838.827l-5.5 18.123-29.395 6.537-9.259-6.485-2.693-21.82c5.258-.966 12.29-2.084 20.736-3.03a50 50 0 0 0-2.617 3.415l-5.029 7.218 7.322-4.881c.11-.076 4.772-3.144 12.4-6.875a334 334 0 0 1 31.417-.877m32.192 22.135c-.037-.141-.837-2.545-4.378-2.123.409-.888.59-2.087.462-3.73-.468-5.961-3.936-10.7-7.738-10.568-3.802.135-6.238 5.077-6.028 11.053.21 5.972 3.402 5.93 7.204 5.796 5.11-.095 7.918-1.665 10.478-.428m18.761-17.17-2.65 18.551-9.336 6.537-29.356-3.336-5.477-18.038a1.25 1.25 0 0 0-.837-.827c-.182-.053-3.816-1.09-7.67-1.424l.704-.047c.609-.043 10.734-1.285 22.825-5.453 13.388 1.028 24.322 2.675 31.797 4.036z'/><g fill='#e6ccad'><path d='M177.087 114.794c.45.371.807 1.332.404 5.809-.495 5.481-2.8 15.102-4.226 16.653-1.575.305-4.877-.057-6.89-.527.348-1.381.447-2.043.728-3.455l3.521-2.463c.28-.195.466-.501.514-.834l2.46-17.243c1.494.808 3.03 1.683 3.489 2.06M47.279 137.302q.265.938.553 1.86c-1.895.557-5.754 1.09-7.499.785-1.446-1.457-4.144-11.485-4.734-16.81-.481-4.315-.144-5.248.295-5.611.51-.428 2.416-1.513 4.068-2.398l2.232 18.114c.042.348.227.656.519.856zm71.23.248-.134 1.156s-4.058 3.763-10.59 4.605c-6.533.842-11.667-3.173-11.667-3.173.214.698.519 2.274 1.998 3.62a47 47 0 0 0-5.592-.348c-.712 0-1.432.02-2.127.054-4.02.21-9.676 6.289-13.654 12.413-1.951.618-8.05 2.554-15.111 4.64-6.59-9.132-9.831-18.463-10.16-19.428l.005-.005c-.133-.372-.267-.744-.39-1.124l1.694 1.186v.004l4.186 2.931 1.4.975c.209.148.456.224.703.224q.135 0 .27-.027l38.475-8.566c.431-.094.78-.419.908-.841l5.486-18.062a19 19 0 0 1 2.493-.195c.71 0 1.547.067 2.494.195l5.646 18.6c.143.47.552.814 1.037.865z'/><path d='m162.427 136.558 1.242-.872a77 77 0 0 1-.662 2.566c-1.726 5.534-3.765 10.43-6.028 14.754-.5.034-1.028.054-1.594.054-1.747 0-3.531-.148-5.506-.33-3.426-.314-8.864-.936-10.581-1.136-1.48-1.18-6.542-5.202-10.754-8.517-.166-.133-.308-.257-.447-.372-.742-.627-1.585-1.34-3.293-1.34-1.55 0-3.816.565-9.29 2.222 2.844-2.127 2.863-4.872 2.863-4.872l.134-1.156 36.094 4.1c.294.034.599-.042.846-.213l3.627-2.54z'/></g><path fill='#ebd599' d='M135.862 60.616c.11 3.21.029 9.03-1.466 14.478a1.2 1.2 0 0 0 0 .624 234 234 0 0 0-5.71-.524c2.136-4.477 2.602-9.244 2.703-10.923 1.64-1.167 3.274-2.566 4.473-3.655M81.4 64.27c.098 1.68.564 6.437 2.697 10.91q-2.856.228-5.705.528a1.2 1.2 0 0 0 0-.615c-1.494-5.438-1.57-11.262-1.465-14.478 1.198 1.09 2.825 2.489 4.472 3.655z'/><path fill='#2d3136' d='M145.517 123.811c-2.56-1.236-5.367.334-10.477.43-3.802.133-6.995.174-7.204-5.797-.21-5.976 2.226-10.918 6.028-11.053 3.802-.133 7.27 4.606 7.738 10.569.128 1.642-.054 2.84-.462 3.729 3.54-.423 4.34 1.98 4.377 2.122m-11.948-8.75a1.95 1.95 0 0 0 1.908-1.98 1.937 1.937 0 0 0-1.98-1.906 1.944 1.944 0 0 0 .072 3.887z'/><circle cx='131.439' cy='115.529' r='1.943' fill='#edf6fa' transform='rotate(-1.049)'/><path fill='#2d3136' d='M84.967 119.622c.056 5.976-3.108 5.615-6.914 5.652-.386.01-.752.014-1.115.02q-.64.017-1.231.003c-3.692-.004-6.053-.172-8.14.956.05-.128.965-2.18 4.282-3.012-.366-.81-.596-1.932-.657-3.487-.228-5.971 3.455-10.868 7.261-10.9 3.802-.038 6.457 4.792 6.514 10.767zm-5.876-3.126a1.944 1.944 0 1 0-.109-3.887 1.944 1.944 0 0 0 .109 3.887'/><circle cx='76.07' cy='116.551' r='1.944' fill='#edf6fa' transform='rotate(-1.473)'/><path fill='#ebd599' fill-opacity='.8' d='M55.92 79.056s.59-7.7 1.797-10.65c.622-1.52 4.426-2.489 7.331-3.18 2.904-.692 7.537-2.075 8.437 2.005s2.35 7.053 3.318 8.367l-1.244-16.803-19.985 4.84-1.315 14.869.623 1.175 1.037-.623m101.131 0s-.59-7.7-1.797-10.65c-.623-1.52-4.427-2.489-7.33-3.18-2.906-.692-7.538-2.075-8.438 2.005-.898 4.08-2.351 7.053-3.32 8.367l1.246-16.803 19.985 4.84 1.314 14.869-.623 1.175z' opacity='.8'/><path fill='#ebd599' fill-opacity='.75' d='M128.793 28.763s-.53 40.295-9.942 40.295H94.379c-9.413 0-10.173-40.277-10.173-40.277l-4.624 13.675.076 18.764 3.283 2.61 1.493 8.058 2.088 4.7 40.288.075 1.864-3.358 1.567-5.67.225-3.58 4.45-12.704zm29.647 37.572s1.938 13.9 7.064 16.407l-7.178-2.165zm-104.1 0s-1.938 13.9-7.065 16.407l7.179-2.165z' opacity='.75'/><path fill='#2d3136' d='M159.823 80.513c2.44.8 4.894 1.646 7.118 2.946-1.19-.472-2.418-.809-3.655-1.114a62 62 0 0 0-3.721-.818 116 116 0 0 0-7.528-1.176 169 169 0 0 0-7.57-.818c-2.531-.214-5.057-.428-7.594-.576-10.139-.624-20.306-.78-30.48-.79-10.172.005-20.34.196-30.48.795-2.535.147-5.062.362-7.593.576s-5.058.486-7.57.818c-2.522.305-5.03.705-7.527 1.175q-1.871.35-3.726.814c-1.232.305-2.465.642-3.655 1.114 2.223-1.299 4.677-2.15 7.123-2.95.205-.067.414-.124.623-.19-.014-.053-.029-.1-.038-.157-.043-.281-.947-6.962.628-16.516.076-.457.404-.828.852-.952l2.584-.736c.561-20.042 8.815-29.467 9.073-29.758-7.375 11.867-7.308 25.942-7.18 29.22l16.057-4.565.038-.008q.1-.022.2-.029c.018 0 .043-.005.066-.005.082-.004.173.005.263.024.014 0 .028.01.047.014.067.014.129.037.199.066.029.015.047.03.076.044q.078.044.157.1c.014.009.03.013.043.028l.028.029c.015.014.033.024.048.039.01.014.438.436 1.118 1.07-.161-5.29.09-20.469 7.319-33.049-.153.441-5.887 17.21-3.911 36.004a30 30 0 0 0 1.974 1.46c.318.22.514.58.518.966.006.062.143 6.053 2.789 11.149q.054.115.09.237c6.58-.447 13.18-.628 19.764-.632 6.587.004 13.18.194 19.76.647.024-.086.053-.17.096-.252 2.645-5.096 2.784-11.087 2.784-11.15.009-.385.204-.746.523-.965a32 32 0 0 0 1.97-1.46c1.979-18.794-3.756-35.563-3.907-36.004 7.222 12.58 7.48 27.758 7.318 33.05a55 55 0 0 0 1.117-1.071c.014-.015.035-.024.047-.04.01-.008.016-.018.03-.028.014-.014.029-.02.043-.029l.157-.104.071-.039c.072-.029.138-.052.21-.07.014 0 .029-.01.043-.01.085-.019.177-.028.262-.024.024 0 .043.005.062.005.066.005.138.014.205.029.01.005.024.005.037.009l16.058 4.563c.13-3.277.196-17.353-7.178-29.219.256.29 8.512 9.717 9.068 29.758l2.588.736c.442.124.775.495.852.952 1.574 9.554.666 16.235.627 16.516q-.015.085-.037.161c.212.062.417.123.627.19m-2.92-.857c.123-1.07.698-6.917-.524-14.882l-18.177-5.167c.163 3.078.235 9.754-1.512 16.12a1.4 1.4 0 0 1-.09.229c.18.019.362.033.537.057 5.11.585 10.197 1.403 15.226 2.507 1.517.352 3.035.729 4.539 1.136zm-22.508-4.563c1.495-5.447 1.576-11.267 1.466-14.478-1.199 1.09-2.832 2.488-4.473 3.654-.1 1.68-.567 6.447-2.703 10.923 1.903.154 3.807.325 5.71.524a1.2 1.2 0 0 1 0-.623m-50.296.087c-2.133-4.473-2.599-9.232-2.698-10.91-1.646-1.166-3.274-2.565-4.473-3.654-.105 3.216-.028 9.04 1.466 14.478.057.21.051.42 0 .615q2.85-.301 5.705-.529m-7.913.765a1.5 1.5 0 0 1-.087-.218c-1.75-6.367-1.673-13.042-1.516-16.121l-18.171 5.167c-1.223 7.956-.652 13.803-.53 14.879 1.51-.41 3.022-.786 4.546-1.138 2.511-.556 5.043-1.022 7.579-1.45 2.541-.424 5.09-.747 7.64-1.057.182-.024.358-.037.54-.062z'/><path fill='#2d3136' d='M125.542 29.29v15.081h-10.69v-4.406h-2.41v20.357h3.64v11.382H97.385V60.322h3.625V39.966h-2.401v4.406H87.925V29.29zm-3.212 11.877v-8.671h-31.2v8.671h4.266v-4.406h8.826v26.773h-3.632V68.5h12.282v-4.966h-3.632V36.761h8.826v4.406z'/><path fill='#cb3349' d='M104.22 36.761h-8.823v4.406h-4.266v-8.672h31.205v8.672h-4.27v-4.406h-8.823v26.773h3.632V68.5H100.59v-4.966h3.632z'/><path fill='#656c67' d='M38.206 94.969c1.558-.354 3.663-.804 6.246-1.302a93 93 0 0 0-5.512 4.072z'/><g fill='#c7b39a'><path d='M95.266 136.301s.436 2.804-3.488 4.003c-3.922 1.2-28.773 8.606-31.607 8.205s-10.026-5.203-10.026-5.203l-1.745-6.204 10.644 6.636zm22.26-.541s-.314 2.82 3.659 3.85c3.972 1.028 29.12 7.347 31.933 6.824s9.79-5.635 9.79-5.635l1.474-6.275-9.784 5.836z'/><path d='M36.184 131.146s3.443 6.887 9.005 4.502l3.973 1.457-.397 2.915-8.343 1.058-2.25-1.323zm140.965-1.896s-5.628 7.188-11.19 4.805l-1.788 1.153.397 2.913 8.343 1.06 2.25-1.324zM90.1 183.355s20.4 10.285 43.498-3.71l-7.417-.506s-16.186 6.406-29 2.866z'/></g><path fill='#2d3136' d='m110.785 164.408 1.412 1.682c.032.04 3.168 3.773 7.003 7.575 2.55 2.527 5.63 3.806 9.162 3.806a16 16 0 0 0 3.751-.463c1.187-.289 2.477-.596 3.797-.912 4.77-1.14 10.171-2.435 12.79-3.169a35 35 0 0 1 1.705-.418c3.485-.805 8.263-1.916 10.795-6.988 1.904-3.806 4.47-8.746 6.375-12.367-1.65.761-3.38 1.586-4.609 2.233-1.894.994-4.374 1.476-7.58 1.476-1.907 0-3.78-.16-5.851-.344-4.141-.381-11.149-1.206-11.22-1.216l-.537-.06-.424-.338c-.063-.046-6.203-4.939-11.166-8.842q-.302-.247-.55-.454c-.523-.446-.523-.446-.835-.446-1.591 0-6.218 1.435-16.353 4.577l-.623.198-.61-.23c-.07-.023-6.72-2.49-14.688-2.49-.652 0-1.302.017-1.935.05-.798.041-5.278 2.169-11.006 11.232l-.371.582-.657.21c-.095.034-9.954 3.216-20.41 6.206l-.679.193-.642-.298c-.068-.028-4.16-1.926-8.053-3.716 2.123 3.137 4.838 7.05 7.227 10.118 4.173 5.374 9.433 10.34 19.518 10.34 1.265 0 2.609-.077 3.989-.234 10.258-1.16 12.9-1.627 14.321-1.875.481-.082.862-.152 1.349-.21.077-.01 8.478-1.097 10.835-6.384 2.43-5.433 2.921-6.728 2.94-6.777l.857-2.288s.106-.593.28-.777q-.001.005.005.004l.017.014c.27.194.67.8.67.8zM46.895 61.832c-2.23.898-4.444 1.825-6.609 2.842-2.169.992-4.301 2.056-6.337 3.242-1.88 1.083-3.683 2.27-5.274 3.611a34 34 0 0 0-.187 1.26 46 46 0 0 0-.344 5.376 53 53 0 0 0 .445 7.185c.33 2.385.765 4.756 1.412 7.044a37 37 0 0 0 1.1 3.37c.207.551.436 1.087.676 1.61.118.26.242.518.365.765l.153.29c.178.091.362.182.536.265l1.068.508c.596.276 1.417.666 2.096.968.04-.032.078-.069.11-.1l-1.977-7.54c.418-.114 4.179-1.086 10.658-2.32a30 30 0 0 1-1.572-.335 32.5 32.5 0 0 1-4.339-1.385 20 20 0 0 1-2.045-.99c-.656-.377-1.303-.803-1.803-1.371 7.118 2.324 21.824 1.27 34.9-.078 11.958-1.234 23.94-2.031 35.988-2.06 12.046.028 24.038.825 35.99 2.06 13.083 1.348 27.79 2.403 34.905.078-.5.568-1.145.994-1.801 1.37-.655.372-1.348.693-2.046.991a33 33 0 0 1-4.343 1.385q-.325.076-.656.147c7.104 1.316 11.24 2.389 11.68 2.508l-1.894 7.182c.381-.17.766-.334 1.141-.509l1.07-.508a21 21 0 0 0 .536-.266l.147-.289a18 18 0 0 0 .371-.765q.361-.785.68-1.61c.417-1.09.778-2.224 1.095-3.37.647-2.288 1.087-4.66 1.412-7.044.307-2.38.468-4.783.445-7.186a47 47 0 0 0-.156-3.595 46 46 0 0 0-.188-1.78 26 26 0 0 0-.19-1.26c-1.591-1.34-3.393-2.527-5.273-3.61-2.037-1.188-4.167-2.251-6.342-3.243-2.16-1.017-4.378-1.944-6.608-2.842a126 126 0 0 0-3.367-1.308c-1.122-.435-2.26-.846-3.397-1.26 2.388.394 4.756.939 7.098 1.54 1.527.395 3.05.84 4.554 1.312-10.727-28.043-36.38-46.302-64.384-46.302-28.011 0-53.658 18.26-64.384 46.302a96 96 0 0 1 4.549-1.312c2.348-.6 4.714-1.146 7.104-1.54-1.143.413-2.275.825-3.403 1.26-1.123.417-2.246.857-3.365 1.307m141.223 16.224c.129 2.581.06 5.167-.229 7.736a49 49 0 0 1-1.434 7.622 40 40 0 0 1-1.244 3.719 34 34 0 0 1-.788 1.834c-.143.302-.293.6-.463.908l-.252.454a8 8 0 0 1-.308.508l-.26.405-.409.21c-.436.225-.793.394-1.189.578l-1.15.519c-.77.33-1.545.64-2.32.943-.541.212-1.019.405-1.472.597l-1.28 4.86a94 94 0 0 0-1.136-.279l-.07.509c1.413.738 4.187 2.234 5.132 3.014 1.903 1.572 2.037 4.063 1.615 8.717-.216 2.39-1.009 7.039-2.109 11.31-1.9 7.35-2.908 8.213-4.21 8.546-.66.17-1.678.34-2.665.34-2.007 0-4.741.055-6.58-.39-.472 1.673-1.82 6.035-4.49 11.483.134-.065.266-.12.386-.184 3.292-1.729 9.62-4.485 9.887-4.605l4.999-2.172-2.56 4.814c-.06.105-5.549 10.429-8.91 17.17-3.34 6.677-9.598 8.13-13.345 8.992-.564.133-1.1.257-1.535.376-2.689.756-8.132 2.054-12.932 3.21-.057.013-.115.028-.174.036a56.7 56.7 0 0 1-9.704 4.793c-12.905 4.888-26.506 4.614-38.837.082-2.003.258-4.637.573-8.144.972a40 40 0 0 1-4.415.257c-11.75 0-17.977-5.966-22.522-11.808-4.865-6.256-10.817-15.542-11.07-15.931l-3.45-5.383 5.832 2.623c2.792 1.257 11.094 5.1 13.469 6.2q.287-.082.569-.165c-3.948-5.63-7.186-12.05-9.474-19.163-1.944.586-4.94 1.04-7.269 1.04-.851 0-1.614-.063-2.2-.201-1.303-.298-2.789-1.554-4.811-8.676-1.174-4.135-2.05-8.65-2.306-10.973-.505-4.536-.413-6.966 1.472-8.535 1.137-.95 4.97-2.963 5.764-3.374l-.17-1.4c-12.18 7.157-19.48 13.482-19.626 13.611l-8.71 7.644 6.205-9.786c4.91-7.755 10.583-14.024 15.802-18.853-.216-.087-.417-.174-.629-.26l-1.15-.519a31 31 0 0 1-1.187-.578l-.409-.21-.261-.405c-.147-.22-.21-.344-.307-.508l-.257-.454c-.16-.307-.317-.606-.46-.908q-.433-.914-.787-1.834a40 40 0 0 1-1.244-3.719 48.5 48.5 0 0 1-1.435-7.622 48.4 48.4 0 0 1-.23-7.736q.105-1.935.372-3.857c.092-.642.188-1.284.317-1.922.133-.65.262-1.256.459-1.967l.137-.5.358-.28c2.032-1.597 4.223-2.802 6.452-3.87a70 70 0 0 1 5.59-2.318C43.622 48.733 52.674 35.78 64.57 26.75c12.3-9.332 26.762-14.266 41.827-14.266s29.527 4.934 41.825 14.267c11.897 9.029 20.949 21.984 26.21 36.59a69 69 0 0 1 5.594 2.32c2.228 1.069 4.42 2.274 6.451 3.87l.358.281.138.5c.201.711.33 1.317.458 1.967.13.638.23 1.28.317 1.922.18 1.278.308 2.566.37 3.855m-10.626 42.546c.404-4.475.052-5.438-.403-5.805-.454-.381-1.99-1.251-3.485-2.063l-2.462 17.24a1.23 1.23 0 0 1-.514.835l-3.522 2.464c-.28 1.41-.38 2.077-.724 3.457 2.013.468 5.314.83 6.887.527 1.428-1.548 3.733-11.17 4.223-16.655m-4.571-15.633 2.637-10c-3.825-.867-10.869-2.317-20.462-3.674-4.899 3.521-10.26 6.337-15.528 8.584 16.12 1.569 27.965 3.903 33.353 5.09m-4.146 24.127 2.999-20.972c-1.334-.289-3.04-.646-5.077-1.036l-2.71 18.976c-.05.335-.234.642-.509.835l-10.162 7.107a1.23 1.23 0 0 1-.839.215l-30.623-3.48a1.23 1.23 0 0 1-1.042-.862l-5.516-18.159c-1.477-.382-5.27-1.267-8.589-1.267-3.334 0-7.122.885-8.599 1.267l-5.517 18.16a1.23 1.23 0 0 1-.908.838l-30.624 6.814a1.3 1.3 0 0 1-.27.029c-.248 0-.496-.074-.702-.222l-10.158-7.112a1.24 1.24 0 0 1-.518-.857l-2.738-22.21c-1.137.221-2.164.432-3.082.624q-.976.537-1.925 1.074l2.906 23.534 14.763 10.342 37.245-8.286 5.498-18.114a1.23 1.23 0 0 1 .982-.863c1.394-.224 2.613-.339 3.646-.339 1.02 0 2.246.115 3.636.339.467.073.847.408.981.863l5.64 18.563 37.453 4.259zm-7.148-3.907 2.65-18.556c-7.475-1.36-18.407-3.006-31.797-4.034-12.089 4.17-22.214 5.41-22.824 5.452l-.706.05c3.856.332 7.489 1.368 7.671 1.422.4.12.712.426.836.827l5.48 18.04 29.354 3.334zm2.04 10.492-1.244.87-3.347 2.35-3.628 2.54c-.247.17-.55.248-.845.215l-36.092-4.104-2.628-.299a1.23 1.23 0 0 1-1.037-.865l-5.649-18.6a19 19 0 0 0-2.489-.194q-1.076 0-2.495.194l-5.485 18.063a1.23 1.23 0 0 1-.908.838l-38.475 8.568a1.4 1.4 0 0 1-.27.026c-.247 0-.495-.073-.707-.224l-1.397-.973-4.187-2.929v-.01l-1.692-1.182c.12.38.256.752.391 1.123l-.005.005c.326.967 3.568 10.295 10.157 19.424a774 774 0 0 0 15.11-4.636c3.975-6.127 9.634-12.203 13.657-12.413a45 45 0 0 1 2.127-.054c1.968 0 3.848.141 5.59.348-1.48-1.344-1.784-2.922-1.999-3.619 0 0 5.132 4.013 11.666 3.175 6.536-.844 10.594-4.61 10.594-4.61s-.02 2.748-2.867 4.875c5.476-1.656 7.741-2.224 9.291-2.224 1.71 0 2.554.715 3.293 1.345.142.114.285.237.449.371 4.21 3.315 9.272 7.337 10.754 8.516 1.715.201 7.153.82 10.58 1.137 1.975.178 3.76.33 5.506.33.565 0 1.093-.024 1.592-.057 2.266-4.322 4.303-9.222 6.03-14.75.232-.851.458-1.709.659-2.57M150.35 89.98q-.014-.009-.028-.01a489 489 0 0 0-8.704-.577 803 803 0 0 0-12.106-.592 65 65 0 0 1-1.394 1.97c-2.196 2.973-6.553 8.425-12.14 13.048 7.908-1.417 21.622-4.925 33.487-13.203.297-.21.592-.43.885-.636m-26.464.82c.334-.417.645-.82.924-1.187a99 99 0 0 0-8.565 1.247c-9.58 1.793-18.426 4.774-25.786 7.828a340 340 0 0 1 16.423-.403c3.462 0 6.828.06 10.101.156 2.816-2.707 5.192-5.476 6.903-7.64zm3.177 90.423c-3.989-.294-7.616-1.953-10.544-4.852l-.444-.453a39 39 0 0 1-4.324.623c-.618.023-1.206.077-1.852.077l-.96.01-.365-.01c-3.894 5.793-12.542 6.921-12.937 6.972-.357.04-.65.09-1.055.164 4.82 1.321 9.74 1.894 14.592 1.568 4.865-.325 11.185-1.38 17.889-4.1zm-15.413-6.61a40 40 0 0 0 2.82-.334 123 123 0 0 1-3.16-3.417c-.417.959-.97 2.22-1.7 3.844h.214c.583 0 1.221-.063 1.826-.092zm-2.97-69.651a42 42 0 0 0 4.649-3.289c-2.115-.036-4.26-.063-6.447-.063-8.98 0-17.367.367-24.97.94-7.627 3.728-12.29 6.795-12.4 6.873l-7.323 4.885 5.03-7.219a47 47 0 0 1 2.619-3.416c-8.447.944-15.481 2.063-20.737 3.03l2.691 21.82 9.26 6.485 29.394-6.536 5.502-18.127a1.24 1.24 0 0 1 .556-.702 1 1 0 0 1 .279-.124c.17-.046 3.109-.885 6.498-1.298zm3.38-16.643q-3.072-.048-6.142-.055h-.004q-4.505.007-9.025.097l-5.553 2.631c-.005.005-.037.02-.078.043-.835.407-8.127 4.086-14.987 10.648 6.15-3.233 15.188-7.456 25.823-10.731a128 128 0 0 1 9.966-2.633M73.248 99.962c2.88-2.857 5.85-5.214 8.474-7.057-10.111 2.348-19.42 5.796-27.573 9.547a320 320 0 0 1 19.099-2.49m1.912-8.813a132 132 0 0 1 10.464-2.476c-5.136.187-10.275.426-15.406.715-2.982.165-5.949.367-8.916.591a138 138 0 0 0-4.435.394c-.422.041-.835.087-1.256.142-3.87 2.315-18.715 11.634-28.905 23.818 9.903-6.881 27.153-17.222 48.454-23.184m-27.327 48.008a61 61 0 0 1-.55-1.862l-4.572-3.2a1.23 1.23 0 0 1-.52-.858l-2.228-18.114c-1.651.885-3.558 1.971-4.068 2.398-.44.363-.775 1.294-.297 5.61.592 5.323 3.288 15.353 4.737 16.81 1.743.308 5.605-.228 7.498-.784m-3.379-45.495c-2.582.5-4.688.949-6.246 1.302l.735 2.77a92 92 0 0 1 5.511-4.072'/></svg>",
"tree": "<svg viewBox='0 0 32 32'><path fill='#7cb342' d='M4 4h10v6H4zm16 10h10v6H20zm0 8h10v6H20zm-4-4v-2h-6v-4H8v14h8v-2h-6v-6z'/></svg>",
- "trigger": "<svg fill='none' viewBox='0 0 32 32'><path fill='#4CAF50' fill-rule='evenodd' d='M11.158 13.51 16 5l12 21.09H4l4.842-8.51 3.425 2.007-1.416 2.49h10.298L16 13.027l-1.417 2.49z' clip-rule='evenodd'/></svg>",
+ "trigger": "<svg fill='none' viewBox='0 0 32 32'><path fill='#4caf50' fill-rule='evenodd' d='M11.158 13.51 16 5l12 21.09H4l4.842-8.51 3.425 2.007-1.416 2.49h10.298L16 13.027l-1.417 2.49z' clip-rule='evenodd'/></svg>",
"tsconfig": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h6v-4H6v-2h6v-2H6v-2h6v-2H6v-2h6v-2h2V4l8 8h2v-1Z' data-mit-no-recolor='true'/><path fill='#0288d1' d='M12 12v18h18V12Zm8 6h-2v8h-2v-8h-2v-2h6Zm8 0h-4v2h2a2.006 2.006 0 0 1 2 2v2a2.006 2.006 0 0 1-2 2h-4v-2h4v-2h-2a2.006 2.006 0 0 1-2-2v-2a2.006 2.006 0 0 1 2-2h4Z'/></svg>",
"tsil": "<svg viewBox='0 0 16 16'><path fill='#795548' d='M14 13.3a.7.7 0 0 1-.7.7H8V8h6z'/><path fill='#ffe57f' d='M14 8H8V2h5.3a.7.7 0 0 1 .7.7z'/><path fill='#ffab40' d='M8 8H2V2.7a.7.7 0 0 1 .7-.7H8z'/><path fill='#212121' d='M8 14H2.7a.7.7 0 0 1-.7-.7V8h6z'/></svg>",
"tune": "<svg viewBox='0 0 32 32'><path fill='#fbc02d' d='M12 10h10v2H12z'/><path fill='#fbc02d' d='M16 4h2v8h-2zm4 18h10v2H20zm4 2h2v4h-2zm0-20h2v14h-2zM2 18h10v2H2z'/><path fill='#fbc02d' d='M6 18h2v10H6zM6 4h2v10H6zm10 12h2v12h-2z'/></svg>",
"turborepo": "<svg viewBox='0 0 32 32'><defs><linearGradient id='a' x1='27.349' x2='7.613' y1='26.455' y2='6.719' gradientTransform='matrix(1 0 0 -1 0 34)' gradientUnits='userSpaceOnUse'><stop offset='.15' stop-color='#2196f3'/><stop offset='.85' stop-color='#f50057'/></linearGradient></defs><path fill='#cfd8dc' d='M16 8a8 8 0 1 0 8 8 8 8 0 0 0-8-8m0 12a4 4 0 1 1 4-4 4 4 0 0 1-4 4'/><path fill='url(#a)' d='M4.281 23.647A13.9 13.9 0 0 1 2 16h4a9.95 9.95 0 0 0 1.192 4.736ZM14 29.84v-4.042a9.9 9.9 0 0 1-3.892-1.732l-2.854 2.855A13.9 13.9 0 0 0 14 29.84M16 2v4a10 10 0 0 1 2 19.8v4.04A13.992 13.992 0 0 0 16 2'/></svg>",
"turborepo_light": "<svg viewBox='0 0 32 32'><defs><linearGradient id='a' x1='27.349' x2='7.613' y1='26.455' y2='6.719' gradientTransform='matrix(1 0 0 -1 0 34)' gradientUnits='userSpaceOnUse'><stop offset='.15' stop-color='#2196f3'/><stop offset='.85' stop-color='#f50057'/></linearGradient></defs><path fill='#455a64' d='M16 8a8 8 0 1 0 8 8 8 8 0 0 0-8-8m0 12a4 4 0 1 1 4-4 4 4 0 0 1-4 4'/><path fill='url(#a)' d='M4.281 23.647A13.9 13.9 0 0 1 2 16h4a9.95 9.95 0 0 0 1.192 4.736ZM14 29.84v-4.042a9.9 9.9 0 0 1-3.892-1.732l-2.854 2.855A13.9 13.9 0 0 0 14 29.84M16 2v4a10 10 0 0 1 2 19.8v4.04A13.992 13.992 0 0 0 16 2'/></svg>",
- "twig": "<svg viewBox='0 0 50 50'><path fill='#8BC34A' d='M9.727 47.556c-.125-.223-.297-2.168-.183-2.087.034.025.171.267.304.537s.282.487.332.482c.123-.011.075-1.196-.1-2.454-.331-2.397-1.176-4.434-2.358-5.69-.2-.212-.344-.4-.319-.418.093-.067 1.327.842 1.842 1.358.293.293.735.825.981 1.182.328.474.465.618.51.533.078-.147-.21-9.903-.376-12.7-.074-1.256.063-1.023.61 1.034 1.064 4.007 1.858 7.922 2.342 11.55.086.637.173 1.173.195 1.19s.092.002.157-.033c.888-.484 1.524-.667 2.55-.736.727-.049.945.062.35.177-1.15.223-1.99 1.013-2.344 2.201-.315 1.061-.327 2.707-.024 3.435.152.365.037.425-1.067.56-.716.088-.977.095-1.202.037-.356-.093-1.118-.098-1.195-.009-.031.037-.243.066-.47.066-.38 0-.423-.017-.534-.215zm1.974-3.232c.152-.205.072-.412-.204-.522-.225-.091-.263-.089-.437.024-.21.137-.252.43-.08.554.18.13.607.096.72-.056zm1.248.085a.8.8 0 0 0 .214-.202c.241-.33-.352-.622-.745-.366-.406.264.08.785.531.568m2.288 3.094c-.033-.038.117-.387.334-.774.216-.388.411-.666.433-.619.07.153-.201 1.28-.33 1.373-.15.107-.354.116-.437.02m-7.036-.41c-.29-.344-.221-.434.14-.183.176.124.321.264.321.311 0 .164-.279.086-.46-.129zm8.649-.146c0-.053.102-.18.227-.282.25-.204.312-.113.143.208-.095.18-.37.235-.37.074m8.065-.827c-.243-.025-.48-.088-.527-.14-.11-.125-.114-3.044-.004-3.044.045 0 .132.15.193.331q.189.57.31.124c.094-.337.065-3.471-.039-4.296-.449-3.55-1.865-6.124-4.342-7.89-1.086-.774-2.653-1.437-4.047-1.712-.764-.15-.522-.224.598-.182 2.364.09 4.167.707 5.847 2.002a11 11 0 0 1 2.32 2.501c.453.682.64.854.64.584 0-.07.063-.882.139-1.805.679-8.26 2.396-15.1 4.984-19.86 1.86-3.422 5.108-6.817 7.885-8.244 1.397-.717 2.539-.988 4.02-.952.933.023 1.01.036 1.77.308a6.8 6.8 0 0 1 1.363.661c.612.407 1.309 1.004 1.234 1.058-.025.018-.342-.165-.704-.407-2.657-1.771-5.062-1.52-7.12.742-1.108 1.22-2.652 3.53-3.634 5.443-2.828 5.503-4.541 11.464-5.291 18.413-.163 1.509-.282 3.76-.195 3.703.032-.022.266-.52.518-1.108 1.597-3.723 3.578-6.428 5.79-7.908.672-.45 1.612-.904 1.714-.83.023.016-.17.22-.43.453-1.958 1.755-3.25 3.76-4.233 6.573-.938 2.68-1.366 5.588-1.369 9.299 0 1.742.189 4.385.367 5.102.125.505.08.546-.585.546-.55 0-2.306.138-3.417.27-.413.05-.816.04-1.608-.036-.58-.056-1.13-.119-1.219-.14-.164-.037-.18-.014-.198.302-.012.186-.1.203-.73.139m2.507-6.725c.294-.11.375-.22.375-.517 0-.63-1.309-.706-1.524-.088-.074.212.13.51.42.616.297.109.413.107.73-.011zm2.369-.052c.277-.222.318-.364.174-.611-.4-.69-1.755-.307-1.428.405.121.265.299.35.738.353.227.001.387-.044.516-.147m3.011 6.681c-.027-.05.088-.267.256-.483.879-1.135 1.22-1.545 1.285-1.545.039 0 .055.037.035.083l-.423.963c-.213.484-.445.925-.519.977-.169.122-.57.125-.634.005m2.446-.596c0-.12.853-.683.896-.59.018.04-.056.21-.166.377-.168.258-.238.304-.464.304-.164 0-.266-.035-.266-.09zm-13.04-.124c-.176-.159-.493-.656-.461-.725.018-.038.248.1.512.309s.456.405.428.438c-.076.088-.372.074-.479-.022'/></svg>",
+ "twig": "<svg viewBox='0 0 50 50'><path fill='#8bc34a' d='M9.727 47.556c-.125-.223-.297-2.168-.183-2.087.034.025.171.267.304.537s.282.487.332.482c.123-.011.075-1.196-.1-2.454-.331-2.397-1.176-4.434-2.358-5.69-.2-.212-.344-.4-.319-.418.093-.067 1.327.842 1.842 1.358.293.293.735.825.981 1.182.328.474.465.618.51.533.078-.147-.21-9.903-.376-12.7-.074-1.256.063-1.023.61 1.034 1.064 4.007 1.858 7.922 2.342 11.55.086.637.173 1.173.195 1.19s.092.002.157-.033c.888-.484 1.524-.667 2.55-.736.727-.049.945.062.35.177-1.15.223-1.99 1.013-2.344 2.201-.315 1.061-.327 2.707-.024 3.435.152.365.037.425-1.067.56-.716.088-.977.095-1.202.037-.356-.093-1.118-.098-1.195-.009-.031.037-.243.066-.47.066-.38 0-.423-.017-.534-.215zm1.974-3.232c.152-.205.072-.412-.204-.522-.225-.091-.263-.089-.437.024-.21.137-.252.43-.08.554.18.13.607.096.72-.056zm1.248.085a.8.8 0 0 0 .214-.202c.241-.33-.352-.622-.745-.366-.406.264.08.785.531.568m2.288 3.094c-.033-.038.117-.387.334-.774.216-.388.411-.666.433-.619.07.153-.201 1.28-.33 1.373-.15.107-.354.116-.437.02m-7.036-.41c-.29-.344-.221-.434.14-.183.176.124.321.264.321.311 0 .164-.279.086-.46-.129zm8.649-.146c0-.053.102-.18.227-.282.25-.204.312-.113.143.208-.095.18-.37.235-.37.074m8.065-.827c-.243-.025-.48-.088-.527-.14-.11-.125-.114-3.044-.004-3.044.045 0 .132.15.193.331q.189.57.31.124c.094-.337.065-3.471-.039-4.296-.449-3.55-1.865-6.124-4.342-7.89-1.086-.774-2.653-1.437-4.047-1.712-.764-.15-.522-.224.598-.182 2.364.09 4.167.707 5.847 2.002a11 11 0 0 1 2.32 2.501c.453.682.64.854.64.584 0-.07.063-.882.139-1.805.679-8.26 2.396-15.1 4.984-19.86 1.86-3.422 5.108-6.817 7.885-8.244 1.397-.717 2.539-.988 4.02-.952.933.023 1.01.036 1.77.308a6.8 6.8 0 0 1 1.363.661c.612.407 1.309 1.004 1.234 1.058-.025.018-.342-.165-.704-.407-2.657-1.771-5.062-1.52-7.12.742-1.108 1.22-2.652 3.53-3.634 5.443-2.828 5.503-4.541 11.464-5.291 18.413-.163 1.509-.282 3.76-.195 3.703.032-.022.266-.52.518-1.108 1.597-3.723 3.578-6.428 5.79-7.908.672-.45 1.612-.904 1.714-.83.023.016-.17.22-.43.453-1.958 1.755-3.25 3.76-4.233 6.573-.938 2.68-1.366 5.588-1.369 9.299 0 1.742.189 4.385.367 5.102.125.505.08.546-.585.546-.55 0-2.306.138-3.417.27-.413.05-.816.04-1.608-.036-.58-.056-1.13-.119-1.219-.14-.164-.037-.18-.014-.198.302-.012.186-.1.203-.73.139m2.507-6.725c.294-.11.375-.22.375-.517 0-.63-1.309-.706-1.524-.088-.074.212.13.51.42.616.297.109.413.107.73-.011zm2.369-.052c.277-.222.318-.364.174-.611-.4-.69-1.755-.307-1.428.405.121.265.299.35.738.353.227.001.387-.044.516-.147m3.011 6.681c-.027-.05.088-.267.256-.483.879-1.135 1.22-1.545 1.285-1.545.039 0 .055.037.035.083l-.423.963c-.213.484-.445.925-.519.977-.169.122-.57.125-.634.005m2.446-.596c0-.12.853-.683.896-.59.018.04-.056.21-.166.377-.168.258-.238.304-.464.304-.164 0-.266-.035-.266-.09zm-13.04-.124c-.176-.159-.493-.656-.461-.725.018-.038.248.1.512.309s.456.405.428.438c-.076.088-.372.074-.479-.022'/></svg>",
"twine": "<svg viewBox='0 0 24 24'><path fill='#1e88e5' d='M4.229 3.119h6.657v17.755H4.23z'/><path fill='#69f0ae' d='M4.229 17.545c0-12.207 15.535-12.207 15.535-12.207v6.658s-8.877 0-8.877 5.549v3.329H4.229z'/></svg>",
"typescript-def": "<svg viewBox='0 0 16 16'><g fill='#0288d1'><path d='M2 2v12h12V2zm1 1h10v10H3z'/><path d='M5 7v1h1v4h1V8h1V7zm5 0a1.003 1.003 0 0 0-1 1v1a1.003 1.003 0 0 0 1 1h1v1H9v1h2a1.003 1.003 0 0 0 1-1v-1a1.003 1.003 0 0 0-1-1h-1V8h2V7z'/></g></svg>",
"typescript": "<svg viewBox='0 0 16 16'><path fill='#0288d1' d='M2 2v12h12V2zm4 6h3v1H8v4H7V9H6zm5 0h2v1h-2v1h1a1.003 1.003 0 0 1 1 1v1a1.003 1.003 0 0 1-1 1h-2v-1h2v-1h-1a1.003 1.003 0 0 1-1-1V9a1.003 1.003 0 0 1 1-1'/></svg>",
@@ -1061,9 +1071,9 @@
"unity": "<svg viewBox='0 0 16 16'><path fill='#42a5f5' d='M8 6.5 5 5l2-1V2L2 5v5l2-1V6.5L7 8v4.5L4 11l-2 1 6 3 6-3-2-1-3 1.5V8l3-1.5V9l2 1V5L9 2v2l2 1Z'/></svg>",
"unocss": "<svg viewBox='0 0 32 32'><circle cx='24' cy='24' r='6' fill='#78909c'/><path fill='#546e7a' d='M2 18v6a6 6 0 0 0 12 0v-6Z'/><path fill='#b0bec5' d='M30 14V8a6 6 0 0 0-12 0v6Z'/></svg>",
"url": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='M10 14h12v4H10z'/><path fill='#42a5f5' d='M12 22H9.562A5.57 5.57 0 0 1 4 16.438v-.876A5.57 5.57 0 0 1 9.562 10H12V6H9.562A9.56 9.56 0 0 0 0 15.562v.876A9.56 9.56 0 0 0 9.562 26H12ZM22.438 6H20v4h2.438A5.57 5.57 0 0 1 28 15.562v.876A5.57 5.57 0 0 1 22.438 22H20v4h2.438A9.56 9.56 0 0 0 32 16.438v-.876A9.56 9.56 0 0 0 22.438 6'/></svg>",
- "uv": "<svg viewBox='0 0 16 16'><path fill='#E040FB' d='M2 2v11c0 .5.5 1 1 1h8c.5 0 1-.5 1-1h1v1h1V2H8v8H7V2z'/></svg>",
+ "uv": "<svg viewBox='0 0 16 16'><path fill='#e040fb' d='M2 2v11c0 .5.5 1 1 1h8c.5 0 1-.5 1-1h1v1h1V2H8v8H7V2z'/></svg>",
"vagrant": "<svg viewBox='0 0 140.625 140.625'><path fill='#1565c0' d='m70.315 132.051 23.269-13.42 36.445-89.24V18.084l-27.142 15.791v9.539L81.16 90.26l-10.846 7.494zM59.449 92.32l10.866-5.365V73.322L54.028 35.326v-10.75l-.112-.064-16.174 9.362v9.539z'/><path fill='#2979ff' d='M86.597 24.463v10.862L70.312 73.32v12.697l-10.862 6.3-21.708-48.904V33.86l16.285-9.38L26.88 8.577l-16.286 9.506v11.644l36.654 89.018 23.064 13.302V98.615l10.847-6.3-.128-.08 21.852-48.824v-9.554l27.148-15.775-16.286-9.507-27.131 15.886z'/></svg>",
- "vala": "<svg viewBox='0 0 64 64'><defs><linearGradient id='c' x1='25.058' x2='25.058' y1='47.028' y2='39.999' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#212121' stop-opacity='0'/><stop offset='.5' stop-color='#212121'/><stop offset='1' stop-color='#212121' stop-opacity='0'/></linearGradient><linearGradient id='e' x1='24' x2='24' y1='5' y2='43' gradientTransform='matrix(1.4324 0 0 1.4363 134.03 -5.86)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#FAFAFA'/><stop offset='.063' stop-color='#FAFAFA' stop-opacity='.235'/><stop offset='.951' stop-color='#FAFAFA' stop-opacity='.157'/><stop offset='1' stop-color='#FAFAFA' stop-opacity='.392'/></linearGradient><linearGradient id='d' x1='31.293' x2='31.293' y1='5.008' y2='59.329' gradientTransform='translate(136.41 -3.39)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#BA68C8'/><stop offset='1' stop-color='#673AB7'/></linearGradient><radialGradient id='a' cx='4.993' cy='43.5' r='2.5' gradientTransform='matrix(2.0038 0 0 1.4 27.988 -17.4)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#212121'/><stop offset='1' stop-color='#212121' stop-opacity='0'/></radialGradient><radialGradient id='b' cx='4.993' cy='43.5' r='2.5' gradientTransform='matrix(2.0038 0 0 1.4 -20.012 -104.4)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#212121'/><stop offset='1' stop-color='#212121' stop-opacity='0'/></radialGradient></defs><g opacity='.6'><path fill='url(#a)' d='M38 40h5v7h-5z' transform='matrix(1.579 0 0 .71429 130.515 24.54)'/><path fill='url(#b)' d='M-10-47h5v7h-5z' transform='matrix(-1.579 0 0 -.71429 130.515 24.54)'/><path fill='url(#c)' d='M10 40h28v7H10z' transform='matrix(1.579 0 0 .71429 130.515 24.54)'/></g><rect width='55' height='55' x='140.91' y='1.11' fill='url(#d)' rx='3' ry='3'/><rect width='53' height='53.142' x='141.91' y='2.039' stroke='url(#e)' stroke-linecap='round' stroke-linejoin='round' opacity='.3' rx='2' ry='2'/><rect width='55' height='55' x='140.91' y='1.11' stroke='#4A148C' stroke-linecap='round' stroke-linejoin='round' opacity='.5' rx='3' ry='3'/><path fill='#9575cd' d='m26.357 57.882-1.111-47.15q-4.854 1.82-7.583 5.694-2.698 3.877-2.698 9.64 0 1.314.136 2.157.167.809.336 1.314.169.472.305.742.167.27.167.472-1.786 0-3.167-.336-1.383-.372-2.327-1.147-.91-.773-1.415-2.055-.473-1.28-.473-3.167 0-2.292.976-4.516 1.011-2.223 2.73-4.213 1.753-1.987 4.08-3.673 2.36-1.685 5.021-2.899 2.695-1.247 5.594-1.92 2.932-.71 5.831-.71.775 0 1.416.034.673.033 1.346.1l.608 42.465L50.654 6.45h4.819L36.298 57.883h-9.943z'/></svg>",
+ "vala": "<svg viewBox='0 0 64 64'><defs><linearGradient id='c' x1='25.058' x2='25.058' y1='47.028' y2='39.999' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#212121' stop-opacity='0'/><stop offset='.5' stop-color='#212121'/><stop offset='1' stop-color='#212121' stop-opacity='0'/></linearGradient><linearGradient id='e' x1='24' x2='24' y1='5' y2='43' gradientTransform='matrix(1.4324 0 0 1.4363 134.03 -5.86)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#fafafa'/><stop offset='.063' stop-color='#fafafa' stop-opacity='.235'/><stop offset='.951' stop-color='#fafafa' stop-opacity='.157'/><stop offset='1' stop-color='#fafafa' stop-opacity='.392'/></linearGradient><linearGradient id='d' x1='31.293' x2='31.293' y1='5.008' y2='59.329' gradientTransform='translate(136.41 -3.39)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ba68c8'/><stop offset='1' stop-color='#673ab7'/></linearGradient><radialGradient id='a' cx='4.993' cy='43.5' r='2.5' gradientTransform='matrix(2.0038 0 0 1.4 27.988 -17.4)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#212121'/><stop offset='1' stop-color='#212121' stop-opacity='0'/></radialGradient><radialGradient id='b' cx='4.993' cy='43.5' r='2.5' gradientTransform='matrix(2.0038 0 0 1.4 -20.012 -104.4)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#212121'/><stop offset='1' stop-color='#212121' stop-opacity='0'/></radialGradient></defs><g opacity='.6'><path fill='url(#a)' d='M38 40h5v7h-5z' transform='matrix(1.579 0 0 .71429 130.515 24.54)'/><path fill='url(#b)' d='M-10-47h5v7h-5z' transform='matrix(-1.579 0 0 -.71429 130.515 24.54)'/><path fill='url(#c)' d='M10 40h28v7H10z' transform='matrix(1.579 0 0 .71429 130.515 24.54)'/></g><rect width='55' height='55' x='140.91' y='1.11' fill='url(#d)' rx='3' ry='3'/><rect width='53' height='53.142' x='141.91' y='2.039' stroke='url(#e)' stroke-linecap='round' stroke-linejoin='round' opacity='.3' rx='2' ry='2'/><rect width='55' height='55' x='140.91' y='1.11' stroke='#4a148c' stroke-linecap='round' stroke-linejoin='round' opacity='.5' rx='3' ry='3'/><path fill='#9575cd' d='m26.357 57.882-1.111-47.15q-4.854 1.82-7.583 5.694-2.698 3.877-2.698 9.64 0 1.314.136 2.157.167.809.336 1.314.169.472.305.742.167.27.167.472-1.786 0-3.167-.336-1.383-.372-2.327-1.147-.91-.773-1.415-2.055-.473-1.28-.473-3.167 0-2.292.976-4.516 1.011-2.223 2.73-4.213 1.753-1.987 4.08-3.673 2.36-1.685 5.021-2.899 2.695-1.247 5.594-1.92 2.932-.71 5.831-.71.775 0 1.416.034.673.033 1.346.1l.608 42.465L50.654 6.45h4.819L36.298 57.883h-9.943z'/></svg>",
"vedic": "<svg viewBox='0 0 288 288'><svg viewBox='0 -15 356 400'><path fill='#ff3d00' d='M90.457 353.95c-38.66-13.815-66.73-48.192-77.845-95.332-5.044-21.395-6.47-56.748-2.288-56.748 1.389 0 5.1 9.7 8.245 21.557 6.884 25.945 18.625 50.342 29.967 62.267 18.839 19.808 65.5 27.566 92.385 15.36 20.943-9.509 29.436-32.108 20.329-54.095-7.038-16.99-23.003-22.67-52.742-18.767 0 0-18.225-19.618-24.032-54.457l18.681 1.694c22.5 2.04 39.488-2.933 48.305-14.142 8.286-10.533 8.107-14.607-1.114-25.325-13.304-15.468-37.193-11.55-85.561 14.033l-24.405-40.91 10.231-7.804c25.64-19.557 70.16-29.334 95.497-20.972 23.078 7.617 40.017 37.839 35.492 63.324-3.059 17.23-16.874 41.362-27.548 48.12l-9.205 5.829 12.715 5.733c21.606 9.743 34.797 2.295 50.556-28.547 21.81-42.681 35.954-53.73 68.847-53.777 15.315-.023 20.766 1.584 31.936 9.412 27.88 19.537 43.06 59.994 39.725 105.87-4.223 58.101-31.744 93.343-72.894 93.343-22.583 0-37.14-7.92-48.727-26.514-10.177-16.333-14.764-48.68-8.919-62.908 2.804-6.827 3.31-7.058 3.494-1.597.337 10.04 11.76 26.358 22.246 31.781 25.73 13.306 62.667-3.411 77.28-34.975 11.095-23.964 5.143-70.186-10.087-78.337-3.186-1.706-11.06-3.101-17.497-3.101-13.682 0-24.427 9.837-39.491 36.153-5.209 9.098-13.974 20.854-19.478 26.123-8.944 8.562-12.137 9.581-30.024 9.581h-20.017l6.47 14.372c9.261 20.57 8.823 53.993-.974 74.34-8.657 17.979-28.674 36.18-44.676 40.626-15.578 4.328-40.946 3.768-54.877-1.21m75.377-278.026c-8.855-15.11-14.304-43.318-8.369-43.318 6.973 15.126 21.265 28.621 36.57 38.037 27.486 13.306 55.358 7.936 85.807-16.535 6.704-5.387 12.64-9.195 13.192-8.462 7.436 11.538 20.297 20.967 24.548 34.375 0 5.658-24.353 21.94-43.57 29.13-47.63 13.72-80.046 8.292-108.178-33.227m24.002-34.927 31.01-34.383 34.46 31.9c-11.787 9.709-20.296 24.775-33.762 32.416-10.64-7.844-26.52-17.092-31.708-29.933' class='colorff4500 svgShape'/></svg></svg>",
"velite": "<svg fill='none' viewBox='0 0 16 16'><path fill='#43a047' d='m5.767 7.155.918 2.46L5 13 1 3.014c.444 0 .866.107 1.267.221 1.433.408 2.594 1.594 3.5 3.92'/><path fill='#26a69a' d='M15 3.014c-1.357-.423-2.581.024-3.602.943L6.75 8.285C4.833 10.077 3.69 9.871 3 8l2 5z'/></svg>",
"velocity": "<svg viewBox='0 0 300 300'><path fill='#0288d1' d='M150 61.553A88.446 88.446 0 0 0 61.553 150 88.446 88.446 0 0 0 150 238.446 88.446 88.446 0 0 0 238.446 150 88.446 88.446 0 0 0 150 61.553m.011 25.082a63.353 63.353 0 0 1 63.353 63.353 63.353 63.353 0 0 1-63.353 63.353 63.353 63.353 0 0 1-63.353-63.353 63.353 63.353 0 0 1 63.353-63.353' paint-order='fill markers stroke'/><path fill='#0288d1' d='M45.008 193.096 12.213 225.89l32.795 32.795V238.44h104.99v-25.098H45.008zM74.088 12.21 41.293 45.006h20.246v104.99h25.098V45.007h20.246zm180.901 29.093V61.55h-104.99v25.097h104.99v20.246L287.784 74.1zM213.32 149.998V254.99h-20.245l32.794 32.795 32.795-32.795h-20.246V150z'/></svg>",
@@ -1072,7 +1082,7 @@
"verdaccio": "<svg viewBox='0 0 24 24'><path fill='#00897b' d='M18.2 10.237h-4.448l-1.827 3.654-4.812-9.624H2.665l7.96 15.92h2.6z' clip-rule='evenodd'/><path fill='#e57373' d='M14.845 3.813v.7h1.767l-.416.825h-2.773v.7h2.42l-.546 1.085h-3.264v.7h3.526l3.766.017 2.01-4.018-1.1-.003v-.006z'/></svg>",
"verified": "<svg viewBox='0 0 24 24'><path fill='#8bc34a' d='M9 3 8 6H4l1 4-3 2 3 2-1 4h4l1 3 3-2 3 2 1-3h4l-1-4 3-2-3-2 1-4h-4l-1-3-3 2zm7 5 1 1-7 7-3-3 1-1 2 2z'/></svg>",
"verilog": "<svg viewBox='0 0 32 32'><path fill='#ff7043' d='M21.833 8A2.17 2.17 0 0 1 24 10.167v11.666A2.17 2.17 0 0 1 21.833 24H10.167A2.17 2.17 0 0 1 8 21.833V10.167A2.17 2.17 0 0 1 10.167 8zm0-2H10.167A4.167 4.167 0 0 0 6 10.167v11.666A4.167 4.167 0 0 0 10.167 26h11.666A4.167 4.167 0 0 0 26 21.833V10.167A4.167 4.167 0 0 0 21.833 6'/><path fill='#ff7043' d='M18 14v4h-4v-4zm2-2h-8v8h8zM2 12h4v2H2zm0 6h4v2H2zm24-6h4v2h-4zm0 6h4v2h-4zm-8 8h2v4h-2zm-6 0h2v4h-2zm6-24h2v4h-2zm-6 0h2v4h-2z'/></svg>",
- "vfl": "<svg viewBox='0 0 24 24'><defs><radialGradient id='a' cx='205.45' cy='208.29' r='225.35' gradientTransform='matrix(.04556 0 0 .0456 2.888 2.88)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#FFD600'/><stop offset='.35' stop-color='#F9A825'/><stop offset='1' stop-color='#F4511E'/></radialGradient></defs><g stroke-width='.046'><path fill='#F4511E' d='M19.97 3H4.03A1.03 1.03 0 0 0 3 4.03v4.136c1.548-1.19 3.563-1.958 5.948-1.958 5.107.004 8.35 3.575 8.348 8.082 0 3.13-1.46 5.485-3.745 6.71h6.419A1.03 1.03 0 0 0 21 19.967V4.031a1.03 1.03 0 0 0-1.03-1.03z'/><path fill='url(#a)' d='M3 17.722v2.247A1.03 1.03 0 0 0 4.03 21h1.837C4.474 20.21 3.49 19 3 17.722'/><path fill='#F4511E' d='M8.948 8.23C6.362 8.142 4.35 9.09 3 10.496v3.162c.918-2.653 3.447-3.87 5.565-3.849 2.647.027 4.689 2.025 4.7 4.284.012 2.158-.892 3.748-3.33 4.14-1.33.213-3.41-.568-3.318-2.578.046-1.037.854-1.622 1.777-1.58-.904 1.213.293 2.102 1.139 1.92 1.048-.223 1.475-1.155 1.475-1.877 0-.762-.717-1.994-2.498-1.952-2.204.053-3.59 1.64-3.638 3.603-.056 2.468 2.254 4.091 4.623 4.12 3.478.046 5.542-2.24 5.538-5.585-.005-3.03-2.434-5.946-6.085-6.072z'/></g></svg>",
+ "vfl": "<svg viewBox='0 0 24 24'><defs><radialGradient id='a' cx='205.45' cy='208.29' r='225.35' gradientTransform='matrix(.04556 0 0 .0456 2.888 2.88)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ffd600'/><stop offset='.35' stop-color='#f9a825'/><stop offset='1' stop-color='#f4511e'/></radialGradient></defs><g stroke-width='.046'><path fill='#f4511e' d='M19.97 3H4.03A1.03 1.03 0 0 0 3 4.03v4.136c1.548-1.19 3.563-1.958 5.948-1.958 5.107.004 8.35 3.575 8.348 8.082 0 3.13-1.46 5.485-3.745 6.71h6.419A1.03 1.03 0 0 0 21 19.967V4.031a1.03 1.03 0 0 0-1.03-1.03z'/><path fill='url(#a)' d='M3 17.722v2.247A1.03 1.03 0 0 0 4.03 21h1.837C4.474 20.21 3.49 19 3 17.722'/><path fill='#f4511e' d='M8.948 8.23C6.362 8.142 4.35 9.09 3 10.496v3.162c.918-2.653 3.447-3.87 5.565-3.849 2.647.027 4.689 2.025 4.7 4.284.012 2.158-.892 3.748-3.33 4.14-1.33.213-3.41-.568-3.318-2.578.046-1.037.854-1.622 1.777-1.58-.904 1.213.293 2.102 1.139 1.92 1.048-.223 1.475-1.155 1.475-1.877 0-.762-.717-1.994-2.498-1.952-2.204.053-3.59 1.64-3.638 3.603-.056 2.468 2.254 4.091 4.623 4.12 3.478.046 5.542-2.24 5.538-5.585-.005-3.03-2.434-5.946-6.085-6.072z'/></g></svg>",
"video": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='m24 6 2 6h-4l-2-6h-3l2 6h-4l-2-6h-3l2 6H8L6 6H5a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h22a3 3 0 0 0 3-3V6Z'/></svg>",
"vim": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M22.19 4H16v4h2.19L12 14.19V8h2V4H2v4h2v20h4v-.01l.01.01L28 8V4z'/></svg>",
"virtual": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='M28 24V6H4v18H2v2h28v-2Zm-8 0h-8v-2h8Zm6-4H6V8h20Z'/></svg>",
@@ -1080,7 +1090,7 @@
"vite": "<svg viewBox='0 0 32 32'><path fill='#ffab00' d='M10 2v16h4v12l9-16h-6l5-12Z'/></svg>",
"vitest": "<svg viewBox='0 0 32 32'><path fill='#689f38' d='M16.094 30.074a1.4 1.4 0 0 1-1.003-.416l-6.622-6.622a1.42 1.42 0 0 1 2.006-2.006l5.62 5.618 12.24-12.24a1.419 1.419 0 0 1 2.007 2.006L17.098 29.658a1.4 1.4 0 0 1-1.004.416'/><path fill='#689f38' fill-opacity='.502' d='M16.089 30.074a1.4 1.4 0 0 0 1.003-.416l6.622-6.622a1.42 1.42 0 0 0-2.006-2.006l-5.62 5.618-12.24-12.24a1.42 1.42 0 0 0-2.007 2.006l13.244 13.244a1.4 1.4 0 0 0 1.004.416'/><path fill='#ffca28' d='M24 10h-6V2l-8 12h6v8z'/></svg>",
"vlang": "<svg style='isolation:isolate' viewBox='0 0 500 500'><path fill='#546e7a' d='m311.64 433.372 130.885-363.97c2.22-6.173-1.28-10.674-7.809-10.044l-102.93 9.915c-6.529.63-13.582 6.17-15.739 12.363L194.901 429.48c-2.158 6.194 1.416 11.223 7.975 11.223h100.191c3.28 0 6.843-2.505 7.953-5.592z'/><path fill='#039be5' d='m65.278 59.359 102.93 9.915c6.529.63 13.59 6.167 15.757 12.358l123.714 353.456c1.083 3.097-.7 5.608-3.98 5.608H202.877c-6.56 0-13.688-5.01-15.907-11.183L57.472 69.398c-2.22-6.173 1.28-10.674 7.809-10.044z'/></svg>",
- "vscode": "<svg viewBox='0 0 32 32'><path fill='#2196f3' d='M24.003 2 12 13.303 4.84 8 2 10l6.772 6L2 22l2.84 2L12 18.702 24.003 30 30 27.087V4.913ZM24 9.434v13.132L15.289 16Z'/></svg>",
+ "vscode": "<svg viewBox='0 0 16 16'><path fill='#2196f3' d='M11.5 11.19V4.8L7.3 7.99M1.17 6.07a.6.6 0 0 1-.01-.81L2 4.48c.14-.13.48-.18.73 0l2.39 1.83 5.55-5.09c.22-.22.61-.32 1.05-.08l2.8 1.34c.25.15.49.38.49.81v9.49c0 .28-.2.58-.42.7l-3.08 1.48c-.22.09-.64 0-.79-.14L5.11 9.69l-2.38 1.83c-.27.18-.6.13-.74 0l-.84-.77c-.22-.23-.2-.61.04-.84l2.1-1.9'/></svg>",
"vue-config": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h16a2 2 0 0 0 2-2V11Zm3 22H6v-2h12Zm0-4H6v-2h12Zm0-4H6v-2h12Zm-4-4V4l8 8Z' data-mit-no-recolor='true'/><path fill='#41b883' d='m14 16 8 14.093 8-14.024V16h-3.11l-4.843 8.49L17.225 16Z'/><path fill='#35495e' d='m17.225 16 4.821 8.492 4.844-8.491h-2.918l-1.906 3.342-1.9-3.343Z'/></svg>",
"vue": "<svg viewBox='0 0 24 24'><path fill='#41b883' d='M1.791 3.851 12 21.471 22.209 3.936V3.85H18.24l-6.18 10.616L5.906 3.851z'/><path fill='#35495e' d='m5.907 3.851 6.152 10.617L18.24 3.851h-3.723L12.084 8.03 9.66 3.85z'/></svg>",
"vuex-store": "<svg viewBox='0 0 16 16'><path fill='#41b883' d='M1.646 14.41 6.729 4.157l1.27 2.501v2.63l-2.525 5.124zm12.708.009L9.27 4.164 8 6.665v2.63l2.517 5.124z'/><path fill='#35495e' d='M1.646 1.582 4.823 8l1.906-3.844-1.27-2.574zm12.708 0L11.177 8 9.27 4.156l1.27-2.574z'/></svg>",
@@ -1088,21 +1098,21 @@
"wakatime_light": "<svg fill='none' viewBox='0 0 340 340'><path stroke='#455a64' stroke-width='33.39' d='M170 44.788c-69.154 0-125.212 56.058-125.212 125.212s56.058 125.213 125.213 125.213S295.212 239.155 295.212 170 239.155 44.788 170 44.788z'/><path fill='#455a64' d='M186.846 206.343c-1.205 1.588-3.011 2.61-5.035 2.61a6 6 0 0 1-.591-.034 7 7 0 0 1-.7-.109 6.7 6.7 0 0 1-1.15-.385 8 8 0 0 1-.547-.28 6.6 6.6 0 0 1-.856-.591 7 7 0 0 1-.42-.367 8 8 0 0 1-.586-.64 7.5 7.5 0 0 1-.754-1.144l-7.378-11.854-7.374 11.854c-1.157 2.107-3.249 3.55-5.652 3.55-2.412 0-4.514-1.454-5.636-3.607l-32.252-46.985c-1.06-1.278-1.712-2.973-1.712-4.844 0-3.96 2.911-7.173 6.501-7.173 2.324 0 4.358 1.35 5.508 3.375l27.224 40.228 7.663-12.477c1.104-2.224 3.248-3.734 5.71-3.734 2.252 0 4.238 1.266 5.404 3.188l7.903 12.972 42.712-61.15c1.16-1.967 3.164-3.269 5.45-3.269 3.59 0 6.5 3.212 6.5 7.172 0 1.73-.553 3.317-1.478 4.555z'/></svg>",
"wallaby": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='M16 2v14H2v14h28V2z'/></svg>",
"wally": "<svg viewBox='0 0 32 32'><path fill='#e65100' d='M8.454 3.084c-.897-.112-1.438.473-2.502 2.43-1.457 2.682-3.888 1.135-1.765 5.275a4.7 4.7 0 0 0 .759 1.122l5.749-9.219c-.84-.614-1.513.448-2.241.392'/><path fill='#ffcc80' d='m28.565 29.985-5.982-8.28a1.67 1.67 0 0 0-.57-1.362c-1.794-1.789-2.69-3.51-1.905-6.975a3.94 3.94 0 0 0-1.326-3.766 29 29 0 0 0 .206-3.22s-.673-2.907-2.354-3.13c0 0-4.426-2.236-7.956.279S7.95 5.71 5.82 8.897a4.5 4.5 0 0 0-.444.867 7 7 0 0 0-1.237.307c-1.4.56-1.344 2.07.56 3.635a1 1 0 0 0 .29.148 8.2 8.2 0 0 1-.29 2.198c-.595 1.674.853 3.91 3.024 1.789a7 7 0 0 0 1.648.942 8.1 8.1 0 0 1 .707 2.522l-.558 8.68Z'/><path fill='#3e2723' d='M8.651 9.017a.803.803 0 0 1-.807-.805v-1.88a.807.807 0 0 1 1.615 0v1.88a.803.803 0 0 1-.808.805m5.314 0a.814.814 0 0 1-.813-.811V6.339a.814.814 0 0 1 1.627 0v1.948a.8.8 0 0 1-.814.73'/><path fill='#e65100' d='M17.082 14.15a6.06 6.06 0 0 1-4.818 4.528 4.75 4.75 0 0 1-5.828-3.13 3.8 3.8 0 0 0 2.999 3.679c.84 1.302.355 3.547.307 4.874a5.4 5.4 0 0 1-.061.84l-1.08 5.044 5.421.015c-.724-.397-1.02-2.476-1.14-3.901l-.079-1.405a1.415 1.415 0 0 1 .974-1.432c2.796-1.81-.672-1.789-.672-2.292a3.6 3.6 0 0 1 1.068-2.195c2.535-1.273 3.114-4.013 2.91-4.624'/><path fill='#e65100' d='M20.518 17.614c.588-.523 1.012-2.457 1.492-3.362.785-1.48 4.797-2.989 3.136-5.446-1.55-2.292-2.24-1.658-3.136-3.28-.84-1.452-1.073-1.763-3.154-1.763a7.7 7.7 0 0 1-2.296-1.124A3.07 3.07 0 0 0 14.531 2c-3.98 0-4.9-.091-5.853 1.195 0 0 4.429-1.841 8.025.533 1.213 1.634 2.441 4.61 1.612 9.64a8.8 8.8 0 0 0 .093 3.228 1.5 1.5 0 0 0 .096.466s4.621 9.882 9.222 12.922H29s-5.867-7.368-8.482-12.371M5.035 16.05a2.496 2.496 0 0 0-1.176-3.24 3 3 0 0 1 .56 3.185c-.56 1.118 0 3.242 2.073 2.627 0 0-2.41.112-1.457-2.571'/><path fill='#b71c1c' d='M9.736 23.933a9.63 9.63 0 0 0 8.41-.28c3.195-1.733 3.788-3.732 3.451-4.123a2.42 2.42 0 0 1 2.053 1.036c.56.839-.46 3.645-5.167 5.602-4.427 1.788-7.848.974-8.632.191-.729-.782-.115-2.426-.115-2.426m-6.55-13.192c-.616.615.393 2.46 1.625 3.018 1.401.615 3.418-1.788 3.026-2.459-.448-.894-3.362-1.844-4.65-.559'/></svg>",
- "watchman": "<svg viewBox='0 0 420 419'><circle cx='210' cy='209.5' r='188.15' fill='#FAFAFA' paint-order='stroke fill markers'/><path fill='#304FFE' d='M191.07 397.1c-35.512-4.049-66.779-16.485-95.318-37.913-22.723-17.061-44.027-43.274-56.077-68.997l-3.932-8.393 25.692-26.37c14.13-14.505 27.522-28.059 29.758-30.12l4.067-3.748 12.5 11.364c16.495 14.996 26.818 23.219 41.05 32.697 14.94 9.95 23.867 14.578 35.877 18.597 22.823 7.637 42.099 5.991 66.082-5.642 17.83-8.65 44.399-28.24 66.179-48.797l8.878-8.38 29.147 29.137c16.03 16.025 29.147 29.84 29.147 30.7s-1.6 4.912-3.554 9.005c-18.398 38.533-49.46 69.834-87.797 88.466-17.732 8.619-33.936 13.787-53.563 17.084-10.16 1.708-38.005 2.465-48.137 1.31zm6.3-123.69c-17.457-3.809-39.276-16.397-63.835-36.829-13.001-10.816-26.615-23.771-26.615-25.327 0-1.265 16.792-17.101 29.7-28.009 20.328-17.179 36.936-27.484 53.753-33.355 6.275-2.19 8.25-2.443 19.147-2.443 10.892 0 12.873.253 19.136 2.44 22.614 7.894 50.68 27.157 79.189 54.35 3.836 3.659 6.975 7.021 6.975 7.472 0 1.1-21.726 20.758-32.4 29.316-19.403 15.557-41.794 28.276-56.169 31.907-8.267 2.088-20.566 2.29-28.881.477zm37.472-20.429c14.201-7.184 18.451-9.747 19.779-11.925 1.556-2.552 1.692-4.934 1.692-29.774 0-24.91-.132-27.216-1.705-29.795-1.34-2.2-5.607-4.783-20.022-12.124-18.175-9.256-18.368-9.329-24.812-9.355-6.44-.026-6.632.044-22.95 8.376-10.12 5.168-17.582 9.58-19.38 11.462l-2.925 3.06.003 27.237c.003 25.164.134 27.452 1.712 30.04 1.301 2.134 5.186 4.602 16.288 10.35 20.15 10.433 22.925 11.538 29.04 11.571 4.82.026 6.487-.627 23.28-9.123m-40.872-12.388c-7.315-3.808-13.914-7.538-14.665-8.29-1.181-1.181-1.28-4.249-.736-22.667l.63-21.3 13.888-7.202c8.096-4.199 14.986-7.202 16.52-7.202 2.953 0 28.618 12.505 31.424 15.311 1.68 1.682 1.788 3.033 1.788 22.477v20.69l-3.46 2.183c-1.902 1.202-8.376 4.65-14.386 7.662-8.03 4.025-11.825 5.448-14.315 5.37-2.329-.075-7.546-2.273-16.688-7.032M29.5 263.432c-1.475-3.866-4.192-15.727-5.967-26.05-2.405-13.986-2.19-43.337.424-58.05 2.41-13.562 4.216-20.627 5.613-21.959.772-.736 7.977 6.088 27.938 26.459 14.794 15.098 26.902 27.655 26.906 27.906s-12.122 12.581-26.948 27.4L30.51 266.082zm333.59-24.851c-14.17-14.479-25.763-26.679-25.763-27.11 0-1.574 51.776-53.76 53.1-53.521 1.493.27 3.338 6.773 5.975 21.06 2.353 12.751 2.343 48.232-.017 61.072-2.37 12.886-5.358 24.1-6.527 24.49-.553.184-12.599-11.512-26.768-25.991M64.75 170.903c-26.893-26.902-29.95-30.252-29.354-32.175 1.335-4.308 8.33-18.529 12.565-25.546 28.808-47.732 75.832-79.809 131.42-89.647 15.231-2.696 45.201-2.944 59.361-.492 41.827 7.244 76.236 24.833 104.91 53.625 15.582 15.647 27.713 32.758 36.971 52.151 6.183 12.95 8.732 8.549-24.429 42.185-15.984 16.213-29.386 29.61-29.782 29.768-.396.16-5.695-4.244-11.775-9.786-32.548-29.67-61.86-48.734-85.116-55.361-7.771-2.215-9.327-2.357-22.05-2.009-12.468.34-14.427.627-21.965 3.212-23.775 8.152-49.675 26.108-80.457 55.78-4.75 4.579-9.006 8.325-9.457 8.325s-14.329-13.513-30.839-30.029z'/></svg>",
+ "watchman": "<svg viewBox='0 0 420 419'><circle cx='210' cy='209.5' r='188.15' fill='#fafafa' paint-order='stroke fill markers'/><path fill='#304ffe' d='M191.07 397.1c-35.512-4.049-66.779-16.485-95.318-37.913-22.723-17.061-44.027-43.274-56.077-68.997l-3.932-8.393 25.692-26.37c14.13-14.505 27.522-28.059 29.758-30.12l4.067-3.748 12.5 11.364c16.495 14.996 26.818 23.219 41.05 32.697 14.94 9.95 23.867 14.578 35.877 18.597 22.823 7.637 42.099 5.991 66.082-5.642 17.83-8.65 44.399-28.24 66.179-48.797l8.878-8.38 29.147 29.137c16.03 16.025 29.147 29.84 29.147 30.7s-1.6 4.912-3.554 9.005c-18.398 38.533-49.46 69.834-87.797 88.466-17.732 8.619-33.936 13.787-53.563 17.084-10.16 1.708-38.005 2.465-48.137 1.31zm6.3-123.69c-17.457-3.809-39.276-16.397-63.835-36.829-13.001-10.816-26.615-23.771-26.615-25.327 0-1.265 16.792-17.101 29.7-28.009 20.328-17.179 36.936-27.484 53.753-33.355 6.275-2.19 8.25-2.443 19.147-2.443 10.892 0 12.873.253 19.136 2.44 22.614 7.894 50.68 27.157 79.189 54.35 3.836 3.659 6.975 7.021 6.975 7.472 0 1.1-21.726 20.758-32.4 29.316-19.403 15.557-41.794 28.276-56.169 31.907-8.267 2.088-20.566 2.29-28.881.477zm37.472-20.429c14.201-7.184 18.451-9.747 19.779-11.925 1.556-2.552 1.692-4.934 1.692-29.774 0-24.91-.132-27.216-1.705-29.795-1.34-2.2-5.607-4.783-20.022-12.124-18.175-9.256-18.368-9.329-24.812-9.355-6.44-.026-6.632.044-22.95 8.376-10.12 5.168-17.582 9.58-19.38 11.462l-2.925 3.06.003 27.237c.003 25.164.134 27.452 1.712 30.04 1.301 2.134 5.186 4.602 16.288 10.35 20.15 10.433 22.925 11.538 29.04 11.571 4.82.026 6.487-.627 23.28-9.123m-40.872-12.388c-7.315-3.808-13.914-7.538-14.665-8.29-1.181-1.181-1.28-4.249-.736-22.667l.63-21.3 13.888-7.202c8.096-4.199 14.986-7.202 16.52-7.202 2.953 0 28.618 12.505 31.424 15.311 1.68 1.682 1.788 3.033 1.788 22.477v20.69l-3.46 2.183c-1.902 1.202-8.376 4.65-14.386 7.662-8.03 4.025-11.825 5.448-14.315 5.37-2.329-.075-7.546-2.273-16.688-7.032M29.5 263.432c-1.475-3.866-4.192-15.727-5.967-26.05-2.405-13.986-2.19-43.337.424-58.05 2.41-13.562 4.216-20.627 5.613-21.959.772-.736 7.977 6.088 27.938 26.459 14.794 15.098 26.902 27.655 26.906 27.906s-12.122 12.581-26.948 27.4L30.51 266.082zm333.59-24.851c-14.17-14.479-25.763-26.679-25.763-27.11 0-1.574 51.776-53.76 53.1-53.521 1.493.27 3.338 6.773 5.975 21.06 2.353 12.751 2.343 48.232-.017 61.072-2.37 12.886-5.358 24.1-6.527 24.49-.553.184-12.599-11.512-26.768-25.991M64.75 170.903c-26.893-26.902-29.95-30.252-29.354-32.175 1.335-4.308 8.33-18.529 12.565-25.546 28.808-47.732 75.832-79.809 131.42-89.647 15.231-2.696 45.201-2.944 59.361-.492 41.827 7.244 76.236 24.833 104.91 53.625 15.582 15.647 27.713 32.758 36.971 52.151 6.183 12.95 8.732 8.549-24.429 42.185-15.984 16.213-29.386 29.61-29.782 29.768-.396.16-5.695-4.244-11.775-9.786-32.548-29.67-61.86-48.734-85.116-55.361-7.771-2.215-9.327-2.357-22.05-2.009-12.468.34-14.427.627-21.965 3.212-23.775 8.152-49.675 26.108-80.457 55.78-4.75 4.579-9.006 8.325-9.457 8.325s-14.329-13.513-30.839-30.029z'/></svg>",
"webassembly": "<svg viewBox='0 0 32 32'><path fill='#7c4dff' d='M22 18h4v4h-4z'/><path fill='#7c4dff' d='M20 2a4 4 0 0 1-8 0H2v28h28V2Zm-2 24h-2v2h-4v-2h-2v2H6v-2H4V16h2v10h4V16h2v10h4V16h2Zm10 2h-2v-4h-4v4h-2V18h2v-2h4v2h2Z'/></svg>",
"webhint": "<svg viewBox='0 0 120 120'><path fill='#1e88e5' d='M115.45 23.033S97.961 33.27 97.534 33.412c-.427.284-.852.57-1.137.854-1.422 1.421-1.848 3.41-1.422 5.26.285.852.711 1.849 1.422 2.56.711.71 1.564 1.137 2.559 1.422 1.848.426 3.84 0 5.262-1.422q.638-.64.851-1.28l.143-.427 2.56-4.692zm-39.102 9.242c-27.441 0-31.99 13.08-31.99 29.29 0 3.838.569 7.962-1.99 11.942-3.84 5.972-8.957 5.828-10.236 5.828-1.706 0-7.962-.993-8.246-2.841h.994c6.682 0 11.658-5.404 11.658-12.655v-2.56h-5.686c-4.123 0-7.82 1.849-10.238 5.12-2.417-3.271-6.113-5.12-10.236-5.12h-5.83v2.56c0 7.11 5.688 12.795 12.797 12.795h1.848c0 4.124 5.687 20.332 47.63 20.332 16.352 0 40.665-2.843 40.665-33.697 0-5.829-1.848-11.23-4.691-15.78-.996.284-1.992.568-3.13.568a8.92 8.92 0 0 1-8.956-8.957q0-1.493.425-2.986c-4.265-2.702-8.53-3.838-14.787-3.838z'/></svg>",
- "webpack": "<svg viewBox='0 0 24 24'><path fill='#FAFAFA' fill-opacity='.785' d='m19.376 15.988-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z'/><path fill='#90CAF9' d='M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.95.95 0 0 0-.57-.179zm0 2.15 7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58l-4-2.31zm10 0-4 2.308v3.58l4-2.308z'/><path fill='#0277BD' d='m12.286 6.21-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58l-4-2.31zm10 0-4 2.308v3.58l4-2.308z'/></svg>",
+ "webpack": "<svg viewBox='0 0 24 24'><path fill='#fafafa' fill-opacity='.785' d='m19.376 15.988-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z'/><path fill='#90caf9' d='M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.95.95 0 0 0-.57-.179zm0 2.15 7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58l-4-2.31zm10 0-4 2.308v3.58l4-2.308z'/><path fill='#0277bd' d='m12.286 6.21-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58l-4-2.31zm10 0-4 2.308v3.58l4-2.308z'/></svg>",
"wepy": "<svg viewBox='0 0 32 32'><path fill='#4caf50' d='M16 2A14 14 0 0 0 2 16v12a2 2 0 0 0 2 2h12a14 14 0 0 0 0-28m0 24a10 10 0 1 1 10-10 10.01 10.01 0 0 1-10 10'/></svg>",
"werf": "<svg viewBox='0 0 100 111'><path fill='#1e88e5' d='m74.849 59.509 3.307 9.697 5.768-9.697zm-.601-20.967v17.932h9.788zm-3.095 10.125V34.182h-4.938zm-10.77-31.582 4.795 14.06h5.032l-8.166-14.959-1.236.668-.425.228zm-6.74 78.095h7.34l-2.77-5.008-.917.976a71.108 69.794 0 0 1-3.653 4.031zm-3.594-4.567.125-29.212a48.51 47.615 0 0 0-3.263 2.064c.125 3.608.715 15.891 3.137 27.147zm-8.374-23.137c.112 2.31.811 10.604 5.165 19.634-1.57-8.843-2.14-17.601-2.34-21.906a62.074 60.926 0 0 0-2.825 2.272m-2.68 27.704h7.64l-.584-.672a69.587 68.302 0 0 1-4.088-4.69l-2.967 5.36zm-2.442-22.688c.275 1.63 2.424 7.773 7.763 15.086-3.516-7.485-4.631-14.336-4.98-17.987a46.725 45.861 0 0 0-2.784 2.901zm3.86-15.062v6.948a46.951 46.083 0 0 1 3.884-2.96c3.425-2.488 4.818-3 5.698-3.055h-.006v-.933zm-24.358 2.08 6.167 10.364 4.244-10.364zm11.154-16.456L16.532 56.473h10.677zm3.097-3.889v10.974l7.509-18.34-1.004-.812-6.507 8.178zM80.198 71.8h6.218l1.548 1.52v23.377l-1.548 1.522H13.563L12.016 96.7V73.32l1.547-1.52h6.22l-7.76-13.04.115-1.701 23.21-29.167 2.11-.229.038-.052 10.428 7.677-1.857 2.431-5.638-4.153L24.199 73.2l12.145 20.407 3.595-6.496c-6.09-8.683-6.534-14.664-6.556-15l.363-1.07a62.861 61.699 0 0 1 3.57-3.827v-11.3l1.55-1.519h22.247l1.548 1.52V66.86a62.175 61.026 0 0 1 3.932 4.182l.361 1.067c-.019.343-.473 6.45-6.756 15.288l3.438 6.21 12.444-20.91-18.45-54.12-5.06 2.74-1.493-2.66 10.841-5.872 2.113.614 23.954 43.878'/></svg>",
"windicss": "<svg fill='none' viewBox='0 0 32 32'><path stroke='#42a5f5' stroke-miterlimit='3.339' stroke-width='4' d='M22 12a4 4 0 1 1 4 4H2m14 10a4 4 0 1 0 4-4H10M8 6a4 4 0 1 1 4 4H2'/><path fill='#42a5f5' d='M2 20h4v4H2z'/></svg>",
- "wolframlanguage": "<svg viewBox='0 0 24 24'><g transform='translate(-.009 -.001)scale(.12121)'><circle cx='99.197' cy='98.946' r='83.28' fill='#212121'/><path fill='#e53935' d='M182.53 98.828a83.4 83.4 0 0 1-39.14 70.722.06.06 0 0 1-.037.019l-28.62-35.664 23.71 2.611s11.385 1.178 13.979 0c2.373-.938 15.174-18.963 15.174-18.963s-36.75-23.23-49.312-36.032c1.435-21.575-1.655-50.269-1.655-50.03-9.252 9.234-10.43 10.668-19.681 19.202-4.028-13.04-5.923-17.546-9.95-30.587-12.104 9.95-21.337 26.799-27.977 46.48a79 79 0 0 0-4.23 5.095 110 110 0 0 0-2.668 3.66 115 115 0 0 0-5.131 8 173 173 0 0 0-3.403 6.052c-7.707 14.476-14.034 31.067-19.515 46.002a1 1 0 0 1-.092-.184C8.993 104.299 14.48 67.36 37.804 42.138c23.325-25.223 59.722-33.574 91.71-21.045 31.988 12.53 53.029 43.382 53.017 77.736z'/><path fill='#FAFAFA' d='M101.45 69.178s-1.416-8.295-2.373-11.367c6.401-6.18 7.357-7.118 13.52-13.041.477 11.845.238 18.007-.479 32.482-3.55-3.568-10.668-8.075-10.668-8.075zm-27.737 40.778s-6.64-4.028-11.624-4.727c1.435-3.33 5.224-7.597 6.18-8.774-1.913.7-15.652 6.861-17.087 12.084a75 75 0 0 1 11.385 3.79 36 36 0 0 0-8.773 20.158s21.814-3.329 38.184-1.195c.283.168.608.25.938.239l8.534.239 27.11 45.137.222.35c-.037.018-.056.036-.074.036-51.133 18.485-88.085-15.542-95.975-27.442q.05-.153.074-.312c7.1-30.018 15.855-65.94 29.999-76.552 7.357-12.82 9.49-31.783 22.752-41.734 3.329 9.95 8.553 30.588 12.103 40.538 15.653 15.653 39.36 35.094 55.234 43.15 1.655.956 3.789 7.596 3.789 7.596l-6.401 8.056-68.275-6.879a55 55 0 0 0-4.58-.184 87 87 0 0 0-14.144 1.361c3.31-8.295 10.429-14.935 10.429-14.935m22.053-8.774c3.789-.46 7.817.957 12.323 3.569 4.267-1.196 4.745-1.435 9.013-2.612-5.463-4.028-11.385-8.295-19.442-7.118a47 47 0 0 0-1.895 6.161z'/></g></svg>",
+ "wolframlanguage": "<svg viewBox='0 0 24 24'><g transform='translate(-.009 -.001)scale(.12121)'><circle cx='99.197' cy='98.946' r='83.28' fill='#212121'/><path fill='#e53935' d='M182.53 98.828a83.4 83.4 0 0 1-39.14 70.722.06.06 0 0 1-.037.019l-28.62-35.664 23.71 2.611s11.385 1.178 13.979 0c2.373-.938 15.174-18.963 15.174-18.963s-36.75-23.23-49.312-36.032c1.435-21.575-1.655-50.269-1.655-50.03-9.252 9.234-10.43 10.668-19.681 19.202-4.028-13.04-5.923-17.546-9.95-30.587-12.104 9.95-21.337 26.799-27.977 46.48a79 79 0 0 0-4.23 5.095 110 110 0 0 0-2.668 3.66 115 115 0 0 0-5.131 8 173 173 0 0 0-3.403 6.052c-7.707 14.476-14.034 31.067-19.515 46.002a1 1 0 0 1-.092-.184C8.993 104.299 14.48 67.36 37.804 42.138c23.325-25.223 59.722-33.574 91.71-21.045 31.988 12.53 53.029 43.382 53.017 77.736z'/><path fill='#fafafa' d='M101.45 69.178s-1.416-8.295-2.373-11.367c6.401-6.18 7.357-7.118 13.52-13.041.477 11.845.238 18.007-.479 32.482-3.55-3.568-10.668-8.075-10.668-8.075zm-27.737 40.778s-6.64-4.028-11.624-4.727c1.435-3.33 5.224-7.597 6.18-8.774-1.913.7-15.652 6.861-17.087 12.084a75 75 0 0 1 11.385 3.79 36 36 0 0 0-8.773 20.158s21.814-3.329 38.184-1.195c.283.168.608.25.938.239l8.534.239 27.11 45.137.222.35c-.037.018-.056.036-.074.036-51.133 18.485-88.085-15.542-95.975-27.442q.05-.153.074-.312c7.1-30.018 15.855-65.94 29.999-76.552 7.357-12.82 9.49-31.783 22.752-41.734 3.329 9.95 8.553 30.588 12.103 40.538 15.653 15.653 39.36 35.094 55.234 43.15 1.655.956 3.789 7.596 3.789 7.596l-6.401 8.056-68.275-6.879a55 55 0 0 0-4.58-.184 87 87 0 0 0-14.144 1.361c3.31-8.295 10.429-14.935 10.429-14.935m22.053-8.774c3.789-.46 7.817.957 12.323 3.569 4.267-1.196 4.745-1.435 9.013-2.612-5.463-4.028-11.385-8.295-19.442-7.118a47 47 0 0 0-1.895 6.161z'/></g></svg>",
"word": "<svg viewBox='0 0 24 24'><path fill='#01579b' d='M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5zM7 13l1.5 7h2l1.5-3 1.5 3h2l1.5-7h1v-2h-4v2h1l-.9 4.2L13 15h-2l-1.1 2.2L9 13h1v-2H6v2z'/></svg>",
"wrangler": "<svg viewBox='0 0 32 32'><path fill='#f57f17' d='M22 20H10.5a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5H22v-6.449A5.95 5.95 0 0 0 18 10h-2a5.98 5.98 0 0 0-4.463 2H10a4 4 0 0 0-4 4 4 4 0 0 0-4 4v1.5a.5.5 0 0 0 .5.5H22Z'/><path fill='#ffab40' d='M24 14v4h1.5a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5H24v2h5.5a.5.5 0 0 0 .5-.5V20a6 6 0 0 0-6-6'/></svg>",
"wxt": "<svg viewBox='0 0 16 16'><path fill='#00c853' d='M14 8.9c.117 1.136-.854 2.043-2 2.1v2c0 .663-.337 1-1 1H8v-1c0-1.52-2-1.34-2 0v1H3c-.663 0-1-.337-1-1v-3h1s1-.1 1-1-1-1-1-1H2V5c0-.663.337-1 1-1h2c.025-1.06.885-1.995 2-2 1.123-.005 1.996.93 2 2h2c.663 0 1 .337 1 1v2c1.082.067 2.117.798 2 2m-3 1h1c.497 0 1-.503 1-1s-.503-1-1-1h-1V5H8V4c0-.497-.503-1-1-1s-1 .503-1 1v1H3v2c1.148.341 1.98.744 2 2 .02 1.226-.707 1.666-2 2v2h2c.156-1.452 1.055-1.948 2-2 1-.056 2.098.695 2 2h2z'/></svg>",
"xaml": "<svg viewBox='0 0 32 32'><path fill='#42a5f5' d='m32 16-5.387 9.333L24.307 24l4.613-8-4.613-8 2.306-1.333z'/><path fill='#42a5f5' d='m25.24 16-4.627 8h-9.226L6.76 16l4.627-8h9.226z'/><path fill='#42a5f5' d='m0 16 5.387-9.333L7.693 8 3.08 16l4.613 8-2.306 1.333z'/></svg>",
"xmake": "<svg viewBox='0 0 16 16'><circle cx='8' cy='8' r='7' fill='#e0f2f1'/><path fill='#e0f2f1' d='M11.759 2.944a6.3 6.3 0 0 0-8.932 1.462l3.281 2.023z'/><path fill='#8bc34a' d='M1.796 9.088 6.107 6.43l-3.28-2.025A6.27 6.27 0 0 0 1.7 8a6.4 6.4 0 0 0 .096 1.088'/><path fill='#4db6ac' d='M13.536 11.01a6.3 6.3 0 0 0-1.777-8.066l-5.65 3.485z'/><path fill='#009688' d='M1.796 9.088a6.3 6.3 0 0 0 11.74 1.922L6.108 6.428z'/></svg>",
"xml": "<svg viewBox='0 0 24 24'><path fill='#8bc34a' d='M13 9h5.5L13 3.5zM6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5 3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41zm11.16 0-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41z'/></svg>",
- "yaml": "<svg viewBox='0 0 24 24'><path fill='#FF5252' d='M13 9h5.5L13 3.5zM6 2h8l6 6v12c0 1.1-.9 2-2 2H6c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2m12 16v-2H9v2zm-4-4v-2H6v2z'/></svg>",
+ "yaml": "<svg viewBox='0 0 24 24'><path fill='#ff5252' d='M13 9h5.5L13 3.5zM6 2h8l6 6v12c0 1.1-.9 2-2 2H6c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2m12 16v-2H9v2zm-4-4v-2H6v2z'/></svg>",
"yang": "<svg viewBox='0 0 24 24'><path fill='#42a5f5' d='M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8 8 8 0 0 0 8 8 4 4 0 0 1-4-4 4 4 0 0 1 4-4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 2.5A1.5 1.5 0 0 1 13.5 8 1.5 1.5 0 0 1 12 9.5 1.5 1.5 0 0 1 10.5 8 1.5 1.5 0 0 1 12 6.5m0 8a1.5 1.5 0 0 0-1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5 1.5 1.5 0 0 0-1.5-1.5'/></svg>",
"yarn": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M27.575 23.967a9.9 9.9 0 0 0-3.751 1.726 22.6 22.6 0 0 1-5.537 2.504 1.55 1.55 0 0 1-.931.52 59 59 0 0 1-6.11.548c-1.102.008-1.777-.282-1.965-.735a1.49 1.49 0 0 1 .82-1.965 3.6 3.6 0 0 1-.486-.359c-.163-.162-.334-.487-.385-.367-.213.52-.324 1.794-.897 2.366-.786.795-2.273.53-3.153.069-.965-.513.069-1.718.069-1.718a.69.69 0 0 1-.94-.324 4.6 4.6 0 0 1-.632-2.794 5.2 5.2 0 0 1 1.674-2.76 8.84 8.84 0 0 1 .624-4.17 9.9 9.9 0 0 1 3-3.469S7.136 11.015 7.82 9.177c.444-1.196.623-1.187.769-1.239a3.44 3.44 0 0 0 1.375-.811 4.99 4.99 0 0 1 4.178-1.607s1.094-3.357 2.12-2.7a17.4 17.4 0 0 1 1.452 2.735s1.213-.71 1.35-.445a10.74 10.74 0 0 1 .495 5.81 13.3 13.3 0 0 1-2.46 5.127c-.129.214 1.47.889 2.477 3.683.932 2.554.103 4.699.248 4.938.026.043.034.06.034.06s1.068.085 3.213-1.24a8.05 8.05 0 0 1 4.05-1.52 1.026 1.026 0 0 1 .453 2Z'/></svg>",
"zeabur": "<svg viewBox='0 0 32 32'><path fill='#cfd8dc' d='m14 20 4 4-4 4H2v-8h8l10-8-6-4 6-4h10v8Z'/><path fill='#651fff' d='M20 4H2v8h18Z'/><path fill='#ff3d00' d='M30 20H14v8h16Z'/></svg>",
diff --git a/options/locale/TRANSLATORS b/options/locale/TRANSLATORS
index e67255f2fb..4eee2b26c1 100644
--- a/options/locale/TRANSLATORS
+++ b/options/locale/TRANSLATORS
@@ -66,6 +66,7 @@ Piotr Orzechowski <piotr AT orzechowski DOT tech>
Richard Bukovansky <richard DOT bukovansky AT gmail DOT com>
Robert Nuske <robert DOT nuske AT web DOT de>
Robin Hübner <profan AT prfn DOT se>
+Ryo Hanafusa <ryo7gumi AT gmail DOT com>
SeongJae Park <sj38 DOT park AT gmail DOT com>
Thiago Avelino <thiago AT avelino DOT xxx>
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index 63ab9f9d3a..aeefaf6064 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -1330,7 +1330,6 @@ editor.update=Aktualizovat %s
editor.delete=Odstranit %s
editor.patch=Použít záplatu
editor.patching=Záplatování:
-editor.fail_to_apply_patch=Nelze použít záplatu „%s“
editor.new_patch=Nová záplata
editor.commit_message_desc=Přidat volitelný rozšířený popis…
editor.signoff_desc=Přidat Signed-off-by podpis přispěvatele na konec zprávy o commitu.
@@ -1348,8 +1347,6 @@ editor.branch_already_exists=Větev „%s“ již existuje v tomto repozitáři.
editor.directory_is_a_file=Jméno adresáře „%s“ je již použito jako jméno souboru v tomto repozitáři.
editor.file_is_a_symlink=`„%s“ je symbolický odkaz. Symbolické odkazy nemohou být upravovány ve webovém editoru`
editor.filename_is_a_directory=Jméno souboru „%s“ je již použito jako jméno adresáře v tomto repozitáři.
-editor.file_editing_no_longer_exists=Upravovaný soubor „%s“ již není souÄástí tohoto repozitáře.
-editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není souÄástí tohoto repozitáře.
editor.file_changed_while_editing=Obsah souboru byl zmÄ›nÄ›n od doby, kdy jste zaÄaly s úpravou. <a target="_blank" rel="noopener noreferrer" href="%s">KliknÄ›te zde</a>, abyste je zobrazili, nebo <strong>potvrÄte zmÄ›ny jeÅ¡tÄ› jednou</strong> pro jejich pÅ™epsání.
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
editor.commit_id_not_matching=ID commitu se neshoduje s ID, když jsi zaÄal/a s úpravami. Odevzdat do záplatové vÄ›tve a poté slouÄit.
@@ -1357,8 +1354,6 @@ editor.push_out_of_date=Nahrání se zdá být zastaralé.
editor.commit_empty_file_header=Odevzdat prázdný soubor
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. PokraÄovat?
editor.no_changes_to_show=Žádné změny k zobrazení.
-editor.fail_to_update_file=Nepodařilo se aktualizovat/vytvořit soubor „%s“.
-editor.fail_to_update_file_summary=Chybové hlášení:
editor.push_rejected_no_message=ZmÄ›na byla serverem zamítnuta bez zprávy. Prosím, zkontrolujte háÄky Gitu.
editor.push_rejected=ZmÄ›na byla serverem zamítnuta. Prosím, zkontrolujte háÄky Gitu.
editor.push_rejected_summary=Úplná zpráva o odmítnutí:
@@ -1373,6 +1368,7 @@ editor.require_signed_commit=Větev vyžaduje podepsaný commit
editor.cherry_pick=Cherry-pick %s na:
editor.revert=Vrátit %s na:
+
commits.desc=Procházet historii změn zdrojového kódu.
commits.commits=Commity
commits.no_commits=Žádné spoleÄné commity. „%s“ a „%s“ mají zcela odliÅ¡nou historii.
@@ -1921,7 +1917,6 @@ pulls.cmd_instruction_checkout_title=Checkout
pulls.cmd_instruction_checkout_desc=Z vašeho repositáře projektu se podívejte na novou větev a vyzkoušejte změny.
pulls.cmd_instruction_merge_title=SlouÄit
pulls.cmd_instruction_merge_desc=SluÄte zmÄ›ny a aktualizujte je na Gitea.
-pulls.cmd_instruction_merge_warning=Varování: Tato operace nemůže slouÄit požadavek na natažení, protože „autodetekce manuálních slouÄení“ nebyla povolena
pulls.clear_merge_message=Vymazat zprávu o slouÄení
pulls.clear_merge_message_hint=Vymazání zprávy o slouÄení odstraní pouze obsah zprávy a ponechá generované přídavky gitu jako "Co-AuthoreBy …".
@@ -2327,7 +2322,6 @@ settings.event_repository=Repozitář
settings.event_repository_desc=Repozitář vytvořen nebo smazán.
settings.event_header_issue=Události úkolů
settings.event_issues=Úkoly
-settings.event_issues_desc=Úkol otevřen, uzavřen, znovu otevřen nebo upraven.
settings.event_issue_assign=Úkol přiřazen
settings.event_issue_assign_desc=Úkol přiřazen nebo nepřiřazen.
settings.event_issue_label=Úkol oštítkován
@@ -2338,7 +2332,6 @@ settings.event_issue_comment=Komentář k úkolu
settings.event_issue_comment_desc=Komentář úkolu přidán, upraven nebo smazán.
settings.event_header_pull_request=Události pull requestu
settings.event_pull_request=Pull request
-settings.event_pull_request_desc=Pull request otevřen, uzavřen, znovu otevřen nebo upraven.
settings.event_pull_request_assign=Pull request přiřazen
settings.event_pull_request_assign_desc=Pull request přiřazen nebo nepřiřazen.
settings.event_pull_request_label=Pull request oštítkován
@@ -2780,15 +2773,13 @@ settings.visibility.private_shortname=Soukromý
settings.update_settings=Upravit nastavení
settings.update_setting_success=Nastavení organizace bylo upraveno.
-settings.change_orgname_prompt=Poznámka: Změna názvu organizace také změní adresu URL vaší organizace a uvolní staré jméno této organizace.
-settings.change_orgname_redirect_prompt=Staré jméno bude přesměrovávat, dokud nebude znovu obsazeno.
+
+
settings.update_avatar_success=Avatar organizace byl aktualizován.
settings.delete=Smazat organizaci
settings.delete_account=Smazat tuto organizaci
settings.delete_prompt=Organizace bude trvale odstraněna. Tato změna <strong>NEMŮŽE</strong> být vrácena!
settings.confirm_delete_account=Potvrdit smazání
-settings.delete_org_title=Smazat organizaci
-settings.delete_org_desc=Tato organizace bude trvale smazána. PokraÄovat?
settings.hooks_desc=PÅ™idat webové háÄky, které budou spouÅ¡tÄ›ny pro <strong>vÅ¡echny repozitáře</strong> v této organizaci.
settings.labels_desc=Přidejte štítky, které mohou být použity pro úkoly <strong>všech repositářů</strong> v rámci této organizace.
@@ -3667,12 +3658,13 @@ owner.settings.chef.keypair.description=Pro autentizaci do registru Chef je zapo
secrets=Tajné klíÄe
description=Tejné klíÄe budou pÅ™edány urÄitým akcím a nelze je pÅ™eÄíst jinak.
none=Zatím zde nejsou žádné tajné klíÄe.
-creation=PÅ™idat tajný klíÄ
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Popis
creation.name_placeholder=nerozliÅ¡ovat velká a malá písmena, pouze alfanumerické znaky nebo podtržítka, nemohou zaÄínat na GITEA_ nebo GITHUB_
creation.value_placeholder=Vložte jakýkoliv obsah. Mezery na zaÄátku a konci budou vynechány.
-creation.success=Tajný klÃ­Ä â€ž%s“ byl pÅ™idán.
-creation.failed=NepodaÅ™ilo se pÅ™idat tajný klíÄ.
+
+
deletion=Odstranit tajný klíÄ
deletion.description=OdstranÄ›ní tajného klíÄe je trvalé a nelze ho vrátit zpÄ›t. PokraÄovat?
deletion.success=Tajný klÃ­Ä byl odstranÄ›n.
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index 43333f8ac6..a1ff2f914e 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -113,9 +113,11 @@ copy_type_unsupported=Dieser Dateityp kann nicht kopiert werden
write=Verfassen
preview=Vorschau
loading=Laden…
+files=Dateien
error=Fehler
error404=Die Seite, die Du versuchst aufzurufen, <strong>existiert nicht</strong> oder <strong>Du bist nicht berechtigt</strong>, diese anzusehen.
+error503=Der Server konnte deine Anfrage nicht abschließen. Bitte versuche es später erneut.
go_back=Zurück
invalid_data=Ungültige Daten: %v
@@ -128,6 +130,7 @@ pin=Anheften
unpin=Loslösen
artifacts=Artefakte
+expired=Abgelaufen
confirm_delete_artifact=Bist du sicher, dass du das Artefakt '%s' löschen möchtest?
archived=Archiviert
@@ -169,6 +172,10 @@ search=Suche ...
type_tooltip=Suchmodus
fuzzy=Ähnlich
fuzzy_tooltip=Ergebnisse einbeziehen, die dem Suchbegriff ähnlich sind
+words=Wörter
+words_tooltip=Nur Suchbegriffe einbeziehen, die den Suchbegriffen exakt entsprechen
+regexp=Regexp
+regexp_tooltip=Nur Suchbegriffe einbeziehen, die dem Regexp exakt entsprechen
exact=Exakt
exact_tooltip=Nur Suchbegriffe einbeziehen, die dem exakten Suchbegriff entsprechen
repo_kind=Repositories durchsuchen ...
@@ -444,6 +451,7 @@ use_scratch_code=Einmalpasswort verwenden
twofa_scratch_used=Du hast dein Einmalpasswort verwendet. Du wurdest zu den Einstellung der Zwei-Faktor-Authentifizierung umgeleitet, dort kannst du dein Gerät abmelden oder ein neues Einmalpasswort erzeugen.
twofa_passcode_incorrect=Ungültige PIN. Wenn du dein Gerät verloren hast, verwende dein Einmalpasswort.
twofa_scratch_token_incorrect=Das Einmalpasswort ist falsch.
+twofa_required=Du musst die Zwei-Faktor-Authentifizierung einrichten, um Zugriff auf die Repositories zu erhalten, oder versuche dich erneut anzumelden.
login_userpass=Anmelden
login_openid=OpenID
oauth_signup_tab=Neues Konto registrieren
@@ -452,6 +460,7 @@ oauth_signup_submit=Konto vervollständigen
oauth_signin_tab=Mit existierendem Konto verbinden
oauth_signin_title=Anmelden um verbundenes Konto zu autorisieren
oauth_signin_submit=Konto verbinden
+oauth.signin.error.general=Beim Verarbeiten der Autorisierungsanfrage ist ein Fehler aufgetreten: %s. Wenn dieser Fehler weiterhin besteht, wende dich bitte an den Administrator.
oauth.signin.error.access_denied=Die Autorisierungsanfrage wurde abgelehnt.
oauth.signin.error.temporarily_unavailable=Autorisierung fehlgeschlagen, da der Authentifizierungsserver vorübergehend nicht verfügbar ist. Bitte versuch es später erneut.
oauth_callback_unable_auto_reg=Automatische Registrierung ist aktiviert, aber der OAuth2-Provider %[1]s hat fehlende Felder zurückgegeben: %[2]s, kann den Account nicht automatisch erstellen. Bitte erstelle oder verbinde einen Account oder kontaktieren den Administrator.
@@ -724,6 +733,8 @@ public_profile=Öffentliches Profil
biography_placeholder=Erzähle uns ein wenig über Dich selbst! (Du kannst Markdown verwenden)
location_placeholder=Teile Deinen ungefähren Standort mit anderen
profile_desc=Lege fest, wie dein Profil anderen Benutzern angezeigt wird. Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und webbasierte Git-Operationen verwendet.
+password_username_disabled=Du bist nicht berechtigt, den Benutzernamen zu ändern. Bitte kontaktiere Deinen Seitenadministrator für weitere Details.
+password_full_name_disabled=Du bist nicht berechtigt, den vollständigen Namen zu ändern. Bitte kontaktiere Deinen Seitenadministrator für weitere Details.
full_name=Vollständiger Name
website=Webseite
location=Standort
@@ -918,6 +929,9 @@ permission_not_set=Nicht festgelegt
permission_no_access=Kein Zugriff
permission_read=Lesen
permission_write=Lesen und Schreiben
+permission_anonymous_read=Anonymes Lesen
+permission_everyone_read=Alle lesen
+permission_everyone_write=Alle schreiben
access_token_desc=Ausgewählte Token-Berechtigungen beschränken die Authentifizierung auf die entsprechenden <a %s>API</a>-Routen. Lies die <a %s>Dokumentation</a> für mehr Informationen.
at_least_one_permission=Du musst mindestens eine Berechtigung auswählen, um ein Token zu erstellen
permissions_list=Berechtigungen:
@@ -1020,6 +1034,9 @@ new_repo_helper=Ein Repository enthält alle Projektdateien, einschließlich des
owner=Besitzer
owner_helper=Einige Organisationen könnten in der Dropdown-Liste nicht angezeigt werden, da die Anzahl an Repositories begrenzt ist.
repo_name=Repository-Name
+repo_name_profile_public_hint=.profile ist ein spezielles Repository, mit dem du die README.md zu deinem öffentlichen Organisationsprofil hinzufügen kannst, das für jeden sichtbar ist. Stelle sicher, dass es öffentlich ist und initialisiere es mit einer README im Profilverzeichnis, um loszulegen.
+repo_name_profile_private_hint=.profile ist ein spezielles Repository, mit dem du die README.md zu deinem privaten Organisationsprofil hinzufügen kannst, das nur für Organisationsmitglieder sichtbar ist. Stelle sicher, dass es privat ist und initialisiere es mit einer README im Profilverzeichnis, um loszulegen.
+repo_name_helper=Ein guter Repository-Name besteht normalerweise aus kurzen, unvergesslichen und einzigartigen Schlagwörtern. Ein Repository namens ".profile" or ".profile-private" kann verwendet werden, um die README.md auf dem Benutzer- oder Organisationsprofil anzuzeigen.
repo_size=Repository-Größe
template=Template
template_select=Vorlage auswählen
@@ -1116,6 +1133,7 @@ blame.ignore_revs=Revisionen in <a href="%s">.git-blame-ignore-revs</a> werden i
blame.ignore_revs.failed=Fehler beim Ignorieren der Revisionen in <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Zeigt maximal 30 Benutzer
+tree_path_not_found=Pfad %[1]s existiert nicht in %[2]s
transfer.accept=Übertragung Akzeptieren
transfer.accept_desc=`Übertragung nach "%s"`
@@ -1126,6 +1144,7 @@ transfer.no_permission_to_reject=Du hast keine Berechtigung, diesen Transfer abz
desc.private=Privat
desc.public=Öffentlich
+desc.public_access=Öffentlicher Zugriff
desc.template=Template
desc.internal=Intern
desc.archived=Archiviert
@@ -1233,6 +1252,7 @@ create_new_repo_command=Erstelle ein neues Repository von der Kommandozeile aus
push_exist_repo=Bestehendes Repository via Kommandozeile pushen
empty_message=Dieses Repository hat keinen Inhalt.
broken_message=Die Git-Daten, die diesem Repository zugrunde liegen, können nicht gelesen werden. Kontaktiere den Administrator deiner Instanz oder lösche dieses Repository.
+no_branch=Dieses Repository hat keine Branches.
code=Code
code.desc=Zugriff auf Quellcode, Dateien, Commits und Branches.
@@ -1331,7 +1351,6 @@ editor.update=%s aktualisiert
editor.delete=%s gelöscht
editor.patch=Patch anwenden
editor.patching=Patche:
-editor.fail_to_apply_patch=Patch "%s" nicht anwendbar
editor.new_patch=Neuer Patch
editor.commit_message_desc=Eine ausführlichere (optionale) Beschreibung hinzufügen…
editor.signoff_desc=Am Ende der Commit Nachricht einen Signed-off-by Anhang vom Committer hinzufügen.
@@ -1344,13 +1363,13 @@ 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=Ungültiger Dateiname: "%s".
+editor.commit_email=Commit-E-Mail-Adresse
+editor.invalid_commit_email=Die E-Mail-Adresse für den Commit ist ungültig.
editor.branch_does_not_exist=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`
editor.filename_is_a_directory=Der Dateiname "%s" wird bereits als Verzeichnisname in diesem Repository verwendet.
-editor.file_editing_no_longer_exists=Die bearbeitete Datei "%s" existiert nicht mehr in diesem Repository.
-editor.file_deleting_no_longer_exists=Die zu löschende Datei "%s" existiert nicht mehr in diesem Repository.
editor.file_changed_while_editing=Der Inhalt der Datei hat sich seit dem Beginn der Bearbeitung geändert. <a target="_blank" rel="noopener noreferrer" href="%s">Hier klicken</a>, um die Änderungen anzusehen, oder <strong>Änderungen erneut comitten</strong>, um sie zu überschreiben.
editor.file_already_exists=Eine Datei mit dem Namen '%s' existiert bereits in diesem Repository.
editor.commit_id_not_matching=Die Commit-ID stimmt nicht mit der ID überein, bei welcher du mit der Bearbeitung begonnen hast. Commite in einen Patch-Branch und merge daraufhin.
@@ -1358,8 +1377,6 @@ editor.push_out_of_date=Der Push scheint veraltet zu sein.
editor.commit_empty_file_header=Leere Datei committen
editor.commit_empty_file_text=Die Datei, die du commiten willst, ist leer. Fortfahren?
editor.no_changes_to_show=Keine Änderungen vorhanden.
-editor.fail_to_update_file=Fehler beim Aktualisieren/Erstellen der Datei "%s".
-editor.fail_to_update_file_summary=Fehlermeldung:
editor.push_rejected_no_message=Die Änderung wurde vom Server ohne Nachricht abgelehnt. Bitte überprüfe die Git Hooks.
editor.push_rejected=Die Änderung wurde vom Server abgelehnt. Bitte überprüfe die Git Hooks.
editor.push_rejected_summary=Vollständige Ablehnungsmeldung:
@@ -1374,6 +1391,7 @@ editor.require_signed_commit=Branch erfordert einen signierten Commit
editor.cherry_pick=Cherry-Picke %s von:
editor.revert=%s zurücksetzen auf:
+
commits.desc=Durchsuche die Quellcode-Änderungshistorie.
commits.commits=Commits
commits.no_commits=Keine gemeinsamen Commits. "%s" und "%s" haben vollständig unterschiedliche Historien.
@@ -1392,6 +1410,7 @@ commits.signed_by_untrusted_user_unmatched=Signiert von nicht vertrauenswürdige
commits.gpg_key_id=GPG-Schlüssel-ID
commits.ssh_key_fingerprint=SSH-Key-Fingerabdruck
commits.view_path=An diesem Punkt im Verlauf anzeigen
+commits.view_file_diff=Änderungen an dieser Datei in diesem Commit anzeigen
commit.operations=Operationen
commit.revert=Zurücksetzen
@@ -1452,6 +1471,8 @@ issues.filter_milestones=Meilenstein filtern
issues.filter_projects=Projekt filtern
issues.filter_labels=Label filtern
issues.filter_reviewers=Reviewer filtern
+issues.filter_no_results=Keine Ergebnisse
+issues.filter_no_results_placeholder=Versuche, deine Suchfilter anzupassen.
issues.new=Neues Issue
issues.new.title_empty=Der Titel kann nicht leer sein
issues.new.labels=Label
@@ -1527,11 +1548,14 @@ issues.filter_project=Projekt
issues.filter_project_all=Alle Projekte
issues.filter_project_none=Kein Projekt
issues.filter_assignee=Zuständig
+issues.filter_assignee_no_assignee=Niemandem zugewiesen
+issues.filter_assignee_any_assignee=Jemandem zugewiesen
issues.filter_poster=Autor
issues.filter_user_placeholder=Benutzer suchen
issues.filter_user_no_select=Alle Benutzer
issues.filter_type=Typ
issues.filter_type.all_issues=Alle Issues
+issues.filter_type.all_pull_requests=Alle Pull-Requests
issues.filter_type.assigned_to_you=Dir zugewiesen
issues.filter_type.created_by_you=Von dir erstellt
issues.filter_type.mentioning_you=Hat dich erwähnt
@@ -1623,12 +1647,15 @@ issues.save=Speichern
issues.label_title=Labelname
issues.label_description=Labelbeschreibung
issues.label_color=Labelfarbe
+issues.label_color_invalid=Ungültige Farbe
issues.label_exclusive=Exklusiv
issues.label_archive=Label archivieren
issues.label_archived_filter=Archivierte Labels anzeigen
issues.label_archive_tooltip=Archivierte Labels werden bei der Suche nach Label standardmäßig von den Vorschlägen ausgeschlossen.
issues.label_exclusive_desc=Nenne das Label <code>Bereich/Element</code> um es gegenseitig ausschließend mit anderen <code>Bereich/</code> Labels zu machen.
issues.label_exclusive_warning=Alle im Konflikt stehenden Labels werden beim Bearbeiten der Labels eines Issues oder eines Pull-Requests entfernt.
+issues.label_exclusive_order=Sortierreihenfolge
+issues.label_exclusive_order_tooltip=Exklusive Labels im gleichen Bereich werden nach dieser numerischen Reihenfolge sortiert.
issues.label_count=%d Label
issues.label_open_issues=%d offene Issues
issues.label_edit=Bearbeiten
@@ -1680,14 +1707,18 @@ issues.timetracker_timer_manually_add=Zeit hinzufügen
issues.time_estimate_set=Geschätzte Zeit festlegen
issues.time_estimate_display=Schätzung: %s
+issues.change_time_estimate_at=Zeitschätzung geändert zu <b>%[1]s</b> %[2]s
issues.remove_time_estimate_at=Zeitschätzung %s entfernt
issues.time_estimate_invalid=Format der Zeitschätzung ist ungültig
issues.start_tracking_history=hat die Zeiterfassung %s gestartet
issues.tracker_auto_close=Der Timer wird automatisch gestoppt, wenn dieser Issue geschlossen wird
issues.tracking_already_started=`Du hast die Zeiterfassung bereits in <a href="%s">diesem Issue</a> gestartet!`
+issues.stop_tracking=Timer stoppen
+issues.stop_tracking_history=hat für <b>%[1]s</b> %[2]s gearbeitet
issues.cancel_tracking=Verwerfen
issues.cancel_tracking_history=`hat die Zeiterfassung %s abgebrochen`
issues.del_time=Diese Zeiterfassung löschen
+issues.add_time_history=hat <b>%[1]s</b> %[2]s gearbeitete Zeit hinzugefügt
issues.del_time_history=`hat %s gearbeitete Zeit gelöscht`
issues.add_time_manually=Zeit manuell hinzufügen
issues.add_time_hours=Stunden
@@ -1847,6 +1878,7 @@ pulls.add_prefix=<strong>%s</strong> Präfix hinzufügen
pulls.remove_prefix=<strong>%s</strong> Präfix entfernen
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 zum Ziel-Branch stehen.
+pulls.is_checking=Die Konfliktprüfung läuft noch. Bitte aktualisiere die Seite in wenigen Augenblicken.
pulls.is_ancestor=Dieser Branch ist bereits im Zielbranch enthalten. Es gibt nichts zu mergen.
pulls.is_empty=Die Änderungen an diesem Branch sind bereits auf dem Zielbranch. Dies wird ein leerer Commit sein.
pulls.required_status_check_failed=Einige erforderliche Prüfungen waren nicht erfolgreich.
@@ -1916,11 +1948,11 @@ pulls.outdated_with_base_branch=Dieser Branch enthält nicht die neusten Commits
pulls.close=Pull-Request schließen
pulls.closed_at=`hat diesen Pull-Request <a id="%[1]s" href="#%[1]s">%[2]s</a> geschlossen`
pulls.reopened_at=`hat diesen Pull-Request <a id="%[1]s" href="#%[1]s">%[2]s</a> wieder geöffnet`
+pulls.cmd_instruction_hint=Zeige Kommandozeilenanweisungen
pulls.cmd_instruction_checkout_title=Checkout
pulls.cmd_instruction_checkout_desc=Wechsle auf einen neuen Branch in deinem lokalen Repository und teste die Änderungen.
pulls.cmd_instruction_merge_title=Mergen
pulls.cmd_instruction_merge_desc=Die Änderungen mergen und auf Gitea aktualisieren.
-pulls.cmd_instruction_merge_warning=Warnung: Dieser Vorgang kann den Pull-Request nicht mergen, da "manueller Merge" nicht aktiviert wurde
pulls.clear_merge_message=Merge-Nachricht löschen
pulls.clear_merge_message_hint=Das Löschen der Merge-Nachricht wird nur den Inhalt der Commit-Nachricht entfernen und generierte Git-Trailer wie "Co-Authored-By …" erhalten.
@@ -1944,6 +1976,7 @@ pulls.upstream_diverging_prompt_behind_1=Dieser Branch ist %[1]d Commit hinter %
pulls.upstream_diverging_prompt_behind_n=Dieser Branch ist %[1]d Commits hinter %[2]s
pulls.upstream_diverging_prompt_base_newer=Der Basis-Branch %s hat neue Änderungen
pulls.upstream_diverging_merge=Fork synchronisieren
+pulls.upstream_diverging_merge_confirm=Möchtest du „%[1]s“ in „%[2]s“ mergen?
pull.deleted_branch=(gelöscht):%s
pull.agit_documentation=Dokumentation zu AGit durchschauen
@@ -2103,6 +2136,12 @@ contributors.contribution_type.deletions=Löschungen
settings=Einstellungen
settings.desc=In den Einstellungen kannst du die Einstellungen des Repositories anpassen
settings.options=Repository
+settings.public_access=Öffentlicher Zugriff
+settings.public_access_desc=Konfiguriere die Zugriffsrechte öffentlicher Besucher, um die Standardwerte dieses Repositorys zu überschreiben.
+settings.public_access.docs.not_set=Nicht gesetzt: keine zusätzliche öffentliche Zugriffsberechtigung. Die Berechtigung der Besucher folgt den Sichtbarkeits- und Mitgliedsberechtigungen des Repositorys.
+settings.public_access.docs.anonymous_read=Anonymes Lesen: Nicht angemeldete Benutzer können mit Leseberechtigung auf die Einheit zugreifen.
+settings.public_access.docs.everyone_read=Alle Lesen: Alle angemeldeten Benutzer können mit Leseberechtigung auf die Einheit zugreifen. Leseberechtigung für Issues/Pull-Request-Einheiten bedeutet auch, dass Benutzer neue Issues/Pull-Requests erstellen können.
+settings.public_access.docs.everyone_write=Alle Schreiben: Alle angemeldeten Benutzer haben Schreibrechte auf die Einheit. Nur die Wiki-Einheit unterstützt diese Berechtigung.
settings.collaboration=Mitarbeiter
settings.collaboration.admin=Administrator
settings.collaboration.write=Schreibrechte
@@ -2316,6 +2355,8 @@ settings.event_fork=Fork
settings.event_fork_desc=Repository geforkt.
settings.event_wiki=Wiki
settings.event_wiki_desc=Wiki-Seite erstellt, umbenannt, bearbeitet oder gelöscht.
+settings.event_statuses=Status
+settings.event_statuses_desc=Commit-Status von der API aktualisiert.
settings.event_release=Release
settings.event_release_desc=Release in einem Repository veröffentlicht, aktualisiert oder gelöscht.
settings.event_push=Push
@@ -2325,7 +2366,6 @@ settings.event_repository=Repository
settings.event_repository_desc=Repository erstellt oder gelöscht.
settings.event_header_issue=Issue Ereignisse
settings.event_issues=Issues
-settings.event_issues_desc=Issue geöffnet, geschlossen, wieder geöffnet oder bearbeitet.
settings.event_issue_assign=Issue zugewiesen
settings.event_issue_assign_desc=Issue zugewiesen oder Zuweisung entfernt.
settings.event_issue_label=Issue mit Label versehen
@@ -2336,7 +2376,6 @@ settings.event_issue_comment=Issue-Kommentar
settings.event_issue_comment_desc=Issue-Kommentar angelegt, geändert oder gelöscht.
settings.event_header_pull_request=Pull-Request-Ereignisse
settings.event_pull_request=Pull-Request
-settings.event_pull_request_desc=Pull-Request geöffnet, geschlossen, wieder geöffnet oder bearbeitet.
settings.event_pull_request_assign=Pull-Request zugewiesen
settings.event_pull_request_assign_desc=Pull-Request zugewiesen oder Zuweisung entfernt.
settings.event_pull_request_label=Pull-Request mit Label versehen
@@ -2353,6 +2392,9 @@ settings.event_pull_request_review_request=Überprüfung des Pull-Requests angef
settings.event_pull_request_review_request_desc=Überprüfung des Pull-Requests angefragt oder die Anfrage entfernt.
settings.event_pull_request_approvals=Zustimmungen zum Pull-Request
settings.event_pull_request_merge=Pull-Request-Merge
+settings.event_header_workflow=Workflow-Ereignisse
+settings.event_workflow_job=Workflow-Jobs
+settings.event_workflow_job_desc=Gitea Actions Workflow Job in Warteschlange, wartend, in Bearbeitung oder abgeschlossen.
settings.event_package=Paket
settings.event_package_desc=Paket wurde in einem Repository erstellt oder gelöscht.
settings.branch_filter=Branch-Filter
@@ -2615,6 +2657,9 @@ diff.image.overlay=Überlagert
diff.has_escaped=Diese Zeile enthält versteckte Unicode-Zeichen
diff.show_file_tree=Dateibaum anzeigen
diff.hide_file_tree=Dateibaum ausblenden
+diff.submodule_added=Submodul %[1]s hinzugefügt bei %[2]s
+diff.submodule_deleted=Submodul %[1]s gelöscht von %[2]s
+diff.submodule_updated=Submodul %[1]s aktualisiert: %[2]s
releases.desc=Behalte den Überblick über Versionen und Downloads.
release.releases=Releases
@@ -2685,6 +2730,7 @@ branch.restore_success=Branch "%s" wurde wiederhergestellt.
branch.restore_failed=Wiederherstellung vom Branch "%s" gescheitert.
branch.protected_deletion_failed=Branch "%s" ist geschützt und kann nicht gelöscht werden.
branch.default_deletion_failed=Branch "%s" kann nicht gelöscht werden, da dieser Branch der Standard-Branch ist.
+branch.default_branch_not_exist=Standardzweig „%s“ existiert nicht.
branch.restore=Branch "%s" wiederherstellen
branch.download=Branch "%s" herunterladen
branch.rename=Branch "%s" umbenennen
@@ -2699,6 +2745,8 @@ branch.create_branch_operation=Branch erstellen
branch.new_branch=Neue Branch erstellen
branch.new_branch_from=Neuen Branch von "%s" erstellen
branch.renamed=Branch %s wurde in %s umbenannt.
+branch.rename_default_or_protected_branch_error=Nur Administratoren können Standard- oder geschützte Branches umbenennen.
+branch.rename_protected_branch_failed=Dieser Branch ist durch globale Schutzregeln geschützt.
tag.create_tag=Tag %s erstellen
tag.create_tag_operation=Tag erstellen
@@ -2773,15 +2821,13 @@ settings.visibility.private_shortname=Privat
settings.update_settings=Einstellungen speichern
settings.update_setting_success=Organisationseinstellungen wurden aktualisiert.
-settings.change_orgname_prompt=Hinweis: Das Ändern des Organisationsnamens wird auch die URL deiner Organisation ändern und den alten Namen freigeben.
-settings.change_orgname_redirect_prompt=Der alte Name wird weiterleiten, bis er wieder beansprucht wird.
+
+
settings.update_avatar_success=Der Organisationsavatar wurde aktualisiert.
settings.delete=Organisation löschen
settings.delete_account=Diese Organisation löschen
settings.delete_prompt=Die Organisation wird dauerhaft gelöscht. Dies <strong>KANN NICHT</strong> rückgängig gemacht werden!
settings.confirm_delete_account=Löschen bestätigen
-settings.delete_org_title=Organisation löschen
-settings.delete_org_desc=Diese Organisation wird dauerhaft gelöscht. Fortfahren?
settings.hooks_desc=Webhooks hinzufügen, die für <strong>alle</strong> Repositories dieser Organisation ausgelöst werden.
settings.labels_desc=Labels hinzufügen, die für <strong>alle Repositories</strong> dieser Organisation genutzt werden können.
@@ -2853,7 +2899,11 @@ teams.invite.title=Du wurdest eingeladen, dem Team <strong>%s</strong> in der Or
teams.invite.by=Von %s eingeladen
teams.invite.description=Bitte klicke auf die folgende Schaltfläche, um dem Team beizutreten.
+view_as_role=Ansehen als: %s
+view_as_public_hint=Du siehst die README als ein öffentlicher Benutzer.
+view_as_member_hint=Du siehst die README als ein Mitglied dieser Organisation.
+worktime=Arbeitszeit
worktime.date_range_start=Startdatum
worktime.date_range_end=Enddatum
worktime.query=Abfrage
@@ -3354,6 +3404,8 @@ monitor.previous=Letzte Ausführung
monitor.execute_times=Ausführungen
monitor.process=Laufende Prozesse
monitor.stacktrace=Stacktraces
+monitor.trace=Ablaufverfolgung
+monitor.performance_logs=Leistungsprotokolle
monitor.processes_count=%d Prozesse
monitor.download_diagnosis_report=Diagnosebericht herunterladen
monitor.desc=Beschreibung
@@ -3528,6 +3580,7 @@ versions=Versionen
versions.view_all=Alle anzeigen
dependency.id=ID
dependency.version=Version
+search_in_external_registry=In %s suchen
alpine.registry=Richte diese Registry ein, indem Du die URL in die <code>/etc/apk/repositories</code>-Datei hinzufügst:
alpine.registry.key=Lade den öffentlichen RSA-Key der Registry in den <code>/etc/apk/keys/</code>-Ordner, um die Signatur zu überprüfen:
alpine.registry.info=Wähle $branch und $repository aus der Liste unten.
@@ -3557,6 +3610,8 @@ conda.install=Um das Paket mit Conda zu installieren, führe den folgenden Befeh
container.details.type=Container-Image Typ
container.details.platform=Plattform
container.pull=Downloade das Container-Image aus der Kommandozeile:
+container.images=Images
+container.digest=Digest
container.multi_arch=Betriebsystem / Architektur
container.layers=Container-Image Ebenen
container.labels=Labels
@@ -3659,12 +3714,18 @@ owner.settings.chef.keypair.description=Ein Schlüsselpaar ist notwendig, um mit
secrets=Secrets
description=Secrets werden an bestimmte Aktionen weitergegeben und können nicht anderweitig ausgelesen werden.
none=Noch keine Secrets vorhanden.
-creation=Secret hinzufügen
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Beschreibung
creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen
creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen.
-creation.success=Das Secret "%s" wurde hinzugefügt.
-creation.failed=Secret konnte nicht hinzugefügt werden.
+creation.description_placeholder=Gib eine Kurzbeschreibung ein (optional).
+
+save_success=Das Secret "%s" wurde gespeichert.
+save_failed=Secret konnte nicht gespeichert werden.
+
+add_secret=Secret hinzufügen
+edit_secret=Secret bearbeiten
deletion=Secret entfernen
deletion.description=Das Entfernen eines Secrets kann nicht rückgängig gemacht werden. Fortfahren?
deletion.success=Das Secret wurde entfernt.
@@ -3742,6 +3803,10 @@ runs.no_workflows.documentation=Weitere Informationen zu Gitea Actions findest d
runs.no_runs=Der Workflow hat noch keine Ausführungen.
runs.empty_commit_message=(leere Commit-Nachricht)
runs.expire_log_message=Protokolle wurden geleert, weil sie zu alt waren.
+runs.delete=Workflow-Ausführung löschen
+runs.delete.description=Bist du sicher, dass du diese Workflow-Ausführung dauerhaft löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.
+runs.not_done=Diese Workflow-Ausführung ist noch nicht abgeschlossen.
+runs.view_workflow_file=Workflow-Datei anzeigen
workflow.disable=Workflow deaktivieren
workflow.disable_success=Workflow '%s' erfolgreich deaktiviert.
@@ -3753,6 +3818,7 @@ workflow.not_found=Workflow '%s' wurde nicht gefunden.
workflow.run_success=Workflow '%s' erfolgreich ausgeführt.
workflow.from_ref=Nutze Workflow von
workflow.has_workflow_dispatch=Dieser Workflow hat einen workflow_dispatch Event-Trigger.
+workflow.has_no_workflow_dispatch=Der Workflow '%s' hat keinen workflow_dispatch Event-Trigger.
need_approval_desc=Um Workflows für den Pull-Request eines Forks auszuführen, ist eine Genehmigung erforderlich.
@@ -3780,6 +3846,8 @@ deleted.display_name=Gelöschtes Projekt
type-1.display_name=Individuelles Projekt
type-2.display_name=Repository-Projekt
type-3.display_name=Organisationsprojekt
+enter_fullscreen=Vollbild
+exit_fullscreen=Vollbild verlassen
[git.filemode]
changed_filemode=%[1]s → %[2]s
diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini
index c2479bf342..4a7fe0fab7 100644
--- a/options/locale/locale_el-GR.ini
+++ b/options/locale/locale_el-GR.ini
@@ -1190,7 +1190,6 @@ editor.update=ΕνημέÏωση %s
editor.delete=ΔιαγÏαφή %s
editor.patch=ΕφαÏμογή ΔιόÏθωσης
editor.patching=ΕπιδιόÏθωση:
-editor.fail_to_apply_patch=`Αδυναμία εφαÏμογής της επιδιόÏθωσης "%s"`
editor.new_patch=Îέα ΔιόÏθωση
editor.commit_message_desc=ΠÏοσθήκη Ï€ÏοαιÏετικής εκτενοÏÏ‚ πεÏιγÏαφής…
editor.signoff_desc=ΠÏοσθέστε ένα Ï€Ïόσθετο Signed-off-by στο τέλος του μηνÏματος καταγÏαφής της υποβολής.
@@ -1208,15 +1207,11 @@ editor.branch_already_exists=Ο κλάδος "%s" υπάÏχει ήδη σε αÏ
editor.directory_is_a_file=Το όνομα φακέλου "%s" χÏησιμοποιείται ήδη ως όνομα αÏχείου σε αυτό το αποθετήÏιο.
editor.file_is_a_symlink=`Το "%s" είναι συμβολικός σÏνδεσμος. Οι συμβολικοί σÏνδεσμοι δεν μποÏοÏν να επεξεÏγαστοÏν στην ενσωματωμένη εφαÏμογή`
editor.filename_is_a_directory=Το όνομα αÏχείου "%s" χÏησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το αποθετήÏιο.
-editor.file_editing_no_longer_exists=Το αÏχείο "%s" που επεξεÏγάζεται, δεν υπάÏχει πλέον σε αυτό το αποθετήÏιο.
-editor.file_deleting_no_longer_exists=Το αÏχείο "%s" που διαγÏάφεται, δεν υπάÏχει πλέον σε αυτό το αποθετήÏιο.
editor.file_changed_while_editing=Τα πεÏιεχόμενα του αÏχείου άλλαξαν από τότε που ξεκίνησε η επεξεÏγασία. <a target="_blank" rel="noopener noreferrer" href="%s">Κάντε κλικ εδώ</a> για να τα δείτε ή <strong>Υποβολή Αλλαγών ξανά</strong> για να τα αντικαταστήσετε.
editor.file_already_exists=Ένα αÏχείο με το όνομα "%s" υπάÏχει ήδη σε αυτό το αποθετήÏιο.
editor.commit_empty_file_header=Υποβολή ενός ÎºÎµÎ½Î¿Ï Î±Ïχείου
editor.commit_empty_file_text=Το αÏχείο που Ï€Ïόκειται να υποβληθεί είναι κενό. Συνέχεια;
editor.no_changes_to_show=Δεν υπάÏχουν αλλαγές για εμφάνιση.
-editor.fail_to_update_file=Αποτυχία ενημέÏωσης/δημιουÏγίας του αÏχείου "%s".
-editor.fail_to_update_file_summary=Μήνυμα Σφάλματος:
editor.push_rejected_no_message=Η αλλαγή αποÏÏίφθηκε από το διακομιστή χωÏίς κάποιο μήνυμα. ΠαÏακαλώ ελέγξτε τα ΆγκιστÏα Git.
editor.push_rejected=Η αλλαγή αποÏÏίφθηκε από τον διακομιστή. ΠαÏακαλώ ελέγξτε τα ΆγκιστÏα Git.
editor.push_rejected_summary=Μήνυμα ΠλήÏους ΑπόÏÏιψης:
@@ -1231,6 +1226,7 @@ editor.require_signed_commit=Ο κλάδος απαιτεί υπογεγÏαμμ
editor.cherry_pick=Ανθολόγηση (cherry-pic) του %s στο:
editor.revert=ΑπόσυÏση του %s στο:
+
commits.desc=Δείτε το ιστοÏικό αλλαγών του πηγαίου κώδικα.
commits.commits=Υποβολές
commits.no_commits=Δεν υπάÏχουν κοινές υποβολές. Τα "%s" και "%s" έχουν εντελώς διαφοÏετικές ιστοÏίες.
@@ -2117,7 +2113,6 @@ settings.event_repository=ΑποθετήÏιο
settings.event_repository_desc=Το αποθετήÏιο δημιουÏγήθηκε ή διαγÏάφηκε.
settings.event_header_issue=Γεγονότα Ζητήματος
settings.event_issues=Ζητήματα
-settings.event_issues_desc=Το ζήτημα άνοιξε, έκλεισε, ανοίχθηκε εκ νέου ή επεξεÏγάστηκε.
settings.event_issue_assign=Ζήτημα Ανατέθηκε
settings.event_issue_assign_desc=Ζήτημα εκχωÏημένο ή μη εκχωÏημένο.
settings.event_issue_label=Σήμανση Ζητήματος
@@ -2128,7 +2123,6 @@ settings.event_issue_comment=Σχόλιο Ζητήματος
settings.event_issue_comment_desc=Το σχόλιο στο ζήτημα δημιουÏγήθηκε, επεξεÏγάστηκε ή διαγÏάφηκε.
settings.event_header_pull_request=Γεγονότα Pull Requests
settings.event_pull_request=Pull Request
-settings.event_pull_request_desc=Το pull request άνοιξε, έκλεισε, άνοιξε εκ νέου ή επεξεÏγάστηκε.
settings.event_pull_request_assign=Το Pull Request Ανατέθηκε
settings.event_pull_request_assign_desc=Το pull request ανατέθηκε ή έγινε αδιάθετο.
settings.event_pull_request_label=Σήμανση Pull Request
@@ -2505,15 +2499,13 @@ settings.visibility.private_shortname=Ιδιωτικός
settings.update_settings=ΕνημέÏωση Ρυθμίσεων
settings.update_setting_success=Οι Ïυθμίσεις του οÏÎ³Î±Î½Î¹ÏƒÎ¼Î¿Ï Î­Ï‡Î¿Ï…Î½ ενημεÏωθεί.
-settings.change_orgname_prompt=Σημείωση: Η αλλαγή του ονόματος του οÏÎ³Î±Î½Î¹ÏƒÎ¼Î¿Ï Î¸Î± αλλάξει επίσης τη διεÏθυνση URL του οÏÎ³Î±Î½Î¹ÏƒÎ¼Î¿Ï ÏƒÎ±Ï‚ και θα απελευθεÏώσει το παλιό όνομα.
-settings.change_orgname_redirect_prompt=Το παλιό όνομα θα ανακατευθÏνει μέχÏι να διεκδικηθεί.
+
+
settings.update_avatar_success=Η εικόνα του οÏÎ³Î±Î½Î¹ÏƒÎ¼Î¿Ï Î­Ï‡ÎµÎ¹ ενημεÏωθεί.
settings.delete=ΔιαγÏαφή ΟÏγανισμοÏ
settings.delete_account=ΔιαγÏαφή Î‘Ï…Ï„Î¿Ï Î¤Î¿Ï… ΟÏγανισμοÏ
settings.delete_prompt=Ο οÏγανισμός θα αφαιÏεθεί οÏιστικά. Αυτό το <strong>ΔΕΠΜΠΟΡΕΙ</strong> να αναιÏεθεί!
settings.confirm_delete_account=Επιβεβαίωση ΔιαγÏαφής
-settings.delete_org_title=ΔιαγÏαφή ΟÏγανισμοÏ
-settings.delete_org_desc=Αυτός ο οÏγανισμός θα διαγÏαφεί οÏιστικά. Συνέχεια;
settings.hooks_desc=ΠÏοσθήκη webhooks που θα ενεÏγοποιοÏνται για <strong>όλα τα αποθετήÏια</strong> κάτω από αυτό τον οÏγανισμό.
settings.labels_desc=ΠÏοσθήκη σημάτων που μποÏοÏν να χÏησιμοποιηθοÏν σε ζητήματα για <strong>όλα τα αποθετήÏια</strong> κάτω από αυτό τον οÏγανισμό.
@@ -3329,12 +3321,13 @@ owner.settings.chef.keypair.description=Ένα ζεÏγος κλειδιών εÎ
secrets=Μυστικά
description=Τα μυστικά θα πεÏάσουν σε οÏισμένες δÏάσεις και δεν μποÏοÏν να αναγνωστοÏν αλλοÏ.
none=Δεν υπάÏχουν ακόμα μυστικά.
-creation=ΠÏοσθήκη ΜυστικοÏ
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=ΠεÏιγÏαφή
creation.name_placeholder=αλφαÏιθμητικοί χαÏακτήÏες ή κάτω παÏλες μόνο, δεν μποÏοÏν να ξεκινοÏν με GITEA_ ή GITHUB_
creation.value_placeholder=Εισάγετε οποιοδήποτε πεÏιεχόμενο. Τα κενά στην αÏχή παÏαλείπονται.
-creation.success=Το μυστικό "%s" Ï€Ïοστέθηκε.
-creation.failed=Αποτυχία δημιουÏγίας μυστικοÏ.
+
+
deletion=ΑφαίÏεση μυστικοÏ
deletion.description=Η αφαίÏεση ενός Î¼Ï…ÏƒÏ„Î¹ÎºÎ¿Ï ÎµÎ¯Î½Î±Î¹ μόνιμη και δεν μποÏεί να αναιÏεθεί. Συνέχεια;
deletion.success=Το μυστικό έχει αφαιÏεθεί.
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9091b6bc4b..f13f20bfa0 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -421,6 +421,7 @@ remember_me.compromised = The login token is not valid anymore which may indicat
forgot_password_title= Forgot Password
forgot_password = Forgot password?
need_account = Need an account?
+sign_up_tip = You are registering the first account in the system, which has administrator privileges. Please carefully remember your username and password. If you forget the username or password, please refer to the Gitea documentation to recover the account.
sign_up_now = Register now.
sign_up_successful = Account was successfully created. Welcome!
confirmation_mail_sent_prompt_ex = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.
@@ -1228,6 +1229,7 @@ migrate.migrating_issues = Migrating Issues
migrate.migrating_pulls = Migrating Pull Requests
migrate.cancel_migrating_title = Cancel Migration
migrate.cancel_migrating_confirm = Do you want to cancel this migration?
+migration_status = Migration status
mirror_from = mirror of
forked_from = forked from
@@ -1331,7 +1333,9 @@ editor.upload_file = Upload File
editor.edit_file = Edit File
editor.preview_changes = Preview Changes
editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface.
+editor.cannot_edit_too_large_file = The file is too large to be edited.
editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface.
+editor.file_not_editable_hint = But you can still rename or move it.
editor.edit_this_file = Edit File
editor.this_file_locked = File is locked
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.
@@ -1351,7 +1355,7 @@ editor.update = Update %s
editor.delete = Delete %s
editor.patch = Apply Patch
editor.patching = Patching:
-editor.fail_to_apply_patch = Unable to apply patch "%s"
+editor.fail_to_apply_patch = Unable to apply patch
editor.new_patch = New Patch
editor.commit_message_desc = Add an optional extended description…
editor.signoff_desc = Add a Signed-off-by trailer by the committer at the end of the commit log message.
@@ -1371,8 +1375,7 @@ editor.branch_already_exists = Branch "%s" already exists in this repository.
editor.directory_is_a_file = Directory name "%s" is already used as a filename in this repository.
editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be edited in the web editor`
editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository.
-editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository.
-editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
+editor.file_modifying_no_longer_exists = The file being modified, "%s", no longer exists in this repository.
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
editor.file_already_exists = A file named "%s" already exists in this repository.
editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge.
@@ -1380,8 +1383,6 @@ editor.push_out_of_date = The push appears to be out of date.
editor.commit_empty_file_header = Commit an empty file
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed?
editor.no_changes_to_show = There are no changes to show.
-editor.fail_to_update_file = Failed to update/create file "%s".
-editor.fail_to_update_file_summary = Error Message:
editor.push_rejected_no_message = The change was rejected by the server without a message. Please check Git Hooks.
editor.push_rejected = The change was rejected by the server. Please check Git Hooks.
editor.push_rejected_summary = Full Rejection Message:
@@ -1395,6 +1396,15 @@ editor.user_no_push_to_branch = User cannot push to branch
editor.require_signed_commit = Branch requires a signed commit
editor.cherry_pick = Cherry-pick %s onto:
editor.revert = Revert %s onto:
+editor.failed_to_commit = Failed to commit changes.
+editor.failed_to_commit_summary = Error Message:
+
+editor.fork_create = Fork Repository to Propose Changes
+editor.fork_create_description = You can not edit this repository directly. Instead you can create a fork, make edits and create a pull request.
+editor.fork_edit_description = You can not edit this repository directly. The changes will be written to your fork <b>%s</b>, so you can create a pull request.
+editor.fork_not_editable = You have forked this repository but your fork is not editable.
+editor.fork_failed_to_push_branch = Failed to push branch %s to your repository.
+editor.fork_branch_exists = Branch "%s" already exists in your fork, please choose a new branch name.
commits.desc = Browse source code change history.
commits.commits = Commits
@@ -1432,7 +1442,6 @@ commitstatus.success = Success
ext_issues = Access to External Issues
ext_issues.desc = Link to an external issue tracker.
-projects = Projects
projects.desc = Manage issues and pulls in projects.
projects.description = Description (optional)
projects.description_placeholder = Description
@@ -1560,6 +1569,7 @@ issues.filter_user_placeholder = Search users
issues.filter_user_no_select = All users
issues.filter_type = Type
issues.filter_type.all_issues = All issues
+issues.filter_type.all_pull_requests = All pull requests
issues.filter_type.assigned_to_you = Assigned to you
issues.filter_type.created_by_you = Created by you
issues.filter_type.mentioning_you = Mentioning you
@@ -1651,6 +1661,7 @@ issues.save = Save
issues.label_title = Name
issues.label_description = Description
issues.label_color = Color
+issues.label_color_invalid = Invalid color
issues.label_exclusive = Exclusive
issues.label_archive = Archive Label
issues.label_archived_filter = Show archived labels
@@ -1715,6 +1726,8 @@ issues.remove_time_estimate_at = removed time estimate %s
issues.time_estimate_invalid = Time estimate format is invalid
issues.start_tracking_history = started working %s
issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed
+issues.stopwatch_already_stopped = The timer for this issue is already stopped
+issues.stopwatch_already_created = The timer for this issue already exists
issues.tracking_already_started = `You have already started time tracking on <a href="%s">another issue</a>!`
issues.stop_tracking = Stop Timer
issues.stop_tracking_history = worked for <b>%[1]s</b> %[2]s
@@ -1956,7 +1969,7 @@ pulls.cmd_instruction_checkout_title = Checkout
pulls.cmd_instruction_checkout_desc = From your project repository, check out a new branch and test the changes.
pulls.cmd_instruction_merge_title = Merge
pulls.cmd_instruction_merge_desc = Merge the changes and update on Gitea.
-pulls.cmd_instruction_merge_warning = Warning: This operation can not merge pull request because "autodetect manual merge" was not enable
+pulls.cmd_instruction_merge_warning = Warning: This operation cannot merge pull request because "autodetect manual merge" is not enabled.
pulls.clear_merge_message = Clear merge message
pulls.clear_merge_message_hint = Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By …".
@@ -2152,6 +2165,7 @@ settings.collaboration.write = Write
settings.collaboration.read = Read
settings.collaboration.owner = Owner
settings.collaboration.undefined = Undefined
+settings.collaboration.per_unit = Unit Permissions
settings.hooks = Webhooks
settings.githooks = Git Hooks
settings.basic_settings = Basic Settings
@@ -2320,8 +2334,8 @@ settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server
settings.webhook_deletion = Remove Webhook
settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue?
settings.webhook_deletion_success = The webhook has been removed.
-settings.webhook.test_delivery = Test Delivery
-settings.webhook.test_delivery_desc = Test this webhook with a fake event.
+settings.webhook.test_delivery = Test Push Event
+settings.webhook.test_delivery_desc = Test this webhook with a fake push event.
settings.webhook.test_delivery_desc_disabled = To test this webhook with a fake event, activate it.
settings.webhook.request = Request
settings.webhook.response = Response
@@ -2370,7 +2384,7 @@ settings.event_repository = Repository
settings.event_repository_desc = Repository created or deleted.
settings.event_header_issue = Issue Events
settings.event_issues = Issues
-settings.event_issues_desc = Issue opened, closed, reopened, or edited.
+settings.event_issues_desc = Issue opened, closed, reopened, edited or deleted.
settings.event_issue_assign = Issue Assigned
settings.event_issue_assign_desc = Issue assigned or unassigned.
settings.event_issue_label = Issue Labeled
@@ -2381,7 +2395,7 @@ settings.event_issue_comment = Issue Comment
settings.event_issue_comment_desc = Issue comment created, edited, or deleted.
settings.event_header_pull_request = Pull Request Events
settings.event_pull_request = Pull Request
-settings.event_pull_request_desc = Pull request opened, closed, reopened, or edited.
+settings.event_pull_request_desc = Pull request opened, closed, reopened, edited or deleted.
settings.event_pull_request_assign = Pull Request Assigned
settings.event_pull_request_assign_desc = Pull request assigned or unassigned.
settings.event_pull_request_label = Pull Request Labeled
@@ -2399,6 +2413,8 @@ settings.event_pull_request_review_request_desc = Pull request review requested
settings.event_pull_request_approvals = Pull Request Approvals
settings.event_pull_request_merge = Pull Request Merge
settings.event_header_workflow = Workflow Events
+settings.event_workflow_run = Workflow Run
+settings.event_workflow_run_desc = Gitea Actions Workflow run queued, waiting, in progress, or completed.
settings.event_workflow_job = Workflow Jobs
settings.event_workflow_job_desc = Gitea Actions Workflow job queued, waiting, in progress, or completed.
settings.event_package = Package
@@ -2753,6 +2769,8 @@ branch.new_branch_from = Create new branch from "%s"
branch.renamed = Branch %s was renamed to %s.
branch.rename_default_or_protected_branch_error = Only admins can rename default or protected branches.
branch.rename_protected_branch_failed = This branch is protected by glob-based protection rules.
+branch.commits_divergence_from = Commits divergence: %[1]d behind and %[2]d ahead of %[3]s
+branch.commits_no_divergence = The same as branch %[1]s
tag.create_tag = Create tag %s
tag.create_tag_operation = Create tag
@@ -2766,6 +2784,7 @@ topic.done = Done
topic.count_prompt = You cannot select more than 25 topics
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
+find_file.follow_symlink= Follow this symlink to where it is pointing at
find_file.go_to_file = Go to file
find_file.no_matching = No matching file found
@@ -2806,6 +2825,7 @@ team_permission_desc = Permission
team_unit_desc = Allow Access to Repository Sections
team_unit_disabled = (Disabled)
+form.name_been_taken = The organisation name "%s" has already been taken.
form.name_reserved = The organization name "%s" is reserved.
form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name.
form.create_org_not_allowed = You are not allowed to create an organization.
@@ -2827,15 +2847,28 @@ settings.visibility.private_shortname = Private
settings.update_settings = Update Settings
settings.update_setting_success = Organization settings have been updated.
-settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name.
-settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed.
+
+settings.rename = Rename Organization
+settings.rename_desc = Changing the organization name will also change your organization's URL and free the old name.
+settings.rename_success = Organization %[1]s have been renamed to %[2]s successfully.
+settings.rename_no_change = Organization name is no change.
+settings.rename_new_org_name = New Organization Name
+settings.rename_failed = Rename Organization failed because of internal error
+settings.rename_notices_1 = This operation <strong>CANNOT</strong> be undone.
+settings.rename_notices_2 = The old name will redirect until it is claimed.
+
settings.update_avatar_success = The organization's avatar has been updated.
settings.delete = Delete Organization
settings.delete_account = Delete This Organization
settings.delete_prompt = The organization will be permanently removed. This <strong>CANNOT</strong> be undone!
+settings.name_confirm = Enter the organization name as confirmation:
+settings.delete_notices_1 = This operation <strong>CANNOT</strong> be undone.
+settings.delete_notices_2 = This operation will permanently delete all the <strong>repositories</strong> of <strong>%s</strong> including code, issues, comments, wiki data and collaborator settings.
+settings.delete_notices_3 = This operation will permanently delete all the <strong>packages</strong> of <strong>%s</strong>.
+settings.delete_notices_4 = This operation will permanently delete all the <strong>projects</strong> of <strong>%s</strong>.
settings.confirm_delete_account = Confirm Deletion
-settings.delete_org_title = Delete Organization
-settings.delete_org_desc = This organization will be deleted permanently. Continue?
+settings.delete_failed = Delete Organization failed because of internal error
+settings.delete_successful = Organization <b>%s</b> has been deleted successfully.
settings.hooks_desc = Add webhooks which will be triggered for <strong>all repositories</strong> under this organization.
settings.labels_desc = Add labels which can be used on issues for <strong>all repositories</strong> under this organization.
@@ -3722,13 +3755,18 @@ owner.settings.chef.keypair.description = A key pair is necessary to authenticat
secrets = Secrets
description = Secrets will be passed to certain actions and cannot be read otherwise.
none = There are no secrets yet.
-creation = Add Secret
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description = Description
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
creation.description_placeholder = Enter short description (optional).
-creation.success = The secret "%s" has been added.
-creation.failed = Failed to add secret.
+
+save_success = The secret "%s" has been saved.
+save_failed = Failed to save secret.
+
+add_secret = Add secret
+edit_secret = Edit secret
deletion = Remove secret
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
deletion.success = The secret has been removed.
@@ -3806,6 +3844,11 @@ runs.no_workflows.documentation = For more information on Gitea Actions, see <a
runs.no_runs = The workflow has no runs yet.
runs.empty_commit_message = (empty commit message)
runs.expire_log_message = Logs have been purged because they were too old.
+runs.delete = Delete workflow run
+runs.cancel = Cancel workflow run
+runs.delete.description = Are you sure you want to permanently delete this workflow run? This action cannot be undone.
+runs.not_done = This workflow run is not done.
+runs.view_workflow_file = View workflow file
workflow.disable = Disable Workflow
workflow.disable_success = Workflow '%s' disabled successfully.
diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini
index 5f989f6acf..cd4f0fd9eb 100644
--- a/options/locale/locale_es-ES.ini
+++ b/options/locale/locale_es-ES.ini
@@ -1180,7 +1180,6 @@ editor.update=Actualizar %s
editor.delete=Eliminar %s
editor.patch=Aplicar parche
editor.patching=Parcheando:
-editor.fail_to_apply_patch=`No se puede aplicar el parche "%s"`
editor.new_patch=Nuevo parche
editor.commit_message_desc=Añadir una descripción extendida opcional…
editor.signoff_desc=Añadir un trailer firmado por el committer al final del mensaje de registro de confirmación.
@@ -1198,15 +1197,11 @@ editor.branch_already_exists=La rama "%s" ya existe en este repositorio.
editor.directory_is_a_file=Nombre del directorio "%s" ya se utiliza como nombre de archivo en este repositorio.
editor.file_is_a_symlink=`"%s" es un enlace simbólico. Los enlaces simbólicos no se pueden editar en el editor web`
editor.filename_is_a_directory=Nombre de archivo "%s" ya se utiliza como nombre de directorio en este repositorio.
-editor.file_editing_no_longer_exists=El archivo que se está editando, "%s", ya no existe en este repositorio.
-editor.file_deleting_no_longer_exists=El archivo que se está eliminando, "%s", ya no existe en este repositorio.
editor.file_changed_while_editing=Desde que comenzó a editar, el contenido del archivo ha sido cambiado. <a target="_blank" rel="noopener noreferrer" href="%s">Haga clic aquí</a> para ver qué ha cambiado o <strong>presione confirmar de nuevo</strong> para sobrescribir los cambios.
editor.file_already_exists=Ya existe un archivo llamado "%s" en este repositorio.
editor.commit_empty_file_header=Commit un archivo vacío
editor.commit_empty_file_text=El archivo que estás tratando de commit está vacío. ¿Proceder?
editor.no_changes_to_show=No existen cambios para mostrar.
-editor.fail_to_update_file=Error al actualizar/crear el archivo "%s".
-editor.fail_to_update_file_summary=Mensaje de error
editor.push_rejected_no_message=El cambio fue rechazado por el servidor sin un mensaje. Por favor, compruebe Git Hooks.
editor.push_rejected=El cambio fue rechazado por el servidor. Por favor, comprueba los Git Hooks.
editor.push_rejected_summary=Mensaje completo de rechazo
@@ -1221,6 +1216,7 @@ editor.require_signed_commit=Esta rama requiere un commit firmado
editor.cherry_pick=Hacer Cherry-pick %s en:
editor.revert=Revertir %s en:
+
commits.desc=Ver el historial de cambios de código fuente.
commits.commits=Commits
commits.no_commits=No hay commits en común. "%s" y "%s" tienen historias totalmente diferentes.
@@ -2101,7 +2097,6 @@ settings.event_repository=Repositorio
settings.event_repository_desc=Repositorio creado o eliminado.
settings.event_header_issue=Eventos de incidencias
settings.event_issues=Incidencias
-settings.event_issues_desc=Incidencia abierta, cerrada, reabierta o editada.
settings.event_issue_assign=Incidencia asignada
settings.event_issue_assign_desc=Incidencia asignada o no asignada.
settings.event_issue_label=Incidencia etiquetada
@@ -2112,7 +2107,6 @@ settings.event_issue_comment=Comentario de incidencia
settings.event_issue_comment_desc=Comentario de incidencias creado, editado o borrado.
settings.event_header_pull_request=Eventos de Pull Requests
settings.event_pull_request=Pull Request
-settings.event_pull_request_desc=Pull request abierto, cerrado, reabierto o editado.
settings.event_pull_request_assign=Pull Request asignado
settings.event_pull_request_assign_desc=Pull Request asignado o no asignado.
settings.event_pull_request_label=Pull Request Etiquetado
@@ -2487,15 +2481,13 @@ settings.visibility.private_shortname=Privado
settings.update_settings=Actualizar configuración
settings.update_setting_success=Configuración de la organización se han actualizado.
-settings.change_orgname_prompt=Nota: Cambiar el nombre de la organización también cambiará la URL de su organización y liberará el nombre antiguo.
-settings.change_orgname_redirect_prompt=El nombre antiguo se redirigirá hasta que se reclame.
+
+
settings.update_avatar_success=Se ha actualizado el avatar de la organización.
settings.delete=Eliminar organización
settings.delete_account=Eliminar esta organización
settings.delete_prompt=La organización será eliminada permanentemente. ¡Esta acción <strong>NO PUEDE</strong> deshacerse!
settings.confirm_delete_account=Confirmar eliminación
-settings.delete_org_title=Eliminar organización
-settings.delete_org_desc=Esta organización se eliminará permanentemente. ¿Continuar?
settings.hooks_desc=Añadir webhooks que serán ejecutados para <strong>todos los repositorios</strong> de esta organización.
settings.labels_desc=Añadir etiquetas que pueden ser utilizadas en problemas para <strong>todos los repositorios</strong> bajo esta organización.
@@ -3309,12 +3301,13 @@ owner.settings.chef.keypair.description=Un par de claves es necesario para auten
secrets=Secretos
description=Los secretos pasarán a ciertas acciones y no se podrán leer de otro modo.
none=Todavía no hay secretos.
-creation=Añadir secreto
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Descripción
creation.name_placeholder=sin distinción de mayúsculas, solo carácteres alfanuméricos o guiones bajos, no puede empezar por GITEA_ o GITHUB_
creation.value_placeholder=Introduce cualquier contenido. Se omitirá el espacio en blanco en el inicio y el final.
-creation.success=El secreto "%s" ha sido añadido.
-creation.failed=Error al añadir secreto.
+
+
deletion=Eliminar secreto
deletion.description=Eliminar un secreto es permanente y no se puede deshacer. ¿Continuar?
deletion.success=El secreto ha sido eliminado.
diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini
index 5d67f03bac..aee09ac278 100644
--- a/options/locale/locale_fa-IR.ini
+++ b/options/locale/locale_fa-IR.ini
@@ -943,13 +943,13 @@ editor.file_changed_while_editing=محتوای پرونده تغییر میکنØ
editor.commit_empty_file_header=کامیت کردن یک پرونده خالی
editor.commit_empty_file_text=ÙØ§ÛŒÙ„ÛŒ Ú©Ù‡ درخواست ارسال دارید خالی است. ادامه بدم?
editor.no_changes_to_show=تغییری برای نمایش وجود ندارد.
-editor.fail_to_update_file_summary=متن خطا:
editor.push_rejected_summary=متن کامل پیام دلیل رد شدن:
editor.add_subdir=Ø§ÙØ²ÙˆØ¯Ù† پوشه…
editor.no_commit_to_branch=نمی‌توان به طور مستقیم درمورد شاخه نطر داد زیرا:
editor.user_no_push_to_branch=کاربر نمیتواند به شاخه ارسال کند
editor.require_signed_commit=شاخه یک کامیت امضا شده لازم دارد
+
commits.desc=تاریخچه تغییرات کد منبع را مرور کنید.
commits.commits=کامیت‌ها
commits.nothing_to_compare=این شاخه ها برابرند.
@@ -1632,7 +1632,6 @@ settings.event_repository=مخزن
settings.event_repository_desc=مخزن ساخته یا حذ٠شد.
settings.event_header_issue=رویدادهای مساله
settings.event_issues=مسائل
-settings.event_issues_desc=مساله باز شد، بسته شد، دوباره باز شد، یا ویرایش شد.
settings.event_issue_assign=مساله تعیین شد
settings.event_issue_assign_desc=مساله واگذار شده یا واگذار نشده است.
settings.event_issue_label=مساله برجسب خورد
@@ -1643,7 +1642,6 @@ settings.event_issue_comment=دیدگاه های مسئله
settings.event_issue_comment_desc=نظر در مسئله ایجاد شد، ویرایش شد یا حذ٠شد.
settings.event_header_pull_request=رویداد های درخواست pull
settings.event_pull_request=تقاضای واکشی
-settings.event_pull_request_desc=درخواست pull باز شد، بسته شد، دوباره باز شد، یا ویرایش شد.
settings.event_pull_request_assign=درخواست pull واگذار شد
settings.event_pull_request_assign_desc=درخواست pull واگذاری شده یا واگذاری نشده.
settings.event_pull_request_label=درخواست pull برچسب دار شد
@@ -1920,14 +1918,13 @@ settings.visibility.private_shortname=پوشیده
settings.update_settings=به‌ روزرسانی تنظیمات
settings.update_setting_success=تنظیمات این سازمان به‌روز شد.
-settings.change_orgname_redirect_prompt=نام قدیمی تا زمانی که ادعا شود تغییر مسیر می دهد.
+
+
settings.update_avatar_success=آواتار این سازمان به‌روز شد.
settings.delete=حذ٠سازمان
settings.delete_account=حذ٠این سازمان
settings.delete_prompt=سازمان برای همیشه حذ٠خواهد شد. این قابل برگشت <strong>نخواهد بود</strong>!
settings.confirm_delete_account=تاییدیه حذÙ
-settings.delete_org_title=حذ٠سازمان
-settings.delete_org_desc=سازمان برای همیشه حذ٠خواهد شد. آیا همچنان ادامه می‌دهید؟
settings.hooks_desc=Ø§ÙØ²ÙˆØ¯Ù† webhook های Ú©Ù‡ برای<strong> تمام مخازن</strong> این سازمان اجرا میشود.
settings.labels_desc=تگ هایی را اضاÙÙ‡ کنید Ú©Ù‡ می‌توانند برای مشکلات <strong>همه مخازن</strong> تحت این سازمان Ø§Ø³ØªÙØ§Ø¯Ù‡ شوند.
@@ -2506,8 +2503,12 @@ conan.details.repository=مخزن
owner.settings.cleanuprules.enabled=ÙØ¹Ø§Ù„ شده
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=شرح
+
+
[actions]
diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini
index 69cee090fe..3b4033b8b3 100644
--- a/options/locale/locale_fi-FI.ini
+++ b/options/locale/locale_fi-FI.ini
@@ -764,6 +764,7 @@ editor.no_changes_to_show=Ei muutoksia näytettäväksi.
editor.add_subdir=Lisää hakemisto…
editor.require_signed_commit=Haara vaatii vahvistetun commitin
+
commits.commits=Commitit
commits.nothing_to_compare=Nämä haarat vastaavat toisiaan.
commits.search_all=Kaikki haarat
@@ -1128,7 +1129,6 @@ settings.event_repository=Repo
settings.event_repository_desc=Repo luotu tai poistettu.
settings.event_header_issue=Ongelmien tapahtumat
settings.event_issues=Ongelmat
-settings.event_issues_desc=Ongelma avattu, suljettu, avattu uudelleen tai muokattu.
settings.event_issue_assign=Ongelma määritetty
settings.event_issue_assign_desc=Ongelma osoitettu tai osoitus poistettu.
settings.event_issue_label_desc=Ongelman tunnisteet päivitetty tai tyhjennetty.
@@ -1318,11 +1318,12 @@ settings.visibility.private=Yksityinen (näkyvä vain organisaation jäsenille)
settings.visibility.private_shortname=Yksityinen
settings.update_settings=Päivitä asetukset
+
+
settings.delete=Poista organisaatio
settings.delete_account=Poista tämä organisaatio
settings.delete_prompt=Organisaatio poistetaan pysyvästi, ja tätä <strong>EI VOI</strong> peruuttaa myöhemmin!
settings.confirm_delete_account=Vahvista poisto
-settings.delete_org_title=Poista organisaatio
settings.hooks_desc=Lisää webkoukkuja, jotka suoritetaan <strong>kaikissa repoissa</strong> tässä organisaatiossa.
@@ -1692,8 +1693,12 @@ conan.details.repository=Repo
owner.settings.cleanuprules.enabled=Käytössä
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Kuvaus
+
+
[actions]
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index b9d550eee5..03614832e9 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -130,6 +130,7 @@ pin=Épingler
unpin=Désépingler
artifacts=Artefacts
+expired=Expiré
confirm_delete_artifact=Êtes-vous sûr de vouloir supprimer l‘artefact « %s » ?
archived=Archivé
@@ -420,6 +421,7 @@ remember_me.compromised=Le jeton de connexion n’est plus valide, ce qui peut i
forgot_password_title=Mot de passe oublié
forgot_password=Mot de passe oublié ?
need_account=Besoin d‘un compte ?
+sign_up_tip=Vous êtes en train d’enregistrer le premier compte du système, doté des privilèges administrateur. Veuillez conserver précieusement ses nom d’utilisateur et mot de passe. En cas d’oublie, consultez la documentation de Gitea pour en récupérer l’accès.
sign_up_now=Inscrivez-vous dès maintenant !
sign_up_successful=Le compte a été créé avec succès. Bienvenue !
confirmation_mail_sent_prompt_ex=Un nouveau courriel de confirmation a été envoyé à <b>%s</b>. Veuillez vérifier votre boîte de réception dans la prochaine %s pour terminer le processus d’inscription. Si votre adresse courriel est incorrecte, vous pouvez vous reconnecter et la modifier.
@@ -450,6 +452,7 @@ use_scratch_code=Utiliser un code de secours
twofa_scratch_used=Vous avez utilisé votre code de secours. Vous avez été redirigé vers cette page de configuration afin de supprimer l'authentification à deux facteurs de votre appareil ou afin de générer un nouveau code de secours.
twofa_passcode_incorrect=Votre code d’accès n’est pas correct. Si vous avez égaré votre appareil, utilisez votre code de secours pour vous connecter.
twofa_scratch_token_incorrect=Votre code de secours est incorrect.
+twofa_required=Vous devez configurer l’authentification à deux facteurs pour avoir accès aux dépôts, ou essayer de vous reconnecter.
login_userpass=Connexion
login_openid=OpenID
oauth_signup_tab=Créer un compte
@@ -1226,6 +1229,7 @@ migrate.migrating_issues=Migration des tickets
migrate.migrating_pulls=Migration des demandes d'ajout
migrate.cancel_migrating_title=Annuler la migration
migrate.cancel_migrating_confirm=Voulez-vous abandonner cette migration ?
+migration_status=Statut de la migration
mirror_from=miroir de
forked_from=bifurqué depuis
@@ -1329,7 +1333,9 @@ editor.upload_file=Téléverser un fichier
editor.edit_file=Modifier le fichier
editor.preview_changes=Aperçu des modifications
editor.cannot_edit_lfs_files=Les fichiers LFS ne peuvent pas être modifiés dans l'interface web.
+editor.cannot_edit_too_large_file=Le fichier est trop gros pour être édité.
editor.cannot_edit_non_text_files=Les fichiers binaires ne peuvent pas être édités dans l'interface web.
+editor.file_not_editable_hint=Mais vous pouvez toujours le renommer ou le déplacer.
editor.edit_this_file=Modifier le fichier
editor.this_file_locked=Le fichier est verrouillé
editor.must_be_on_a_branch=Vous devez être sur une branche pour appliquer ou proposer des modifications à ce fichier.
@@ -1341,7 +1347,7 @@ editor.name_your_file=Nommez votre fichier…
editor.filename_help=Ajoutez un dossier en entrant son nom suivi d'une barre oblique ('/'). Supprimez un dossier avec un retour arrière au début du champ.
editor.or=ou
editor.cancel_lower=Annuler
-editor.commit_signed_changes=Réviser les changements (signé)
+editor.commit_signed_changes=Réviser les changements signés
editor.commit_changes=Réviser les changements
editor.add_tmpl=Ajouter {filename}
editor.add=Ajouter %s
@@ -1349,7 +1355,7 @@ editor.update=Actualiser %s
editor.delete=Supprimer %s
editor.patch=Appliquer le correctif
editor.patching=Correction:
-editor.fail_to_apply_patch=`Impossible d'appliquer le correctif "%s"`
+editor.fail_to_apply_patch=Impossible d’appliquer le correctif.
editor.new_patch=Nouveau correctif
editor.commit_message_desc=Ajouter une description détaillée facultative…
editor.signoff_desc=Créditer l'auteur "Signed-off-by:" en pied de révision.
@@ -1369,8 +1375,7 @@ 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. Ce type de fichiers ne peut être modifié dans l'éditeur web.`
editor.filename_is_a_directory=« %s » est déjà utilisé comme nom de dossier dans ce dépôt.
-editor.file_editing_no_longer_exists=Impossible de modifier le fichier « %s » car il n’existe plus dans ce dépôt.
-editor.file_deleting_no_longer_exists=Impossible de supprimer le fichier « %s » car il n’existe plus dans ce dépôt.
+editor.file_modifying_no_longer_exists=Le fichier en cours d’édition, '%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. <a target="_blank" rel="noopener noreferrer" href="%s">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.
editor.file_already_exists=Un fichier nommé "%s" existe déjà dans ce dépôt.
editor.commit_id_not_matching=L’ID de la révision ne correspond pas à l’ID lorsque vous avez commencé à éditer. Faites une révision dans une branche de correctif puis fusionnez.
@@ -1378,8 +1383,6 @@ editor.push_out_of_date=Cet envoi semble être obsolète.
editor.commit_empty_file_header=Réviser un fichier vide
editor.commit_empty_file_text=Le fichier que vous allez réviser est vide. Continuer ?
editor.no_changes_to_show=Il n’y a aucune modification à afficher.
-editor.fail_to_update_file=Impossible de mettre à jour/créer le fichier "%s".
-editor.fail_to_update_file_summary=Message d'erreur :
editor.push_rejected_no_message=La modification a été rejetée par le serveur sans message. Veuillez vérifier les Git Hooks.
editor.push_rejected=La modification a été rejetée par le serveur. Veuillez vérifier vos Git Hooks.
editor.push_rejected_summary=Message de rejet complet :
@@ -1393,6 +1396,15 @@ editor.user_no_push_to_branch=L'utilisateur ne peut pas pousser vers la branche
editor.require_signed_commit=Cette branche nécessite une révision signée
editor.cherry_pick=Picorer %s vers:
editor.revert=Rétablir %s sur:
+editor.failed_to_commit=Impossible de réviser les modifications.
+editor.failed_to_commit_summary=Message d’erreur :
+
+editor.fork_create=Bifurquer le Dépôt pour proposer vos modifications
+editor.fork_create_description=Vous ne pouvez pas modifier ce dépôt directement. Cependant, vous pouvez bifurquer ce dépôt, et créer une demande d’ajout avec vos contributions.
+editor.fork_edit_description=Vous ne pouvez pas modifier ce dépôt directement. Les modifications seront écrites sur une bifurcation <b>%s</b>, et ainsi faire une demande d’ajout.
+editor.fork_not_editable=Vous avez bifurqué ce dépôt mais votre copie n’est pas modifiable.
+editor.fork_failed_to_push_branch=Impossible de pousser la branche %s vers votre dépôt.
+editor.fork_branch_exists=La branche « %s » existe déjà dans votre bifurcation, veuillez choisir un nouveau nom de branche.
commits.desc=Naviguer dans l'historique des modifications.
commits.commits=Révisions
@@ -1557,6 +1569,7 @@ issues.filter_user_placeholder=Rechercher des utilisateurs
issues.filter_user_no_select=Tous les utilisateurs
issues.filter_type=Type
issues.filter_type.all_issues=Tous les tickets
+issues.filter_type.all_pull_requests=Toutes les demandes d’ajout
issues.filter_type.assigned_to_you=Qui vous sont assignés
issues.filter_type.created_by_you=Créés par vous
issues.filter_type.mentioning_you=Vous mentionnant
@@ -1648,6 +1661,7 @@ issues.save=Enregistrer
issues.label_title=Nom du label
issues.label_description=Description du label
issues.label_color=Couleur du label
+issues.label_color_invalid=Couleur invalide
issues.label_exclusive=Exclusif
issues.label_archive=Archivé
issues.label_archived_filter=Afficher les labels archivés
@@ -1712,6 +1726,8 @@ issues.remove_time_estimate_at=a supprimé le temps estimé %s
issues.time_estimate_invalid=Le format du temps estimé est invalide
issues.start_tracking_history=`a commencé son travail %s.`
issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé.
+issues.stopwatch_already_stopped=Le minuteur pour ce ticket est déjà arrêtée.
+issues.stopwatch_already_created=Le minuteur pour ce ticket existe déjà.
issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur <a href="%s">un autre ticket</a> !`
issues.stop_tracking=Arrêter le minuteur
issues.stop_tracking_history=a travaillé <b>%[1]s</b> %[2]s
@@ -1878,6 +1894,7 @@ pulls.add_prefix=Ajouter le préfixe <strong>%s</strong>
pulls.remove_prefix=Enlever le préfixe <strong>%s</strong>
pulls.data_broken=Cette demande d’ajout est impossible par manque d'informations de bifurcation.
pulls.files_conflicted=Cette demande d'ajout contient des modifications en conflit avec la branche ciblée.
+pulls.is_checking=Recherche de conflits de fusion…
pulls.is_ancestor=Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner.
pulls.is_empty=Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide.
pulls.required_status_check_failed=Certains contrôles requis n'ont pas réussi.
@@ -2148,6 +2165,7 @@ settings.collaboration.write=Écriture
settings.collaboration.read=Lecture
settings.collaboration.owner=Propriétaire
settings.collaboration.undefined=Indéfini
+settings.collaboration.per_unit=Permissions de ressource
settings.hooks=Webhooks
settings.githooks=Déclencheurs Git
settings.basic_settings=Paramètres de base
@@ -2366,7 +2384,7 @@ settings.event_repository=Dépôt
settings.event_repository_desc=Dépôt créé ou supprimé.
settings.event_header_issue=Événements de ticket
settings.event_issues=Ticket
-settings.event_issues_desc=Ticket ouvert, rouvert, fermé ou modifié.
+settings.event_issues_desc=Ticket ouvert, rouvert, fermé, modifié ou supprimé.
settings.event_issue_assign=Assignation
settings.event_issue_assign_desc=Ticket assigné ou dé-assigné.
settings.event_issue_label=Labellisation
@@ -2377,7 +2395,7 @@ settings.event_issue_comment=Commentaire
settings.event_issue_comment_desc=Commentaire créé, modifié ou supprimé.
settings.event_header_pull_request=Événements de demande d'ajout
settings.event_pull_request=Demande d'ajout
-settings.event_pull_request_desc=Demande d’ajout ouverte, rouverte, fermée ou modifiée.
+settings.event_pull_request_desc=Demande d’ajout ouverte, rouverte, fermée, modifiée ou supprimée.
settings.event_pull_request_assign=Assignation
settings.event_pull_request_assign_desc=Demande d'ajout assignée ou non assignée.
settings.event_pull_request_label=Labellisation
@@ -2395,8 +2413,10 @@ settings.event_pull_request_review_request_desc=Création ou suppresion de deman
settings.event_pull_request_approvals=Approbations de demande d'ajout
settings.event_pull_request_merge=Fusion de demande d'ajout
settings.event_header_workflow=Événements du flux de travail
+settings.event_workflow_run=Exécution du flux de travail
+settings.event_workflow_run_desc=Tâche du flux de travail Gitea Actions ajoutée, en attente, en cours ou terminée.
settings.event_workflow_job=Tâches du flux de travail
-settings.event_workflow_job_desc=Tâche du flux de travail Gitea Actions en file d’attente, en attente, en cours ou terminée.
+settings.event_workflow_job_desc=Travaux du flux de travail Gitea Actions en file d’attente, en attente, en cours ou terminée.
settings.event_package=Paquet
settings.event_package_desc=Paquet créé ou supprimé.
settings.branch_filter=Filtre de branche
@@ -2749,6 +2769,8 @@ branch.new_branch_from=`Créer une nouvelle branche à partir de "%s"`
branch.renamed=La branche %s à été renommée en %s.
branch.rename_default_or_protected_branch_error=Seuls les administrateurs peuvent renommer les branches par défaut ou protégées.
branch.rename_protected_branch_failed=Cette branche est protégée par des règles de protection basées sur des globs.
+branch.commits_divergence_from=Divergence de révisions : %[1]d en retard et %[2]d en avance sur %[3]s
+branch.commits_no_divergence=Identique à la branche %[1]s
tag.create_tag=Créer l'étiquette %s
tag.create_tag_operation=Créer une étiquette
@@ -2802,6 +2824,7 @@ team_permission_desc=Autorisation
team_unit_desc=Permettre l’accès aux Sections du dépôt
team_unit_disabled=(Désactivé)
+form.name_been_taken=Le nom d’organisation « %s » a déjà été utilisé.
form.name_reserved=Le nom d'organisation "%s" est réservé.
form.name_pattern_not_allowed=Le motif « %s » n'est pas autorisé dans un nom d'organisation.
form.create_org_not_allowed=Vous n'êtes pas autorisé à créer une organisation.
@@ -2823,15 +2846,28 @@ settings.visibility.private_shortname=Privé
settings.update_settings=Appliquer les paramètres
settings.update_setting_success=Les paramètres de l'organisation ont été mis à jour.
-settings.change_orgname_prompt=Remarque : Changer le nom de l'organisation changera également l'URL de votre organisation et libèrera l'ancien nom.
-settings.change_orgname_redirect_prompt=L'ancien nom d'utilisateur redirigera jusqu'à ce qu'il soit réclamé.
+
+settings.rename=Renommer l’organisation
+settings.rename_desc=Changer le nom de l’organisation changera également l’URL de votre organisation et libèrera l’ancien nom.
+settings.rename_success=L’organisation %[1]s a bien été renommé en %[2]s.
+settings.rename_no_change=Le nom de l’organisation n’a pas été modifié.
+settings.rename_new_org_name=Nouveau nom d’organisation
+settings.rename_failed=Le renommage de l’organisation a échoué en raison d’une erreur interne.
+settings.rename_notices_1=Cette opération <strong>ne peut pas </strong> être annulée.
+settings.rename_notices_2=L’ancien nom redirigera jusqu'à ce qu'il soit réclamé.
+
settings.update_avatar_success=L'avatar de l'organisation a été mis à jour.
settings.delete=Supprimer l'organisation
settings.delete_account=Supprimer cette organisation
settings.delete_prompt=Cette organisation sera supprimée définitivement. Cette action est <strong>IRRÉVERSIBLE</strong> !
+settings.name_confirm=Entrez le nom de l’organisation pour confirmer :
+settings.delete_notices_1=Cette opération <strong>ne peut pas </strong> être annulée.
+settings.delete_notices_2=Cette opération supprimera définitivement <strong>tous les dépôts</strong> de <strong>%s</strong>, y compris le code, les tickets, les commentaires, les données de wiki et les accès des collaborateurs.
+settings.delete_notices_3=Cette opération supprimera définitivement <strong>tous les paquets</strong> de <strong>%s</strong>.
+settings.delete_notices_4=Cette opération supprimera définitivement <strong>tous les projets</strong> de <strong>%s</strong>.
settings.confirm_delete_account=Confirmer la suppression
-settings.delete_org_title=Supprimer l'organisation
-settings.delete_org_desc=Cette organisation sera supprimée définitivement. Voulez-vous continuer ?
+settings.delete_failed=La suppression de l’organisation a échoué en raison d’une erreur interne.
+settings.delete_successful=L’organisation <b>%s</b> a été supprimée avec succès.
settings.hooks_desc=Vous pouvez ajouter des webhooks qui seront activés pour <strong>tous les dépôts</strong> de cette organisation.
settings.labels_desc=Ajoute des labels qui peuvent être utilisés sur les tickets pour <strong>tous les dépôts</strong> de cette organisation.
@@ -3427,12 +3463,12 @@ monitor.queue.type=Type
monitor.queue.exemplar=Type d'exemple
monitor.queue.numberworkers=Nombre de processus
monitor.queue.activeworkers=Processus actifs
-monitor.queue.maxnumberworkers=Nombre maximale de processus
+monitor.queue.maxnumberworkers=Nombre maximal de processus
monitor.queue.numberinqueue=Position dans la queue
monitor.queue.review_add=Examiner / ajouter des processus
monitor.queue.settings.title=Paramètres du réservoir
monitor.queue.settings.desc=Les bassins croissent proportionnellement au besoin de leurs exécuteurs.
-monitor.queue.settings.maxnumberworkers=Nombre maximale de processus
+monitor.queue.settings.maxnumberworkers=Nombre maximal de processus
monitor.queue.settings.maxnumberworkers.placeholder=Actuellement %[1]d
monitor.queue.settings.maxnumberworkers.error=Le nombre de processus doit être un nombre
monitor.queue.settings.submit=Appliquer les paramètres
@@ -3718,13 +3754,18 @@ owner.settings.chef.keypair.description=Une paire de clés est nécessaire pour
secrets=Secrets
description=Les secrets seront transmis à certaines actions et ne pourront pas être lus autrement.
none=Il n'y a pas encore de secrets.
-creation=Ajouter un secret
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Description
creation.name_placeholder=Caractères alphanumériques ou tirets bas uniquement, insensibles à la casse, ne peut commencer par GITEA_ ou GITHUB_.
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillés.
creation.description_placeholder=Décrire brièvement votre dépôt (optionnel).
-creation.success=Le secret "%s" a été ajouté.
-creation.failed=Impossible d'ajouter le secret.
+
+save_success=Le secret « %s » a été enregistré.
+save_failed=Impossible d’enregistrer le secret.
+
+add_secret=Ajouter un secret
+edit_secret=Modifier le secret
deletion=Supprimer le secret
deletion.description=La suppression d'un secret est permanente et irréversible. Continuer ?
deletion.success=Le secret a été supprimé.
@@ -3802,6 +3843,11 @@ runs.no_workflows.documentation=Pour plus d’informations sur les actions Gitea
runs.no_runs=Le flux de travail n'a pas encore d'exécution.
runs.empty_commit_message=(message de révision vide)
runs.expire_log_message=Les journaux ont été supprimés car ils étaient trop anciens.
+runs.delete=Supprimer cette exécution
+runs.cancel=Annuler l’exécution du flux
+runs.delete.description=Êtes-vous sûr de vouloir supprimer définitivement cette exécution ? Cette action ne peut pas être annulée.
+runs.not_done=Cette exécution du flux de travail n’est pas terminée.
+runs.view_workflow_file=Voir le fichier du flux de travail
workflow.disable=Désactiver le flux de travail
workflow.disable_success=Le flux de travail « %s » a bien été désactivé.
diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini
index ca9712b9e1..7c7cb58dcf 100644
--- a/options/locale/locale_ga-IE.ini
+++ b/options/locale/locale_ga-IE.ini
@@ -421,6 +421,7 @@ remember_me.compromised=Níl an comhartha logála isteach bailí níos mó a d'f
forgot_password_title=Dearmad ar an bPasfhocal
forgot_password=Dearmad ar an bPasfhocal?
need_account=An bhfuil cuntas ag teastáil uait?
+sign_up_tip=Tá tú ag clárú an chéad chuntais sa chóras, a bhfuil pribhléidí riarthóra aige. Cuimhnigh go cúramach ar d’ainm úsáideora agus do phasfhocal. Má dhéanann tú dearmad ar an ainm úsáideora nó ar an pasfhocal, féach ar dhoiciméadacht Gitea le do thoil chun an cuntas a aisghabháil.
sign_up_now=Cláraigh anois.
sign_up_successful=Cruthaíodh cuntas go rathúil. Fáilte romhat!
confirmation_mail_sent_prompt_ex=Tá ríomhphost dearbhaithe nua seolta chuig <b>%s</b>. Seiceáil do bhosca isteach laistigh den chéad %s eile chun an próiseas clárúcháin a chur i gcrích. Má tá do sheoladh ríomhphoist clárúcháin mícheart, is féidir leat síniú isteach arís agus é a athrú.
@@ -1228,6 +1229,7 @@ migrate.migrating_issues=Saincheisteanna Imirce
migrate.migrating_pulls=Iarratais Tarraingthe á n-Imirce
migrate.cancel_migrating_title=Cealaigh Imirce
migrate.cancel_migrating_confirm=Ar mhaith leat an imirce seo a chealú?
+migration_status=Stádas imirce
mirror_from=scáthán de
forked_from=forcailte ó
@@ -1331,7 +1333,9 @@ editor.upload_file=Uaslódáil Comhad
editor.edit_file=Cuir Comhad in eagar
editor.preview_changes=Athruithe Réamhamhar
editor.cannot_edit_lfs_files=Ní féidir comhaid LFS a chur in eagar sa chomhéadan gréasáin.
+editor.cannot_edit_too_large_file=Tá an comhad rómhór le cur in eagar.
editor.cannot_edit_non_text_files=Ní féidir comhaid dhénártha a chur in eagar sa chomhéadan gréasáin.
+editor.file_not_editable_hint=Ach is féidir leat é a athainmniú nó a bhogadh fós.
editor.edit_this_file=Cuir Comhad in eagar
editor.this_file_locked=Tá an comhad faoi ghlas
editor.must_be_on_a_branch=Caithfidh tú a bheith ar bhrainse chun athruithe a dhéanamh nó a mholadh ar an gcomhad seo.
@@ -1351,7 +1355,7 @@ editor.update=Nuashonraigh %s
editor.delete=Scrios %s
editor.patch=Cuir paiste i bhfeidh
editor.patching=Paisteáil:
-editor.fail_to_apply_patch=Ní féidir paiste "%s" a chur i bhfeidhm
+editor.fail_to_apply_patch=Ní féidir an paiste a chur i bhfeidhm
editor.new_patch=Paiste Nua
editor.commit_message_desc=Cuir cur síos leathnaithe roghnach leis…
editor.signoff_desc=Cuir leantóir sínithe ag an gcoiteoir ag deireadh na teachtaireachta logála tiomanta.
@@ -1371,8 +1375,7 @@ editor.branch_already_exists=Tá brainse "%s" ann cheana féin sa stóras seo.
editor.directory_is_a_file=Úsáidtear ainm eolaire "%s" cheana féin mar ainm comhaid sa stóras seo.
editor.file_is_a_symlink=Is nasc siombalach é "%s". Ní féidir naisc shiombalacha a chur in eagar san eagarthóir gréasáin
editor.filename_is_a_directory=Úsáidtear ainm comhaid "%s" cheana féin mar ainm eolaire sa stóras seo.
-editor.file_editing_no_longer_exists=Níl an comhad atá á chur in eagar, "%s", ann sa stóras seo a thuilleadh.
-editor.file_deleting_no_longer_exists=Níl an comhad atá á scriosadh, "%s", ann sa stóras seo a thuilleadh.
+editor.file_modifying_no_longer_exists=Níl an comhad atá á mhodhnú, "%s", sa stóras seo a thuilleadh.
editor.file_changed_while_editing=Tá athrú tagtha ar ábhar an chomhad ó thosaigh tú ag eagarthóireacht <a target="_blank" rel="noopener noreferrer" href="%s">Cliceáil anseo</a> chun iad a fheiceáil nó Athru <strong>ithe a Tiomantas arís</strong> chun iad a fhorscríobh.
editor.file_already_exists=Tá comhad darb ainm "%s" ann cheana féin sa stóras seo.
editor.commit_id_not_matching=Ní mheaitseálann an ID Tiomanta leis an ID nuair a thosaigh tú ag eagarthóireacht. Tiomanta isteach i mbrainse paiste agus ansin cumaisc.
@@ -1380,8 +1383,6 @@ editor.push_out_of_date=Is cosúil go bhfuil an brú as dáta.
editor.commit_empty_file_header=Tiomantas comhad folamh
editor.commit_empty_file_text=Tá an comhad atá tú ar tí tiomantas folamh. Ar aghaidh?
editor.no_changes_to_show=Níl aon athruithe le taispeáint.
-editor.fail_to_update_file=Theip ar nuashonrú/cruthú comhad "%s".
-editor.fail_to_update_file_summary=Teachtaireacht Earráide:
editor.push_rejected_no_message=Dhiúltaigh an freastalaí an t-athrú gan teachtaireacht. Seiceáil Git Hooks le do thoil.
editor.push_rejected=Dhiúltaigh an freastalaí an t-athrú. Seiceáil Git Hooks le do thoil.
editor.push_rejected_summary=Teachtaireacht Diúltaithe Iomlán:
@@ -1395,6 +1396,15 @@ editor.user_no_push_to_branch=Ní féidir leis an úsáideoir brúigh go dtí an
editor.require_signed_commit=Éilíonn an Brainse tiomantas sínithe
editor.cherry_pick=Roghnaigh silíní %s ar:
editor.revert=Fill %s ar:
+editor.failed_to_commit=Theip ar athruithe a chur i bhfeidhm.
+editor.failed_to_commit_summary=Teachtaireacht Earráide:
+
+editor.fork_create=Stóras Forc chun Athruithe a Mholadh
+editor.fork_create_description=Ní féidir leat an stóras seo a chur in eagar go díreach. Ina áit sin, is féidir leat forc a chruthú, eagarthóireachtaí a dhéanamh agus iarratas tarraingthe a chruthú.
+editor.fork_edit_description=Ní féidir leat an stóras seo a chur in eagar go díreach. Scríobhfar na hathruithe chuig do fhorc <b>%s</b>, ionas gur féidir leat iarratas tarraingthe a chruthú.
+editor.fork_not_editable=Tá forc déanta agat ar an stóras seo ach ní féidir do fhorc a chur in eagar.
+editor.fork_failed_to_push_branch=Theip ar bhrainse %s a bhrú chuig do stóras.
+editor.fork_branch_exists=Tá brainse "%s" ann cheana féin i do fhorc, roghnaigh ainm brainse nua le do thoil.
commits.desc=Brabhsáil stair athraithe cód foinse.
commits.commits=Tiomáintí
@@ -1559,6 +1569,7 @@ issues.filter_user_placeholder=Cuardaigh úsáideoirí
issues.filter_user_no_select=Gach úsáideoir
issues.filter_type=Cineál
issues.filter_type.all_issues=Gach saincheist
+issues.filter_type.all_pull_requests=Gach iarratas tarraingt
issues.filter_type.assigned_to_you=Sannta duit
issues.filter_type.created_by_you=Cruthaithe agat
issues.filter_type.mentioning_you=Ag tagairt duit
@@ -1650,6 +1661,7 @@ issues.save=Sábháil
issues.label_title=Ainm
issues.label_description=Cur síos
issues.label_color=Dath
+issues.label_color_invalid=Dath neamhbhailí
issues.label_exclusive=Eisiach
issues.label_archive=Lipéad Cartlann
issues.label_archived_filter=Taispeáin lipéid cartlainne
@@ -1714,6 +1726,8 @@ issues.remove_time_estimate_at=baineadh meastachán ama %s
issues.time_estimate_invalid=Tá formáid meastachán ama neamhbhailí
issues.start_tracking_history=thosaigh ag obair %s
issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo
+issues.stopwatch_already_stopped=Tá an lasc ama don cheist seo stoptha cheana féin
+issues.stopwatch_already_created=Tá an lasc ama don cheist seo ann cheana féin
issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar <a href="%s">eagrán eile</a>!`
issues.stop_tracking=Stad uaineadóir
issues.stop_tracking_history=d'oibrigh do <b>%[1]s</b> %[2]s
@@ -1955,7 +1969,7 @@ pulls.cmd_instruction_checkout_title=Seiceáil
pulls.cmd_instruction_checkout_desc=Ó stór tionscadail, seiceáil brainse nua agus déan tástáil ar na hathruithe.
pulls.cmd_instruction_merge_title=Cumaisc
pulls.cmd_instruction_merge_desc=Cumaisc na hathruithe agus nuashonrú ar Gitea.
-pulls.cmd_instruction_merge_warning=Rabhadh: Ní féidir leis an oibríocht seo an t-iarratas tarraingthe a chumasc toisc nach raibh "cumasc láimhe uathoibríoch" cumasaithe
+pulls.cmd_instruction_merge_warning=Rabhadh: Ní féidir iarratas tarraingthe cumaisc a dhéanamh leis an oibríocht seo mar nach bhfuil "autodetect manual merge" cumasaithe.
pulls.clear_merge_message=Glan an teachtaireacht chumaisc
pulls.clear_merge_message_hint=Má imrítear an teachtaireacht chumaisc ní bhainfear ach ábhar na teachtaireachta tiomanta agus coimeádfar leantóirí git ginte ar nós "Co-Authored-By …".
@@ -2151,6 +2165,7 @@ settings.collaboration.write=Scríobh
settings.collaboration.read=Léigh
settings.collaboration.owner=Úinéir
settings.collaboration.undefined=Neamhshainithe
+settings.collaboration.per_unit=Ceadanna Aonaid
settings.hooks=Crúcaí Gréasán
settings.githooks=Crúcanna Git
settings.basic_settings=Socruithe Bunúsacha
@@ -2369,7 +2384,7 @@ settings.event_repository=Stóras
settings.event_repository_desc=Stóras a cruthaíodh nó a scriosadh.
settings.event_header_issue=Imeachtaí Eisiúint
settings.event_issues=Saincheisteanna
-settings.event_issues_desc=Osclaíodh, dúnadh, athosclaíodh nó cuireadh an cheist in eagar.
+settings.event_issues_desc=Ceist oscailte, dúnta, athoscailte, eagarthóireachta nó scriosta.
settings.event_issue_assign=Saincheist Sannaithe
settings.event_issue_assign_desc=Eisiúint sannta nó neamhshannta.
settings.event_issue_label=Eisiúint Lipéadaithe
@@ -2380,7 +2395,7 @@ settings.event_issue_comment=Trácht Eisiúna
settings.event_issue_comment_desc=Trácht eisiúna cruthaithe, curtha in eagar nó a scriosadh.
settings.event_header_pull_request=Tarraingt Imeachtaí Iarratas
settings.event_pull_request=Iarratas Tarraingthe
-settings.event_pull_request_desc=Iarratas tarraingthe oscailte, dúnta, athoscailte nó curtha in eagar.
+settings.event_pull_request_desc=Iarratas tarraingthe a osclaíodh, a dúnadh, a hathosclaíodh, a cuireadh in eagar nó a scriosadh.
settings.event_pull_request_assign=Iarratas Tarraingthe Sannta
settings.event_pull_request_assign_desc=Iarratas tarraingthe sannta nó neamhshannta.
settings.event_pull_request_label=Iarratas Tarraingthe Lipéadaithe
@@ -2398,6 +2413,8 @@ settings.event_pull_request_review_request_desc=Tarraing athbhreithniú iarratai
settings.event_pull_request_approvals=Ceaduithe Iarratais Tarraing
settings.event_pull_request_merge=Cumaisc Iarratas Tarraing
settings.event_header_workflow=Imeachtaí Sreabhadh Oibre
+settings.event_workflow_run=Rith Sreabhadh Oibre
+settings.event_workflow_run_desc=Tá rith Sreabhadh Oibre Gníomhartha Gitea sa scuaine, ag fanacht, ar siúl, nó críochnaithe.
settings.event_workflow_job=Poist Sreabhadh Oibre
settings.event_workflow_job_desc=Gitea Actions Sreabhadh oibre post ciúáilte, ag fanacht, ar siúl, nó críochnaithe.
settings.event_package=Pacáiste
@@ -2752,6 +2769,8 @@ branch.new_branch_from=`Cruthaigh brainse nua ó "%s"`
branch.renamed=Ainmníodh brainse %s go %s.
branch.rename_default_or_protected_branch_error=Ní féidir ach le riarthóirí brainsí réamhshocraithe nó cosanta a athainmniú.
branch.rename_protected_branch_failed=Tá an brainse seo faoi chosaint ag rialacha cosanta domhanda.
+branch.commits_divergence_from=Déanann sé dialltacht a thiomnú: %[1]d taobh thiar agus %[2]d chun tosaigh ar %[3]s
+branch.commits_no_divergence=Mar an gcéanna le brainse %[1]s
tag.create_tag=Cruthaigh clib %s
tag.create_tag_operation=Cruthaigh clib
@@ -2765,6 +2784,7 @@ topic.done=Déanta
topic.count_prompt=Ní féidir leat níos mó ná 25 topaicí a roghnú
topic.format_prompt=Ní mór do thopaicí tosú le litir nó uimhir, is féidir daiseanna ('-') agus poncanna ('.') a áireamh, a bheith suas le 35 carachtar ar fad. Ní mór litreacha a bheith i litreacha beaga.
+find_file.follow_symlink=Lean an nasc siombalach seo go dtí an áit a bhfuil sé ag pointeáil air
find_file.go_to_file=Téigh go dtí an comhad
find_file.no_matching=Níl aon chomhad meaitseála le fáil
@@ -2805,6 +2825,7 @@ team_permission_desc=Cead
team_unit_desc=Ceadaigh Rochtain ar Rannóga Stóras
team_unit_disabled=(Díchumasaithe)
+form.name_been_taken=Tá ainm na heagraíochta "%s" tógtha cheana féin.
form.name_reserved=Tá an t-ainm eagraíochta "%s" curtha in áirithe.
form.name_pattern_not_allowed=Ní cheadaítear an patrún "%s" in ainm eagraíochta.
form.create_org_not_allowed=Níl cead agat eagraíocht a chruthú.
@@ -2826,15 +2847,28 @@ settings.visibility.private_shortname=Príobháideach
settings.update_settings=Nuashonrú Socruithe
settings.update_setting_success=Nuashonraíodh socruithe eagraíochta.
-settings.change_orgname_prompt=Nóta: Athróidh ainm na heagraíochta ag athrú URL d'eagraíochta agus saorfar an sean-ainm.
-settings.change_orgname_redirect_prompt=Déanfaidh an sean-ainm a atreorú go dtí go n-éilítear é.
+
+settings.rename=Athainmnigh an Eagraíocht
+settings.rename_desc=Má athraíonn tú ainm na heagraíochta, athrófar URL d’eagraíochta freisin agus saorfar an seanainm.
+settings.rename_success=Athainmníodh an eagraíocht %[1]s go %[2]s go rathúil.
+settings.rename_no_change=Níl aon athrú ar ainm na heagraíochta.
+settings.rename_new_org_name=Ainm Nua na hEagraíochta
+settings.rename_failed=Theip ar athainmniú na hEagraíochta mar gheall ar earráid inmheánach
+settings.rename_notices_1=Nà <strong>FÉIDIR</strong> an oibríocht seo a chealú.
+settings.rename_notices_2=Déanfar an seanainm a atreorú go dtí go n-éileofar é.
+
settings.update_avatar_success=Nuashonraíodh avatar na heagraíochta.
settings.delete=Scrios Eagraíocht
settings.delete_account=Scrios an Eagraíocht seo
settings.delete_prompt=Bainfear an eagraíocht go buan. <strong>Nà FÉIDIR</strong> é seo a chealú!
+settings.name_confirm=Cuir isteach ainm na heagraíochta mar dheimhniú:
+settings.delete_notices_1=Nà <strong>FÉIDIR</strong> an oibríocht seo a chealú.
+settings.delete_notices_2=Scriosfaidh an oibríocht seo go buan gach <strong>stórais</strong> de chuid <strong>%s</strong>, lena n-áirítear cód, saincheisteanna, tuairimí, sonraí vicí agus socruithe comhoibritheora.
+settings.delete_notices_3=Scriosfaidh an oibríocht seo gach <strong>pacáiste</strong> de chuid <strong>%s</strong> go buan.
+settings.delete_notices_4=Scriosfaidh an oibríocht seo gach <strong>tionscadal</strong> de chuid <strong>%s</strong> go buan.
settings.confirm_delete_account=Deimhnigh scriosadh
-settings.delete_org_title=Scrios Eagraíocht
-settings.delete_org_desc=Scriosfar an eagraíocht seo go buan. Lean ar aghaidh?
+settings.delete_failed=Theip ar Scriosadh na hEagraíochta mar gheall ar earráid inmheánach
+settings.delete_successful=Scriosadh an eagraíocht <b>%s</b> go rathúil.
settings.hooks_desc=Cuir crúcaí gréasán in leis a spreagfar do <strong>gach stóras</strong> faoin eagraíocht seo.
settings.labels_desc=Cuir lipéid leis ar féidir iad a úsáid ar shaincheisteanna do <strong>gach stóras</strong> faoin eagraíocht seo.
@@ -3721,13 +3755,18 @@ owner.settings.chef.keypair.description=Tá eochairphéire riachtanach le fíord
secrets=Rúin
description=Cuirfear rúin ar aghaidh chuig gníomhartha áirithe agus ní féidir iad a léamh ar mhalairt.
none=Níl aon rúin ann fós.
-creation=Cuir Rúnda leis
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Cur síos
creation.name_placeholder=carachtair alfanumair nó íoslaghda amháin nach féidir a thosú le GITEA_ nó GITHUB_
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
creation.description_placeholder=Cuir isteach cur síos gairid (roghnach).
-creation.success=Tá an rún "%s" curtha leis.
-creation.failed=Theip ar an rún a chur leis.
+
+save_success=Tá an rún "%s" sábháilte.
+save_failed=Theip ar an rún a shábháil.
+
+add_secret=Cuir rún leis
+edit_secret=Cuir rún in eagar
deletion=Bain rún
deletion.description=Is buan rún a bhaint agus ní féidir é a chealú. Lean ort?
deletion.success=Tá an rún bainte.
@@ -3805,6 +3844,11 @@ runs.no_workflows.documentation=Le haghaidh tuilleadh eolais ar Gitea Actions, f
runs.no_runs=Níl aon rith ag an sreabhadh oibre fós.
runs.empty_commit_message=(teachtaireacht tiomantas folamh)
runs.expire_log_message=Glanadh logaí toisc go raibh siad ró-sean.
+runs.delete=Scrios rith sreabha oibre
+runs.cancel=Cealaigh rith an tsreabha oibre
+runs.delete.description=An bhfuil tú cinnte gur mian leat an rith sreabha oibre seo a scriosadh go buan? Ní féidir an gníomh seo a chealú.
+runs.not_done=Níl an rith sreabha oibre seo críochnaithe.
+runs.view_workflow_file=Féach ar chomhad sreabha oibre
workflow.disable=Díchumasaigh sreabhadh oibre
workflow.disable_success=D'éirigh le sreabhadh oibre '%s' a dhíchumasú.
diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini
index 0dae5505aa..e823b325a5 100644
--- a/options/locale/locale_hu-HU.ini
+++ b/options/locale/locale_hu-HU.ini
@@ -711,6 +711,7 @@ editor.commit_empty_file_header=Egy üres fájl commitolása
editor.no_changes_to_show=Nincsen megjeleníthető változás.
editor.add_subdir=Mappa hozzáadása…
+
commits.commits=Commit-ok
commits.search_all=Minden ág
commits.author=Szerző
@@ -1174,13 +1175,13 @@ settings.visibility.private_shortname=Privát
settings.update_settings=Beállítások frissítése
settings.update_setting_success=A szervezet beállításai frissültek.
+
+
settings.update_avatar_success=A szervezet avatarja frissítve.
settings.delete=Szervezet törlése
settings.delete_account=A szervezet törlése
settings.delete_prompt=A szervezet véglegesen el lesz távolítva. <strong>NEM</strong> vonható vissza!
settings.confirm_delete_account=Törlés megerősítése
-settings.delete_org_title=Szervezet törlése
-settings.delete_org_desc=Ez a szervezet véglegesen törölve lesz. Folytatható?
settings.hooks_desc=Webhook hozzáadása a szervezet <strong>összes tárolójához</strong>.
@@ -1592,8 +1593,12 @@ conan.details.repository=Tároló
owner.settings.cleanuprules.enabled=Engedélyezett
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Leírás
+
+
[actions]
diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini
index 808ebaa9ec..38f65c74ff 100644
--- a/options/locale/locale_id-ID.ini
+++ b/options/locale/locale_id-ID.ini
@@ -717,6 +717,7 @@ editor.new_branch_name_desc=Nama branch baru…
editor.cancel=Membatalkan
editor.no_changes_to_show=Tidak ada perubahan untuk ditampilkan.
+
commits.commits=Melakukan
commits.author=Penulis
commits.message=Pesan
@@ -1055,10 +1056,11 @@ settings.visibility.private_shortname=Pribadi
settings.update_settings=Perbarui Setelan
settings.update_setting_success=Pengaturan organisasi telah diperbarui.
+
+
settings.delete=Menghapus Organisasi
settings.delete_account=Menghapus Organisasi Ini
settings.confirm_delete_account=Konfirmasi Penghapusan
-settings.delete_org_title=Menghapus Organisasi
settings.hooks_desc=Tambahkan webhooks yang akan dipicu untuk <strong>semua repositori</strong> di bawah organisasi ini.
@@ -1394,8 +1396,12 @@ conan.details.repository=Repositori
owner.settings.cleanuprules.enabled=Aktif
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Deskripsi
+
+
[actions]
diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini
index 999b21c608..3b4c605801 100644
--- a/options/locale/locale_is-IS.ini
+++ b/options/locale/locale_is-IS.ini
@@ -689,7 +689,7 @@ editor.create_new_branch=Búðu til <strong>nýja grein</strong> og sameiningarb
editor.create_new_branch_np=Búðu til <strong>nýja grein</strong> fyrir þetta framlag.
editor.new_branch_name_desc=Heiti nýjar greinar…
editor.cancel=Hætta við
-editor.fail_to_update_file_summary=Villuskilaboð:
+
commits.commits=Framlög
commits.author=Höfundur
@@ -1027,11 +1027,9 @@ settings.event_release=Útgáfa
settings.event_push=Push
settings.event_repository=Hugbúnaðarsafn
settings.event_issues=Vandamál
-settings.event_issues_desc=Vandamál opið, lokað, enduropnað eða breytt.
settings.event_issue_label=Vandamál Lýst
settings.event_issue_comment=Ummæli um Vandamál
settings.event_pull_request=Sameiningarbeiðni
-settings.event_pull_request_desc=Sameiningarbeiðni opnuð, lokuð, enduropnuð eða breytt.
settings.active=Virkt
settings.update_webhook=Uppfæra Vefkrók
settings.slack_token=Táknlykill
@@ -1118,6 +1116,8 @@ settings.visibility.private_shortname=Einka
settings.update_settings=Uppfæra Stillingar
+
+
members.private=Faldir
members.owner=Eigandi
members.member=Meðlimur
@@ -1325,8 +1325,12 @@ npm.details.tag=Merki
pypi.requires=Þarfnast Python
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Lýsing
+
+
[actions]
diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini
index f4a6767ea4..2d4d5b71d0 100644
--- a/options/locale/locale_it-IT.ini
+++ b/options/locale/locale_it-IT.ini
@@ -1014,7 +1014,6 @@ editor.file_changed_while_editing=I contenuti di questo file hanno subito dei ca
editor.commit_empty_file_header=Commit di un file vuoto
editor.commit_empty_file_text=Il file che stai per effettuare il commit è vuoto. Procedere?
editor.no_changes_to_show=Non ci sono cambiamenti da mostrare.
-editor.fail_to_update_file_summary=Messaggio d'errore:
editor.push_rejected_no_message=La modifica è stata rifiutata dal server senza un messaggio. Controlla Git Hooks.
editor.push_rejected=La modifica è stata rifiutata dal server. Controlla Git Hooks.
editor.push_rejected_summary=Messaggio Di Rifiuto Completo:
@@ -1025,6 +1024,7 @@ editor.require_signed_commit=Il branch richiede un commit firmato
editor.cherry_pick=Cherry-pick %s suto:
editor.revert=Ripristina %s su:
+
commits.desc=Sfoglia la cronologia di modifiche del codice rogente.
commits.commits=Commit
commits.nothing_to_compare=Questi rami sono uguali.
@@ -1190,7 +1190,7 @@ issues.context.edit=Modifica
issues.context.delete=Elimina
issues.reopen_issue=Riapri
issues.create_comment=Commento
-issues.closed_at=`chiuso questo probleam <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+issues.closed_at=`ha chiuso questo problema <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`riaperto questo problema <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`ha fatto riferimento a questa issue dal commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">ha fatto riferimento a questo problema %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@@ -1764,7 +1764,6 @@ settings.event_repository=Repository
settings.event_repository_desc=Repository creato o eliminato.
settings.event_header_issue=Eventi dei Problemi
settings.event_issues=Issues
-settings.event_issues_desc=Issue aperto, chiuso, riaperto o modificato.
settings.event_issue_assign=Issue Assegnato
settings.event_issue_assign_desc=Issue assegnata o non assegnata.
settings.event_issue_label=Issue etichettato
@@ -1775,7 +1774,6 @@ settings.event_issue_comment=Commento Issue
settings.event_issue_comment_desc=Commento issue creato, modificato o rimosso.
settings.event_header_pull_request=Eventi di Pull Request
settings.event_pull_request=Pull Request
-settings.event_pull_request_desc=Pull request aperta, chiusa, riaperta o modificata.
settings.event_pull_request_assign=Pull Request assegnata
settings.event_pull_request_assign_desc=Pull request assegnata o non assegnata.
settings.event_pull_request_label=Pull Request etichettata
@@ -2078,14 +2076,13 @@ settings.visibility.private_shortname=Privato
settings.update_settings=Aggiorna Impostazioni
settings.update_setting_success=Le impostazioni dell'organizzazione sono state aggiornate.
-settings.change_orgname_redirect_prompt=Il vecchio nome reindirizzerà fino a quando non sarà richiesto.
+
+
settings.update_avatar_success=L'avatar dell'organizzazione è stato aggiornato.
settings.delete=Elimina organizzazione
settings.delete_account=Elimina questa organizzazione
settings.delete_prompt=L'organizzazione verrà rimossa definitivamente. Questa operazione <strong>NON PUÒ</strong> essere annullata!
settings.confirm_delete_account=Conferma Eliminazione
-settings.delete_org_title=Elimina organizzazione
-settings.delete_org_desc=Questa organizzazione verrà eliminata definitivamente. Continuare?
settings.hooks_desc=Aggiungi i webhooks che verranno attivati per <strong>tutti i repository</strong> sotto questa organizzazione.
settings.labels_desc=Aggiungi i webhooks che verranno attivati per <strong>tutti i repository</strong> sotto questa organizzazione.
@@ -2782,8 +2779,12 @@ settings.delete.error=Impossibile eliminare il pacchetto.
owner.settings.cleanuprules.enabled=Attivo
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Descrizione
+
+
[actions]
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index a6366565b2..d983dbab21 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -130,6 +130,7 @@ pin=ピン留ã‚
unpin=ピン留ã‚解除
artifacts=æˆæžœç‰©
+expired=期é™åˆ‡ã‚Œ
confirm_delete_artifact=アーティファクト %s を削除ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ
archived=アーカイブ
@@ -420,6 +421,7 @@ remember_me.compromised=ログイントークンã¯ã‚‚ã†æœ‰åйã§ã¯ãªãã€ã‚
forgot_password_title=パスワードを忘れãŸ
forgot_password=パスワードをãŠå¿˜ã‚Œã§ã™ã‹ï¼Ÿ
need_account=アカウントãŒå¿…è¦ã§ã™ã‹ï¼Ÿ
+sign_up_tip=管ç†è€…権é™ã‚’æŒã¤ã€ã“ã®ã‚·ã‚¹ãƒ†ãƒ ã®æœ€åˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’登録ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚ ユーザーåã¨ãƒ‘スワードをよã覚ãˆã¦ãŠã„ã¦ãã ã•ã„。 ユーザーåã¾ãŸã¯ãƒ‘スワードを忘れãŸå ´åˆã¯ã€Giteaã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’復元ã—ã¦ãã ã•ã„。
sign_up_now=登録ã¯ã“ã¡ã‚‰ã€‚
sign_up_successful=アカウントã¯ç„¡äº‹ã«ä½œæˆã•れã¾ã—ãŸã€‚よã†ã“ã!
confirmation_mail_sent_prompt_ex=æ–°ã—ã„確èªãƒ¡ãƒ¼ãƒ«ã‚’ <b>%s</b> ã«é€ä¿¡ã—ã¾ã—ãŸã€‚ %s以内ã«ãƒ¡ãƒ¼ãƒ«ãƒœãƒƒã‚¯ã‚¹ã‚’確èªã—ã€ç™»éŒ²æ‰‹ç¶šãを完了ã—ã¦ãã ã•ã„。 登録メールアドレスãŒé–“é•ã£ã¦ã„ã‚‹å ´åˆã¯ã€ã‚‚ã†ã„ã¡ã©ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã™ã‚‹ã¨å¤‰æ›´ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚
@@ -450,6 +452,7 @@ use_scratch_code=スクラッãƒã‚³ãƒ¼ãƒ‰ã‚’使ã†
twofa_scratch_used=ã‚ãªãŸã¯ã‚¹ã‚¯ãƒ©ãƒƒãƒã‚³ãƒ¼ãƒ‰ã‚’使用ã—ã¾ã—ãŸã€‚ 2è¦ç´ èªè¨¼ã®è¨­å®šãƒšãƒ¼ã‚¸ã«ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã—ã¾ã—ãŸã®ã§ã€ãƒ‡ãƒã‚¤ã‚¹ã®ç™»éŒ²ã‚’解除ã™ã‚‹ã‹ã€æ–°ã—ã„スクラッãƒã‚³ãƒ¼ãƒ‰ã‚’生æˆã—ã¾ã—ょã†ã€‚
twofa_passcode_incorrect=ãƒ‘ã‚¹ã‚³ãƒ¼ãƒ‰ãŒæ­£ã—ãã‚りã¾ã›ã‚“。デãƒã‚¤ã‚¹ã‚’紛失ã—ãŸå ´åˆã¯ã€ã‚¹ã‚¯ãƒ©ãƒƒãƒã‚³ãƒ¼ãƒ‰ã‚’使ã£ã¦ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã—ã¦ãã ã•ã„。
twofa_scratch_token_incorrect=スクラッãƒã‚³ãƒ¼ãƒ‰ãŒæ­£ã—ãã‚りã¾ã›ã‚“。
+twofa_required=リãƒã‚¸ãƒˆãƒªã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã«ã¯2段階èªè¨¼ã‚’設定ã™ã‚‹ã‹ã€å†åº¦ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ãã ã•ã„。
login_userpass=サインイン
login_openid=OpenID
oauth_signup_tab=æ–°è¦ã‚¢ã‚«ã‚¦ãƒ³ãƒˆç™»éŒ²
@@ -1329,7 +1332,9 @@ editor.upload_file=ファイルをアップロード
editor.edit_file=ファイルを編集
editor.preview_changes=変更をプレビュー
editor.cannot_edit_lfs_files=LFSã®ãƒ•ァイルã¯Webインターフェースã§ç·¨é›†ã§ãã¾ã›ã‚“。
+editor.cannot_edit_too_large_file=ã“ã®ãƒ•ァイルã¯å¤§ãã™ãŽã‚‹ãŸã‚ã€ç·¨é›†ã§ãã¾ã›ã‚“。
editor.cannot_edit_non_text_files=ãƒã‚¤ãƒŠãƒªãƒ•ァイルã¯Webインターフェースã§ç·¨é›†ã§ãã¾ã›ã‚“。
+editor.file_not_editable_hint=åå‰ã®å¤‰æ›´ã‚„移動ã¯å¯èƒ½ã§ã™ã€‚
editor.edit_this_file=ファイルを編集
editor.this_file_locked=ファイルã¯ãƒ­ãƒƒã‚¯ã•れã¦ã„ã¾ã™
editor.must_be_on_a_branch=ã“ã®ãƒ•ァイルを変更ã—ãŸã‚Šå¤‰æ›´ã®ææ¡ˆã‚’ã™ã‚‹ã«ã¯ã€ãƒ–ランãƒä¸Šã«ã„ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚
@@ -1349,7 +1354,7 @@ editor.update=%s ã‚’æ›´æ–°
editor.delete=%s を削除
editor.patch=パッãƒã®é©ç”¨
editor.patching=パッãƒ:
-editor.fail_to_apply_patch=`パッãƒã‚’é©ç”¨ã§ãã¾ã›ã‚“ "%s"`
+editor.fail_to_apply_patch=パッãƒã‚’é©ç”¨ã§ãã¾ã›ã‚“
editor.new_patch=æ–°ã—ã„パッãƒ
editor.commit_message_desc=詳細ãªèª¬æ˜Žã‚’追加…
editor.signoff_desc=ã‚³ãƒŸãƒƒãƒˆãƒ­ã‚°ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®æœ€å¾Œã«ã‚³ãƒŸãƒƒã‚¿ãƒ¼ã® Signed-off-by 行を追加
@@ -1369,8 +1374,7 @@ editor.branch_already_exists=ブランム"%s" ã¯ã€ã“ã®ãƒªãƒã‚¸ãƒˆãƒªã«æ—¢
editor.directory_is_a_file=ディレクトリå "%s" ã¯ã™ã§ã«ãƒªãƒã‚¸ãƒˆãƒªå†…ã®ãƒ•ァイルã§ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚
editor.file_is_a_symlink=`"%s" ã¯ã‚·ãƒ³ãƒœãƒªãƒƒã‚¯ãƒªãƒ³ã‚¯ã§ã™ã€‚ シンボリックリンクã¯Webエディターã§ç·¨é›†ã§ãã¾ã›ã‚“。`
editor.filename_is_a_directory=ファイルå "%s" ã¯ã€ã“ã®ãƒªãƒã‚¸ãƒˆãƒªä¸Šã§ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªåã¨ã—ã¦ã™ã§ã«ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚
-editor.file_editing_no_longer_exists=編集中ã®ãƒ•ァイル "%s" ãŒã€ã‚‚ã†ãƒªãƒã‚¸ãƒˆãƒªå†…ã«ã‚りã¾ã›ã‚“。
-editor.file_deleting_no_longer_exists=削除ã—よã†ã¨ã—ãŸãƒ•ァイル "%s" ãŒã€ã™ã§ã«ãƒªãƒã‚¸ãƒˆãƒªå†…ã«ã‚りã¾ã›ã‚“。
+editor.file_modifying_no_longer_exists=修正中ã®ãƒ•ァイル "%s" ãŒã€ã™ã§ã«ãƒªãƒã‚¸ãƒˆãƒªå†…ã«ã‚りã¾ã›ã‚“。
editor.file_changed_while_editing=ã‚ãªãŸãŒç·¨é›†ã‚’é–‹å§‹ã—ãŸã‚ã¨ã€ãƒ•ァイルã®å†…容ãŒå¤‰æ›´ã•れã¾ã—ãŸã€‚ <a target="_blank" rel="noopener noreferrer" href="%s">ã“ã“をクリック</a>ã—ã¦ä½•ãŒå¤‰æ›´ã•れãŸã‹ç¢ºèªã™ã‚‹ã‹ã€<strong>ã‚‚ã†ä¸€åº¦"変更をコミット"をクリック</strong>ã—ã¦ä¸Šæ›¸ãã—ã¾ã™ã€‚
editor.file_already_exists=ファイル "%s" ã¯ã€ã“ã®ãƒªãƒã‚¸ãƒˆãƒªã«æ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚
editor.commit_id_not_matching=コミットIDãŒç·¨é›†ã‚’é–‹å§‹ã—ãŸã¨ãã®IDã¨ä¸€è‡´ã—ã¾ã›ã‚“。 パッãƒç”¨ã®ãƒ–ランãƒã«ã‚³ãƒŸãƒƒãƒˆã—ãŸã‚ã¨ãƒžãƒ¼ã‚¸ã—ã¦ãã ã•ã„。
@@ -1378,8 +1382,6 @@ editor.push_out_of_date=ã“ã®ãƒ—ãƒƒã‚·ãƒ¥ã¯æœ€æ–°ã§ã¯ãªã„よã†ã§ã™ã€‚
editor.commit_empty_file_header=空ファイルã®ã‚³ãƒŸãƒƒãƒˆ
editor.commit_empty_file_text=コミットã—よã†ã¨ã—ã¦ã„るファイルã¯ç©ºã§ã™ã€‚ 続行ã—ã¾ã™ã‹ï¼Ÿ
editor.no_changes_to_show=表示ã™ã‚‹å¤‰æ›´ç®‡æ‰€ã¯ã‚りã¾ã›ã‚“。
-editor.fail_to_update_file=ファイル "%s" を作æˆã¾ãŸã¯å¤‰æ›´ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚
-editor.fail_to_update_file_summary=エラーメッセージ:
editor.push_rejected_no_message=サーãƒãƒ¼ãŒãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’出ã•ãšã«å¤‰æ›´ã‚’æ‹’å¦ã—ã¾ã—ãŸã€‚ Git フックを確èªã—ã¦ãã ã•ã„。
editor.push_rejected=サーãƒãƒ¼ãŒå¤‰æ›´ã‚’æ‹’å¦ã—ã¾ã—ãŸã€‚ Gitフックを確èªã—ã¦ãã ã•ã„。
editor.push_rejected_summary=æ‹’å¦ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸å…¨ä½“:
@@ -1393,6 +1395,9 @@ editor.user_no_push_to_branch=ユーザーã¯ãƒ–ランãƒã«ãƒ—ッシュã§ãã
editor.require_signed_commit=ブランãƒã§ã¯ç½²åã•れãŸã‚³ãƒŸãƒƒãƒˆãŒå¿…é ˆã§ã™
editor.cherry_pick=ãƒã‚§ãƒªãƒ¼ãƒ”ック %s:
editor.revert=リãƒãƒ¼ãƒˆ %s:
+editor.failed_to_commit=変更ã®ã‚³ãƒŸãƒƒãƒˆã«å¤±æ•—ã—ã¾ã—ãŸã€‚
+editor.failed_to_commit_summary=エラーメッセージ:
+
commits.desc=ソースコードã®å¤‰æ›´å±¥æ­´ã‚’å‚ç…§ã—ã¾ã™ã€‚
commits.commits=コミット
@@ -1557,6 +1562,7 @@ issues.filter_user_placeholder=ユーザーを検索
issues.filter_user_no_select=ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼
issues.filter_type=タイプ
issues.filter_type.all_issues=ã™ã¹ã¦ã®ã‚¤ã‚·ãƒ¥ãƒ¼
+issues.filter_type.all_pull_requests=ã™ã¹ã¦ã®ãƒ—ルリクエスト
issues.filter_type.assigned_to_you=è‡ªåˆ†ãŒæ‹…当
issues.filter_type.created_by_you=自分ãŒä½œæˆ
issues.filter_type.mentioning_you=自分ãŒé–¢ä¿‚
@@ -1648,6 +1654,7 @@ issues.save=ä¿å­˜
issues.label_title=åå‰
issues.label_description=説明
issues.label_color=カラー
+issues.label_color_invalid=無効ãªè‰²ã§ã™
issues.label_exclusive=排他
issues.label_archive=アーカイブ ラベル
issues.label_archived_filter=アーカイブã•れãŸãƒ©ãƒ™ãƒ«ã‚’表示
@@ -1878,6 +1885,7 @@ pulls.add_prefix=先頭㫠<strong>%s</strong> を追加
pulls.remove_prefix=先頭㮠<strong>%s</strong> を除去
pulls.data_broken=ã“ã®ãƒ—ルリクエストã¯ã€ãƒ•ã‚©ãƒ¼ã‚¯ã®æƒ…å ±ãŒè¦‹ã¤ã‹ã‚‰ãªã„ãŸã‚壊れã¦ã„ã¾ã™ã€‚
pulls.files_conflicted=ã“ã®ãƒ—ルリクエストã¯ã€ã‚¿ãƒ¼ã‚²ãƒƒãƒˆãƒ–ランãƒã¨ç«¶åˆã™ã‚‹å¤‰æ›´ã‚’å«ã‚“ã§ã„ã¾ã™ã€‚
+pulls.is_checking=マージã®ç«¶åˆã‚’確èªã—ã¦ã„ã¾ã™...
pulls.is_ancestor=ã“ã®ãƒ–ランãƒã¯æ—¢ã«ã‚¿ãƒ¼ã‚²ãƒƒãƒˆãƒ–ランãƒã«å«ã¾ã‚Œã¦ã„ã¾ã™ã€‚マージã™ã‚‹ã‚‚ã®ã¯ã‚りã¾ã›ã‚“。
pulls.is_empty=ã“ã®ãƒ–ランãƒã®å¤‰æ›´ã¯æ—¢ã«ã‚¿ãƒ¼ã‚²ãƒƒãƒˆãƒ–ランãƒã«ã‚りã¾ã™ã€‚ã“れã¯ç©ºã®ã‚³ãƒŸãƒƒãƒˆã«ãªã‚Šã¾ã™ã€‚
pulls.required_status_check_failed=ã„ãã¤ã‹ã®å¿…è¦ãªã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ãƒã‚§ãƒƒã‚¯ãŒæˆåŠŸã—ã¦ã„ã¾ã›ã‚“。
@@ -1952,7 +1960,6 @@ pulls.cmd_instruction_checkout_title=ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ
pulls.cmd_instruction_checkout_desc=プロジェクトリãƒã‚¸ãƒˆãƒªã‹ã‚‰æ–°ã—ã„ブランãƒã‚’ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã—ã€å¤‰æ›´å†…容をテストã—ã¾ã™ã€‚
pulls.cmd_instruction_merge_title=マージ
pulls.cmd_instruction_merge_desc=変更内容をマージã—ã¦ã€Giteaã«å映ã—ã¾ã™ã€‚
-pulls.cmd_instruction_merge_warning=警告: 「手動マージã®è‡ªå‹•検出ã€ãŒæœ‰åйã§ã¯ãªã„ãŸã‚ã€ã“ã®æ“作ã§ã¯ãƒ—ルリクエストをマージã§ãã¾ã›ã‚“
pulls.clear_merge_message=マージメッセージをクリア
pulls.clear_merge_message_hint=マージメッセージã®ã‚¯ãƒªã‚¢ã¯ã€ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®é™¤åŽ»ã ã‘を行ã„ã¾ã™ã€‚ 生æˆã•れãŸGitトレーラー("Co-Authored-By …" ç­‰)ã¯ãã®ã¾ã¾æ®‹ã‚Šã¾ã™ã€‚
@@ -2366,7 +2373,6 @@ settings.event_repository=リãƒã‚¸ãƒˆãƒª
settings.event_repository_desc=リãƒã‚¸ãƒˆãƒªãŒä½œæˆãƒ»å‰Šé™¤ã•れãŸã¨ã。
settings.event_header_issue=イシューã®ã‚¤ãƒ™ãƒ³ãƒˆ
settings.event_issues=イシュー
-settings.event_issues_desc=イシューãŒã‚ªãƒ¼ãƒ—ン・クローズ・å†ã‚ªãƒ¼ãƒ—ン・編集ã•れãŸã¨ã。
settings.event_issue_assign=イシューã®ã‚¢ã‚µã‚¤ãƒ³
settings.event_issue_assign_desc=ã‚¤ã‚·ãƒ¥ãƒ¼ã®æ‹…当者ãŒå‰²ã‚Šå½“ã¦ã‚‰ã‚ŒãŸã¨ãã€è§£é™¤ã•れãŸã¨ã。
settings.event_issue_label=イシューã®ãƒ©ãƒ™ãƒ«
@@ -2377,7 +2383,6 @@ settings.event_issue_comment=イシューã¸ã®ã‚³ãƒ¡ãƒ³ãƒˆ
settings.event_issue_comment_desc=イシューã¸ã®ã‚³ãƒ¡ãƒ³ãƒˆãŒä½œæˆãƒ»ç·¨é›†ãƒ»å‰Šé™¤ã•れãŸã¨ã。
settings.event_header_pull_request=プルリクエストã®ã‚¤ãƒ™ãƒ³ãƒˆ
settings.event_pull_request=プルリクエスト
-settings.event_pull_request_desc=プルリクエストãŒã‚ªãƒ¼ãƒ—ン・クローズ・å†ã‚ªãƒ¼ãƒ—ン・編集ã•れãŸã¨ã。
settings.event_pull_request_assign=プルリクエストã®ã‚¢ã‚µã‚¤ãƒ³
settings.event_pull_request_assign_desc=ãƒ—ãƒ«ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®æ‹…当者ãŒå‰²ã‚Šå½“ã¦ãƒ»è§£é™¤ã•れãŸã¨ã。
settings.event_pull_request_label=プルリクエストã®ãƒ©ãƒ™ãƒ«
@@ -2395,6 +2400,8 @@ settings.event_pull_request_review_request_desc=プルリクエストã®ãƒ¬ãƒ“ãƒ
settings.event_pull_request_approvals=ãƒ—ãƒ«ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®æ‰¿èª
settings.event_pull_request_merge=プルリクエストã®ãƒžãƒ¼ã‚¸
settings.event_header_workflow=ワークフローイベント
+settings.event_workflow_run=ワークフロー実行
+settings.event_workflow_run_desc=Gitea Actions ã®ãƒ¯ãƒ¼ã‚¯ãƒ•ロー実行ãŒã€ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã€å¾…機中ã€å®Ÿè¡Œä¸­ã€å®Œäº†ã«ãªã£ãŸã¨ã。
settings.event_workflow_job=ワークフロージョブ
settings.event_workflow_job_desc=Gitea Actions ã®ãƒ¯ãƒ¼ã‚¯ãƒ•ロージョブãŒã€ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã€å¾…機中ã€å®Ÿè¡Œä¸­ã€å®Œäº†ã«ãªã£ãŸã¨ã。
settings.event_package=パッケージ
@@ -2802,6 +2809,7 @@ team_permission_desc=権é™
team_unit_desc=リãƒã‚¸ãƒˆãƒªã®ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’許å¯
team_unit_disabled=(無効)
+form.name_been_taken=組織å "%s" ã¯æ—¢ã«ä½¿ç”¨ã•れã¦ã„ã¾ã™ã€‚
form.name_reserved=組織å "%s" ã¯äºˆç´„ã•れã¦ã„ã¾ã™ã€‚
form.name_pattern_not_allowed=`"%s" ã®å½¢å¼ã¯çµ„ç¹”åã«ä½¿ç”¨ã§ãã¾ã›ã‚“。`
form.create_org_not_allowed=組織を作æˆã™ã‚‹æ¨©é™ãŒã‚りã¾ã›ã‚“。
@@ -2823,15 +2831,28 @@ settings.visibility.private_shortname=プライベート
settings.update_settings=è¨­å®šã®æ›´æ–°
settings.update_setting_success=組織ã®è¨­å®šã‚’æ›´æ–°ã—ã¾ã—ãŸã€‚
-settings.change_orgname_prompt=注æ„: 組織åを変更ã™ã‚‹ã¨çµ„ç¹”ã®URLも変更ã•れã€å¤ã„åå‰ã¯è§£æ”¾ã•れã¾ã™ã€‚
-settings.change_orgname_redirect_prompt=å¤ã„åå‰ã¯ã€å†ä½¿ç”¨ã•れã¦ã„ãªã„é™ã‚Šãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã—ã¾ã™ã€‚
+
+settings.rename=組織åã®å¤‰æ›´
+settings.rename_desc=組織åを変更ã™ã‚‹ã¨çµ„ç¹”ã®URLも変更ã•れã€å¤ã„åå‰ã¯è§£æ”¾ã•れã¾ã™ã€‚
+settings.rename_success=組織 %[1]s ã® %[2]s ã¸ã®æ”¹åã«æˆåŠŸã—ã¾ã—ãŸã€‚
+settings.rename_no_change=組織åã®å¤‰æ›´ã¯ã‚りã¾ã›ã‚“。
+settings.rename_new_org_name=æ–°ã—ã„組織å
+settings.rename_failed=内部エラーã®ãŸã‚組織åを変更ã§ãã¾ã›ã‚“ã§ã—ãŸ
+settings.rename_notices_1=ã“ã®æ“作ã¯<strong>å…ƒã«æˆ»ã›ã¾ã›ã‚“</strong> 。
+settings.rename_notices_2=å¤ã„åå‰ã¯ã€å†ä½¿ç”¨ã•れるã¾ã§ã¯ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã—ã¾ã™ã€‚
+
settings.update_avatar_success=組織ã®ã‚¢ãƒã‚¿ãƒ¼ã‚’æ›´æ–°ã—ã¾ã—ãŸã€‚
settings.delete=組織を削除
settings.delete_account=ã“ã®çµ„織を削除
settings.delete_prompt=çµ„ç¹”ã¯æ’ä¹…çš„ã«å‰Šé™¤ã•れã¾ã™ã€‚ å…ƒã«æˆ»ã™ã“ã¨ã¯<strong>ã§ãã¾ã›ã‚“</strong>ï¼
+settings.name_confirm=確èªã®ãŸã‚組織åを入力:
+settings.delete_notices_1=ã“ã®æ“作ã¯<strong>å…ƒã«æˆ»ã›ã¾ã›ã‚“</strong> 。
+settings.delete_notices_2=ã“ã®æ“作ã«ã‚ˆã‚Šã€<strong>%s</strong>ã®ã™ã¹ã¦ã®<strong>リãƒã‚¸ãƒˆãƒª</strong>ãŒæ’ä¹…çš„ã«å‰Šé™¤ã•れã¾ã™ã€‚ コードã€ã‚¤ã‚·ãƒ¥ãƒ¼ã€ã‚³ãƒ¡ãƒ³ãƒˆã€Wikiデータã€å…±åŒä½œæ¥­è€…ã®è¨­å®šã‚‚å«ã¾ã‚Œã¾ã™ã€‚
+settings.delete_notices_3=ã“ã®æ“作ã«ã‚ˆã‚Šã€<strong>%s</strong>ã®ã™ã¹ã¦ã®<strong>パッケージ</strong>ãŒæ’ä¹…çš„ã«å‰Šé™¤ã•れã¾ã™ã€‚
+settings.delete_notices_4=ã“ã®æ“作ã«ã‚ˆã‚Šã€<strong>%s</strong>ã®ã™ã¹ã¦ã®<strong>プロジェクト</strong>ãŒæ’ä¹…çš„ã«å‰Šé™¤ã•れã¾ã™ã€‚
settings.confirm_delete_account=削除を確èª
-settings.delete_org_title=組織ã®å‰Šé™¤
-settings.delete_org_desc=組織をæ’ä¹…çš„ã«å‰Šé™¤ã—ã¾ã™ã€‚ 続行ã—ã¾ã™ã‹ï¼Ÿ
+settings.delete_failed=内部エラーã®ãŸã‚組織を削除ã§ãã¾ã›ã‚“ã§ã—ãŸ
+settings.delete_successful=組織ã®<b>%s</b>ã®å‰Šé™¤ã«æˆåŠŸã—ã¾ã—ãŸã€‚
settings.hooks_desc=ã“ã®çµ„ç¹”ã®<strong>ã™ã¹ã¦ã®ãƒªãƒã‚¸ãƒˆãƒª</strong>ã§ãƒˆãƒªã‚¬ãƒ¼ã•れるWebhookを追加ã—ã¾ã™ã€‚
settings.labels_desc=ã“ã®çµ„ç¹”ã®<strong>ã™ã¹ã¦ã®ãƒªãƒã‚¸ãƒˆãƒª</strong>ã§ä½¿ç”¨å¯èƒ½ãªã‚¤ã‚·ãƒ¥ãƒ¼ãƒ©ãƒ™ãƒ«ã‚’追加ã—ã¾ã™ã€‚
@@ -3718,13 +3739,18 @@ owner.settings.chef.keypair.description=Chefレジストリã®èªè¨¼ã«ã¯ã‚­ãƒ¼
secrets=シークレット
description=シークレットã¯ç‰¹å®šã®Actionsã«æ¸¡ã•れã¾ã™ã€‚ ãれ以外ã§èª­ã¿å‡ºã•れるã“ã¨ã¯ã‚りã¾ã›ã‚“。
none=シークレットã¯ã¾ã ã‚りã¾ã›ã‚“。
-creation=シークレットを追加
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=説明
creation.name_placeholder=å¤§æ–‡å­—å°æ–‡å­—ã®åŒºåˆ¥ãªã—ã€è‹±æ•°å­—ã¨ã‚¢ãƒ³ãƒ€ãƒ¼ã‚¹ã‚³ã‚¢ã®ã¿ã€GITEA_ ã‚„ GITHUB_ ã§å§‹ã¾ã‚‹ã‚‚ã®ã¯ä¸å¯
creation.value_placeholder=内容を入力ã—ã¦ãã ã•ã„。å‰å¾Œã®ç©ºç™½ã¯é™¤åŽ»ã•れã¾ã™ã€‚
creation.description_placeholder=ç°¡å˜ãªèª¬æ˜Žã‚’入力ã—ã¦ãã ã•ã„。 (オプション)
-creation.success=シークレット "%s" を追加ã—ã¾ã—ãŸã€‚
-creation.failed=シークレットã®è¿½åŠ ã«å¤±æ•—ã—ã¾ã—ãŸã€‚
+
+save_success=シークレット "%s" ã‚’ä¿å­˜ã—ã¾ã—ãŸã€‚
+save_failed=シークレットã®ä¿å­˜ã«å¤±æ•—ã—ã¾ã—ãŸã€‚
+
+add_secret=シークレットを追加
+edit_secret=シークレットを編集
deletion=シークレットã®å‰Šé™¤
deletion.description=シークレットã®å‰Šé™¤ã¯æ’ä¹…çš„ã§å…ƒã«æˆ»ã™ã“ã¨ã¯ã§ãã¾ã›ã‚“。 続行ã—ã¾ã™ã‹ï¼Ÿ
deletion.success=シークレットを削除ã—ã¾ã—ãŸã€‚
@@ -3802,6 +3828,10 @@ runs.no_workflows.documentation=Gitea Actions ã®è©³ç´°ã«ã¤ã„ã¦ã¯ã€<a targ
runs.no_runs=ワークフローã¯ã¾ã å®Ÿè¡Œã•れã¦ã„ã¾ã›ã‚“。
runs.empty_commit_message=(空ã®ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸)
runs.expire_log_message=ログã¯å¤ã™ãŽã‚‹ãŸã‚消去ã•れã¦ã„ã¾ã™ã€‚
+runs.delete=ワークフローã®å®Ÿè¡Œã‚’削除
+runs.delete.description=ã“ã®ãƒ¯ãƒ¼ã‚¯ãƒ•ローを完全ã«å‰Šé™¤ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿã“ã®æ“作ã¯å…ƒã«æˆ»ã›ã¾ã›ã‚“。
+runs.not_done=ã“ã®ãƒ¯ãƒ¼ã‚¯ãƒ•ローã®å®Ÿè¡Œã¯å®Œäº†ã—ã¦ã„ã¾ã›ã‚“。
+runs.view_workflow_file=ワークフローファイルを表示
workflow.disable=ワークフローを無効ã«ã™ã‚‹
workflow.disable_success=ワークフロー '%s' ãŒç„¡åйã«ãªã‚Šã¾ã—ãŸã€‚
@@ -3841,6 +3871,8 @@ deleted.display_name=削除ã•れãŸãƒ—ロジェクト
type-1.display_name=個人プロジェクト
type-2.display_name=リãƒã‚¸ãƒˆãƒª プロジェクト
type-3.display_name=組織プロジェクト
+enter_fullscreen=フルスクリーン
+exit_fullscreen=フルスクリーンを終了
[git.filemode]
changed_filemode=%[1]s → %[2]s
diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini
index 08f6d723de..6f36ad905c 100644
--- a/options/locale/locale_ko-KR.ini
+++ b/options/locale/locale_ko-KR.ini
@@ -648,6 +648,7 @@ editor.filename_cannot_be_empty=파ì¼ëª…ì´ ë¹ˆì¹¸ìž…ë‹ˆë‹¤.
editor.no_changes_to_show=표시할 ë³€ê²½ì‚¬í•­ì´ ì—†ìŠµë‹ˆë‹¤.
editor.add_subdir=경로 추가...
+
commits.desc=소스 코드 변경 ë‚´ì—­ íƒìƒ‰
commits.commits=커밋
commits.search_all=모든 브랜치
@@ -1151,12 +1152,12 @@ settings.visibility.private_shortname=비공개
settings.update_settings=설정 ì—…ë°ì´íЏ
settings.update_setting_success=ì¡°ì§ ì„¤ì •ì´ ë³€ê²½ë˜ì—ˆìŠµë‹ˆë‹¤.
+
+
settings.update_avatar_success=ì¡°ì§ì˜ 아바타가 갱신ë˜ì—ˆìŠµë‹ˆë‹¤.
settings.delete=ì¡°ì§ ì‚­ì œ
settings.delete_account=ì´ ì¡°ì§ì„ 삭제합니다.
settings.confirm_delete_account=ì‚­ì œ 승ì¸
-settings.delete_org_title=ì¡°ì§ ì‚­ì œ
-settings.delete_org_desc=ì´ ì¡°ì§ì´ ì˜êµ¬ížˆ ì‚­ì œë©ë‹ˆë‹¤. ê³„ì† í•˜ì‹œê² ìŠµë‹ˆê¹Œ?
members.membership_visibility=íšŒì› í‘œì‹œ:
@@ -1542,8 +1543,12 @@ conan.details.repository=저장소
owner.settings.cleanuprules.enabled=활성화ë¨
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=설명
+
+
[actions]
diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini
index 718ca0594e..5df5ec35d3 100644
--- a/options/locale/locale_lv-LV.ini
+++ b/options/locale/locale_lv-LV.ini
@@ -1196,7 +1196,6 @@ editor.update=Atjaunot %s
editor.delete=Dzēst %s
editor.patch=Pielietot ielÄpu
editor.patching=Pielieto ielÄpu:
-editor.fail_to_apply_patch=`NeizdevÄs pielietot ielÄpu "%s"`
editor.new_patch=Jauns ielÄps
editor.commit_message_desc=Pievienot neobligÄtu paplaÅ¡inÄtu aprakstu…
editor.signoff_desc=Pievienot revÄ«zijas žurnÄla ziņojuma beigÄs Signed-off-by ar revÄ«zijas autoru.
@@ -1214,15 +1213,11 @@ editor.branch_already_exists=Atzars "%s" Å¡ajÄ repozitorijÄ jau eksistÄ“.
editor.directory_is_a_file=Direktorijas nosaukums "%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
editor.filename_is_a_directory=Faila nosaukums "%s" sakrÄ«t ar direktorijas nosaukumu Å¡ajÄ repozitorijÄ.
-editor.file_editing_no_longer_exists=Fails "%s", ko labojat, vairs neeksistÄ“ Å¡ajÄ repozitorijÄ.
-editor.file_deleting_no_longer_exists=Fails "%s", ko dzēšat, vairs neeksistÄ“ Å¡ajÄ repozitorijÄ.
editor.file_changed_while_editing=Faila saturs ir mainÄ«jies kopÅ¡ sÄkÄt to labot. Noklikšķiniet <a target="_blank" rel="noopener noreferrer" href="%s">Å¡eit</a>, lai apskatÄ«tu, vai <strong>NosÅ«tiet izmaiņas atkÄrtoti</strong>, lai pÄrrakstÄ«tu.
editor.file_already_exists=Fails ar nosaukumu "%s" Å¡ajÄ repozitorijÄ jau eksistÄ“.
editor.commit_empty_file_header=Iesūtīt tukšu failu
editor.commit_empty_file_text=Fails, ko vÄ“laties iesÅ«tÄ«t, ir tukÅ¡s. Vai turpinÄt?
editor.no_changes_to_show=Nav izmaiņu, ko rÄdÄ«t.
-editor.fail_to_update_file=NeizdevÄs atjaunot/izveidot failu "%s".
-editor.fail_to_update_file_summary=Kļūdas ziņojums:
editor.push_rejected_no_message=Izmaiņu iesÅ«tīšana tika noraidÄ«ta, bet serveris neatgrieza paziņojumu. PÄrbaudiet git ÄÄ·us Å¡im repozitorijam.
editor.push_rejected=Serveris noraidÄ«ja Å¡o izmaiņu. PÄrbaudiet git ÄÄ·us.
editor.push_rejected_summary=Pilns noraidīšanas ziņojums:
@@ -1237,6 +1232,7 @@ editor.require_signed_commit=AtzarÄ var iesÅ«tÄ«t tikai parakstÄ«tas revÄ«zijas
editor.cherry_pick=Izlasīt %s uz:
editor.revert=Atgriezt %s uz:
+
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.
@@ -2123,7 +2119,6 @@ settings.event_repository=Repozitorijs
settings.event_repository_desc=Repozitorijs izveidots vai dzēsts.
settings.event_header_issue=Problēmu notikumi
settings.event_issues=Problēmas
-settings.event_issues_desc=ProblÄ“ma atvÄ“rta, aizvÄ“rta, atkÄrtoti atvÄ“rta vai mainÄ«ta.
settings.event_issue_assign=Problēmas atbildīgie
settings.event_issue_assign_desc=Problēmai piešķirti vai noņemti atbildīgie.
settings.event_issue_label=Problēmu iezīmes
@@ -2134,7 +2129,6 @@ settings.event_issue_comment=ProblÄ“mas komentÄrs
settings.event_issue_comment_desc=ProblÄ“mas komentÄrs pievienots, labots vai dzÄ“sts.
settings.event_header_pull_request=Izmaiņu pieprasījuma notikumi
settings.event_pull_request=Izmaiņu pieprasījums
-settings.event_pull_request_desc=Izmaiņu pieprasÄ«jums atvÄ“rts, aizvÄ“rts, atkÄrtoti atvÄ“rts vai mainÄ«ts.
settings.event_pull_request_assign=Izmaiņu pieprasījuma atbildīgie
settings.event_pull_request_assign_desc=Izmaiņu pieprasījumam piešķirti vai noņemti atbildīgie.
settings.event_pull_request_label=Izmaiņu pieprasījuma iezīmes
@@ -2509,15 +2503,13 @@ settings.visibility.private_shortname=PrivÄta
settings.update_settings=Mainīt iestatījumus
settings.update_setting_success=OrganizÄcijas iestatÄ«jumi tika saglabÄti.
-settings.change_orgname_prompt=PiezÄ«me: organizÄcijas nosaukuma maiņa izmainÄ«s arÄ« organizÄcijas URL un atbrÄ«vos veco nosaukumu.
-settings.change_orgname_redirect_prompt=Vecais vÄrds pÄrsÅ«tÄ«s uz jauno, kamÄ“r vien tas nebÅ«s izmantots.
+
+
settings.update_avatar_success=OrganizÄcijas attÄ“ls tika saglabÄts.
settings.delete=DzÄ“st organizÄciju
settings.delete_account=DzÄ“st Å¡o organizÄciju
settings.delete_prompt=Å Ä« darbÄ«ba pilnÄ«bÄ dzÄ“sÄ«s Å¡o organizÄciju, kÄ arÄ« tÄ ir <strong>NEATGRIEZENISKA</strong>!
settings.confirm_delete_account=ApstiprinÄt dzēšanu
-settings.delete_org_title=DzÄ“st organizÄciju
-settings.delete_org_desc=OrganizÄcija tiks dzÄ“sta neatgriezeniski. Vai turpinÄt?
settings.labels_desc=Pievienojiet iezÄ«mes, kas var tikt izmantotas <strong>visos</strong> šīs organizÄcijas repozitorijos.
@@ -3332,12 +3324,13 @@ owner.settings.chef.keypair.description=AtslÄ“gu pÄris ir nepiecieÅ¡ams, lai au
secrets=Noslēpumi
description=NoslÄ“pumi tiks padoti atsevišķÄm darbÄ«bÄm un citÄdi nevar tikt nolasÄ«ti.
none=PagaidÄm nav neviena noslÄ“puma.
-creation=Pievienot noslēpumu
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Apraksts
creation.name_placeholder=reÄ£istr-nejÅ«tÄ«gs, tikai burti, cipari un apakÅ¡svÄ«tras, nevar sÄkties ar GITEA_ vai GITHUB_
creation.value_placeholder=Ievadiet jebkÄdu saturu. Atstarpes sÄkumÄ un beigÄ tiks noņemtas.
-creation.success=Noslēpums "%s" tika pievienots.
-creation.failed=NeizdevÄs pievienot noslÄ“pumu.
+
+
deletion=Dzēst noslēpumu
deletion.description=NoslÄ“puma dzēšana ir neatgriezeniska. Vai turpinÄt?
deletion.success=Noslēpums tika izdzēsts.
diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini
index eff4c1f85f..460736cf7f 100644
--- a/options/locale/locale_nl-NL.ini
+++ b/options/locale/locale_nl-NL.ini
@@ -1012,7 +1012,6 @@ editor.file_changed_while_editing=De bestandsinhoud is veranderd sinds je bent b
editor.commit_empty_file_header=Commit een leeg bestand
editor.commit_empty_file_text=Het bestand dat u wilt committen is leeg. Doorgaan?
editor.no_changes_to_show=Er zijn geen wijzigingen om weer te geven.
-editor.fail_to_update_file_summary=Foutmelding:
editor.push_rejected_no_message=De wijziging is afgewezen door de server zonder bericht. Controleer de Git Hooks alsjeblieft.
editor.push_rejected=De wijziging is afgewezen door de server. Controleer Controleer de Git Hooks alsjeblieft.
editor.push_rejected_summary=Volledig afwijzingsbericht:
@@ -1023,6 +1022,7 @@ editor.require_signed_commit=Branch vereist een ondertekende commit
editor.cherry_pick=Cherry-pick %s op:
editor.revert=%s ongedaan maken op:
+
commits.desc=Bekijk de broncode-wijzigingsgeschiedenis.
commits.commits=Commits
commits.nothing_to_compare=Deze branches zijn gelijk.
@@ -1707,7 +1707,6 @@ settings.event_repository=Repository
settings.event_repository_desc=Repository gemaakt of verwijderd.
settings.event_header_issue=Issue gebeurtenissen
settings.event_issues=Kwesties
-settings.event_issues_desc=Issue geopend, gesloten, heropend of bewerkt.
settings.event_issue_assign=Probleem toegekend
settings.event_issue_assign_desc=Issue toegewezen of niet-toegewezen.
settings.event_issue_label=Issue gelabeld
@@ -1718,7 +1717,6 @@ settings.event_issue_comment=Issue commentaar
settings.event_issue_comment_desc=Issue reactie aangemaakt, bewerkt of verwijderd.
settings.event_header_pull_request=Pull Request Events
settings.event_pull_request=Pull request
-settings.event_pull_request_desc=Pull request geopend, gesloten, heropend of bewerkt.
settings.event_pull_request_assign=Pull request toegewezen
settings.event_pull_request_assign_desc=Pull request toegewezen of niet-toegewezen.
settings.event_pull_request_label=Pull-aanvraag gelabeld
@@ -1989,13 +1987,13 @@ settings.visibility.private=Privé (alleen zichtbaar voor organisatieleden)
settings.visibility.private_shortname=Privé
settings.update_settings=Instellingen bijwerken
+
+
settings.update_avatar_success=De avatar van de organisatie is aangepast.
settings.delete=Verwijder organisatie
settings.delete_account=Verwijder deze organisatie
settings.delete_prompt=Deze organisatie zal permanent worden verwijderd. U kunt dit <strong>NIET</strong> ongedaan maken!
settings.confirm_delete_account=Bevestig verwijdering
-settings.delete_org_title=Verwijder organisatie
-settings.delete_org_desc=Deze organisatie zal permanent verwijderd worden. Doorgaan?
settings.hooks_desc=Een webhook toevoegen die door <strong>alle repositories</strong> in deze organisatie getriggerd kan worden.
settings.labels_desc=Voeg labels toe die kunnen worden gebruikt bij problemen voor <strong>alle repositories</strong> in deze organisatie.
@@ -2515,8 +2513,12 @@ settings.link.button=Repository link bijwerken
owner.settings.cleanuprules.enabled=Ingeschakeld
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Omschrijving
+
+
[actions]
diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini
index b45f0fc8e0..d841658a9c 100644
--- a/options/locale/locale_pl-PL.ini
+++ b/options/locale/locale_pl-PL.ini
@@ -942,13 +942,13 @@ editor.file_changed_while_editing=Zawartość pliku zmieniła się, odkąd rozpo
editor.commit_empty_file_header=Commituj pusty plik
editor.commit_empty_file_text=Plik, który zamierzasz commitować, jest pusty. Kontynuować?
editor.no_changes_to_show=Brak zmian do pokazania.
-editor.fail_to_update_file_summary=Komunikat błędu:
editor.push_rejected_summary=Pełny komunikat odrzucenia:
editor.add_subdir=Dodaj katalog…
editor.no_commit_to_branch=Zatwierdzanie bezpośrednio do tej gałęzi nie jest możliwe, ponieważ:
editor.user_no_push_to_branch=Użytkownik nie może wypychać do gałęzi
editor.require_signed_commit=Gałąź wymaga podpisanych commitów
+
commits.desc=Przeglądaj historię zmian kodu źródłowego.
commits.commits=Commity
commits.search_all=Wszystkie gałęzie
@@ -1594,7 +1594,6 @@ settings.event_repository=Repozytorium
settings.event_repository_desc=Repozytorium stworzone lub usunięte.
settings.event_header_issue=Zdarzenia zgłoszeń
settings.event_issues=Zgłoszenia
-settings.event_issues_desc=Zgłoszenie otwarte, zamknięte, ponownie otwarte lub zmodyfikowane.
settings.event_issue_assign=Zgłoszenie przypisane
settings.event_issue_assign_desc=Zgłoszenie przypisane bądź nieprzypisane.
settings.event_issue_label=Zgłoszenie oznaczone
@@ -1605,7 +1604,6 @@ settings.event_issue_comment=Komentarz w zgłoszeniu
settings.event_issue_comment_desc=Komentarz w zgłoszeniu stworzony, edytowany lub usunięty.
settings.event_header_pull_request=Zdarzenia Pull Requestów
settings.event_pull_request=Pull Request
-settings.event_pull_request_desc=Pull request otwarty, zamknięty, ponownie otwarty lub zmodyfikowany.
settings.event_pull_request_assign=Pull Request przypisany
settings.event_pull_request_assign_desc=Pull Request przypisany bÄ…dz nieprzypisany.
settings.event_pull_request_label=Pull Request zaetykietowany
@@ -1862,14 +1860,13 @@ settings.visibility.private_shortname=Prywatny
settings.update_settings=Aktualizuj ustawienia
settings.update_setting_success=Ustawienia organizacji zostały zaktualizowane.
-settings.change_orgname_redirect_prompt=Stara nazwa będzie przekierowywała dopóki ktoś jej nie zajmie.
+
+
settings.update_avatar_success=Awatar organizacji został zaktualizowany.
settings.delete=Usuń organizację
settings.delete_account=Usuń tą organizację
settings.delete_prompt=Organizacja zostanie trwale usunięta. Tej akcji <strong>NIE MOŻNA</strong> cofnąć!
settings.confirm_delete_account=Potwierdź usunięcie
-settings.delete_org_title=Usuń organizację
-settings.delete_org_desc=Ta organizacja zostanie trwale usunięta. Kontynuować?
settings.hooks_desc=Dodaj webhooki, uruchamiane dla <strong>wszystkich repozytoriów</strong> w tej organizacji.
settings.labels_desc=Dodaj etykiety, które mogą być używane w zgłoszeniach dla <strong>wszystkich repozytoriów</strong> w tej organizacji.
@@ -2405,8 +2402,12 @@ conan.details.repository=Repozytorium
owner.settings.cleanuprules.enabled=Włączone
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Opis
+
+
[actions]
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 75d425417c..32e007577b 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -1191,7 +1191,6 @@ editor.update=Atualizar %s
editor.delete=Excluir %s
editor.patch=Aplicar Correção
editor.patching=Corrigindo:
-editor.fail_to_apply_patch=`Não foi possível aplicar a correção "%s"`
editor.new_patch=Nova correção
editor.commit_message_desc=Adicione uma descrição detalhada (opcional)...
editor.signoff_desc=Adicione um assinado-por-committer no final do log do commit.
@@ -1209,15 +1208,11 @@ editor.branch_already_exists=Branch "%s" já existe neste repositório.
editor.directory_is_a_file=O nome do diretório "%s" já é usado como um nome de arquivo neste repositório.
editor.file_is_a_symlink=`"%s" é um link simbólico. Links simbólicos não podem ser editados no editor da web`
editor.filename_is_a_directory=O nome do arquivo "%s" já é usado como um nome de diretório neste repositório.
-editor.file_editing_no_longer_exists=O arquivo que está sendo editado, "%s", não existe mais neste repositório.
-editor.file_deleting_no_longer_exists=O arquivo a ser excluído, "%s", não existe mais neste repositório.
editor.file_changed_while_editing=O conteúdo do arquivo mudou desde que você começou a editar. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver o que foi editado ou <strong>clique em Aplicar commit das alterações novamemente</strong> para sobreescrever estas alterações.
editor.file_already_exists=Um arquivo com nome "%s" já existe neste repositório.
editor.commit_empty_file_header=Fazer commit de um arquivo vazio
editor.commit_empty_file_text=O arquivo que você está prestes fazer commit está vazio. Continuar?
editor.no_changes_to_show=Nenhuma alteração a mostrar.
-editor.fail_to_update_file=Falha ao atualizar/criar arquivo "%s".
-editor.fail_to_update_file_summary=Mensagem de erro:
editor.push_rejected_no_message=A alteração foi rejeitada pelo servidor sem uma mensagem. Por favor, verifique os Hooks Git.
editor.push_rejected=A alteração foi rejeitada pelo servidor. Por favor, verifique os Hooks Git.
editor.push_rejected_summary=Mensagem completa de rejeição:
@@ -1232,6 +1227,7 @@ editor.require_signed_commit=Branch requer um commit assinado
editor.cherry_pick=Cherry-pick %s para:
editor.revert=Reverter %s para:
+
commits.desc=Veja o histórico de alterações do código de fonte.
commits.commits=Commits
commits.no_commits=Nenhum commit em comum. "%s" e "%s" tem históricos completamente diferentes.
@@ -2104,7 +2100,6 @@ settings.event_repository=Repositório
settings.event_repository_desc=Repositório criado ou excluído.
settings.event_header_issue=Eventos da Issue
settings.event_issues=Issues
-settings.event_issues_desc=Issue aberta, fechada, reaberta ou editada.
settings.event_issue_assign=Issue Atribuída
settings.event_issue_assign_desc=Issue atribuída ou não atribuída.
settings.event_issue_label=Issue Rotulada
@@ -2115,7 +2110,6 @@ settings.event_issue_comment=Comentário da issue
settings.event_issue_comment_desc=Comentário da issue criado, editado ou excluído.
settings.event_header_pull_request=Eventos de Pull Request
settings.event_pull_request=Pull request
-settings.event_pull_request_desc=Pull request aberto, fechado, reaberto ou editado.
settings.event_pull_request_assign=Pull Request Atribuído
settings.event_pull_request_assign_desc=Pull request atribuído ou desatribuído.
settings.event_pull_request_label=Pull Request Rotulado
@@ -2468,14 +2462,13 @@ settings.visibility.private_shortname=Privado
settings.update_settings=Atualizar Configurações
settings.update_setting_success=Configurações da organização foram atualizadas.
-settings.change_orgname_redirect_prompt=O nome antigo irá redirecionar até que seja reivindicado.
+
+
settings.update_avatar_success=O avatar da organização foi atualizado.
settings.delete=Excluir organização
settings.delete_account=Excluir esta organização
settings.delete_prompt=A organização será excluída permanentemente. Isto <strong>NÃO PODERÃ</strong> ser desfeito!
settings.confirm_delete_account=Confirmar exclusão
-settings.delete_org_title=Excluir organização
-settings.delete_org_desc=Essa organização será excluída permanentemente. Continuar?
settings.hooks_desc=Adicionar Webhooks que serão acionados para <strong>todos os repositórios</strong> desta organização.
settings.labels_desc=Adicionar rótulos que possam ser usadas em issues para <strong>todos os repositórios</strong> desta organização.
@@ -3269,12 +3262,13 @@ owner.settings.chef.keypair=Gerar par de chaves
secrets=Segredos
description=Os segredos serão passados a certas ações e não poderão ser lidos de outra forma.
none=Não há segredos ainda.
-creation=Adicionar Segredo
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Descrição
creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_
creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos.
-creation.success=O segredo "%s" foi adicionado.
-creation.failed=Falha ao adicionar segredo.
+
+
deletion=Excluir segredo
deletion.description=A exclusão de um segredo é permanente e não pode ser desfeita. Continuar?
deletion.success=O segredo foi excluído.
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index 00fa30e2e1..740de78809 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -421,6 +421,7 @@ remember_me.compromised=O identificador da sessão já não é válido, o que po
forgot_password_title=Esqueci-me da senha
forgot_password=Esqueceu a sua senha?
need_account=Precisa de uma conta?
+sign_up_tip=Você está a registar a primeira conta no sistema, que tem privilégios de administrador. Guarde cuidadosamente o nome de utilizador e a senha. Se se esquecer do nome de utilizador ou da senha, consulte a documentação do Gitea para recuperar a conta.
sign_up_now=Inscreva-se agora.
sign_up_successful=A conta foi criada com sucesso. Bem-vindo/a!
confirmation_mail_sent_prompt_ex=Foi enviado um email de confirmação para <b>%s</b>. Verifique a sua caixa de entrada dentro de %s para completar o processo de registo. Se o seu endereço de email de registo estiver errado, pode iniciar a sessão novamente e mudá-lo.
@@ -1228,6 +1229,7 @@ migrate.migrating_issues=Migrando questões
migrate.migrating_pulls=Migrando pedidos de integração
migrate.cancel_migrating_title=Cancelar migração
migrate.cancel_migrating_confirm=Quer cancelar esta migração?
+migration_status=Estado da migração
mirror_from=réplica de
forked_from=derivado de
@@ -1331,7 +1333,9 @@ editor.upload_file=Carregar ficheiro
editor.edit_file=Editar ficheiro
editor.preview_changes=Pré-visualizar modificações
editor.cannot_edit_lfs_files=Ficheiros LFS não podem ser editados na interface web.
+editor.cannot_edit_too_large_file=O ficheiro é demasiado grande para ser editado.
editor.cannot_edit_non_text_files=Ficheiros binários não podem ser editados na interface da web.
+editor.file_not_editable_hint=Mas ainda pode renomeá-lo ou movê-lo.
editor.edit_this_file=Editar ficheiro
editor.this_file_locked=Ficheiro bloqueado
editor.must_be_on_a_branch=Tem que estar num ramo para fazer ou propor modificações neste ficheiro.
@@ -1351,7 +1355,7 @@ editor.update=Modificar %s
editor.delete=Eliminar %s
editor.patch=Aplicar remendo (patch)
editor.patching=Remendando (patching):
-editor.fail_to_apply_patch=`Não foi possível aplicar o remendo (patch) "%s"`
+editor.fail_to_apply_patch=Não foi possível aplicar o remendo (patch)
editor.new_patch=Novo remendo (patch)
editor.commit_message_desc=Adicionar uma descrição alargada opcional…
editor.signoff_desc=Adicionar "Assinado-por" seguido do autor do cometimento no fim da mensagem do registo de cometimentos.
@@ -1371,8 +1375,7 @@ editor.branch_already_exists=O ramo "%s" já existe neste repositório.
editor.directory_is_a_file=O nome da pasta "%s" já é usado como um nome de ficheiro neste repositório.
editor.file_is_a_symlink=`"%s" é uma ligação simbólica. Ligações simbólicas não podem ser editadas no editor web`
editor.filename_is_a_directory=O nome de ficheiro "%s" já está a ser usado como um nome de pasta neste repositório.
-editor.file_editing_no_longer_exists=O ficheiro que está a ser editado, "%s", já não existe neste repositório.
-editor.file_deleting_no_longer_exists=O ficheiro que está a ser eliminado, "%s", já não existe neste repositório.
+editor.file_modifying_no_longer_exists=O ficheiro que está a ser modificado, "%s", já não existe neste repositório.
editor.file_changed_while_editing=O conteúdo do ficheiro mudou desde que começou a editar. <a target="_blank" rel="noopener noreferrer" href="%s">Clique aqui</a> para ver as modificações ou clique em <strong>Cometer novamente</strong> para escrever por cima.
editor.file_already_exists=Já existe um ficheiro com o nome "%s" neste repositório.
editor.commit_id_not_matching=O ID do cometimento não corresponde ao ID de quando começou a editar. Faça o cometimento para um ramo de remendo (patch) e depois faça a integração.
@@ -1380,8 +1383,6 @@ editor.push_out_of_date=O envio parece estar obsoleto.
editor.commit_empty_file_header=Cometer um ficheiro vazio
editor.commit_empty_file_text=O ficheiro que está prestes a cometer está vazio. Quer continuar?
editor.no_changes_to_show=Não existem modificações para mostrar.
-editor.fail_to_update_file=Falhou ao modificar/criar o ficheiro "%s".
-editor.fail_to_update_file_summary=Mensagem de erro:
editor.push_rejected_no_message=A modificação foi rejeitada pelo servidor sem qualquer mensagem. Verifique os Automatismos do Git.
editor.push_rejected=A modificação foi rejeitada pelo servidor. Verifique os Automatismos do Git.
editor.push_rejected_summary=Mensagem completa de rejeição:
@@ -1395,6 +1396,15 @@ editor.user_no_push_to_branch=O utilizador não pode enviar para o ramo
editor.require_signed_commit=O ramo requer um cometimento assinado
editor.cherry_pick=Escolher a dedo %s para:
editor.revert=Reverter %s para:
+editor.failed_to_commit=Falhou ao cometer as modificações.
+editor.failed_to_commit_summary=Mensagem de erro:
+
+editor.fork_create=Faça uma derivação do repositório para propor modificações
+editor.fork_create_description=Não pode editar este repositório. Ao invés disso, crie uma derivação, faça as modificações nessa derivação e crie um pedido de integração.
+editor.fork_edit_description=Não pode editar este repositório. As modificações irão ser escritas na sua derivação <b>%s</b>, para que possa criar um pedido de integração.
+editor.fork_not_editable=Fez uma derivação deste repositório, mas a sua derivação não é editável.
+editor.fork_failed_to_push_branch=Falhou ao enviar o ramo %s para o seu repositório.
+editor.fork_branch_exists=O ramo "%s" já existe na sua derivação, escolha outro nome para o ramo.
commits.desc=Navegar pelo histórico de modificações no código fonte.
commits.commits=Cometimentos
@@ -1523,7 +1533,7 @@ issues.remove_labels=removeu os rótulos %s %s
issues.add_remove_labels=adicionou o(s) rótulo(s) %s e removeu %s %s
issues.add_milestone_at=`adicionou esta questão à etapa <b>%s</b> %s`
issues.add_project_at=`adicionou esta questão ao planeamento <b>%s</b> %s`
-issues.move_to_column_of_project=`isto foi movido para %s dentro de %s em %s`
+issues.move_to_column_of_project=`moveu isto para %s em %s %s`
issues.change_milestone_at=`modificou a etapa de <b>%s</b> para <b>%s</b> %s`
issues.change_project_at=`modificou o planeamento de <b>%s</b> para <b>%s</b> %s`
issues.remove_milestone_at=`removeu esta questão da etapa <b>%s</b> %s`
@@ -1552,13 +1562,14 @@ issues.filter_project=Planeamento
issues.filter_project_all=Todos os planeamentos
issues.filter_project_none=Nenhum planeamento
issues.filter_assignee=Encarregado
-issues.filter_assignee_no_assignee=Não atribuído
-issues.filter_assignee_any_assignee=Atribuído a qualquer pessoa
+issues.filter_assignee_no_assignee=Não atribuída
+issues.filter_assignee_any_assignee=Atribuída a alguém
issues.filter_poster=Autor(a)
issues.filter_user_placeholder=Procurar utilizadores
issues.filter_user_no_select=Todos os utilizadores
issues.filter_type=Tipo
issues.filter_type.all_issues=Todas as questões
+issues.filter_type.all_pull_requests=Todos os pedidos de integração
issues.filter_type.assigned_to_you=Atribuídas a si
issues.filter_type.created_by_you=Criadas por si
issues.filter_type.mentioning_you=Mencionando a si
@@ -1650,6 +1661,7 @@ issues.save=Guardar
issues.label_title=Nome do rótulo
issues.label_description=Descrição do rótulo
issues.label_color=Cor do rótulo
+issues.label_color_invalid=Cor inválida
issues.label_exclusive=Exclusivo
issues.label_archive=Arquivar rótulo
issues.label_archived_filter=Mostrar rótulos arquivados
@@ -1714,6 +1726,8 @@ issues.remove_time_estimate_at=removeu a estimativa de tempo %s
issues.time_estimate_invalid=O formato da estimativa de tempo é inválido
issues.start_tracking_history=começou a trabalhar %s
issues.tracker_auto_close=O cronómetro será parado automaticamente quando esta questão for fechada
+issues.stopwatch_already_stopped=O cronómetro para esta questão já está parado
+issues.stopwatch_already_created=O cronómetro para esta questão já existe
issues.tracking_already_started=`Você já iniciou a contagem de tempo <a href="%s">noutra questão</a>!`
issues.stop_tracking=Parar cronómetro
issues.stop_tracking_history=trabalhou durante <b>%[1]s</b> %[2]s
@@ -1955,7 +1969,7 @@ pulls.cmd_instruction_checkout_title=Checkout
pulls.cmd_instruction_checkout_desc=A partir do seu repositório, crie um novo ramo e teste nele as modificações.
pulls.cmd_instruction_merge_title=Integrar
pulls.cmd_instruction_merge_desc=Integrar as modificações e enviar para o Gitea.
-pulls.cmd_instruction_merge_warning=Aviso: Esta operação não pode executar pedidos de integração porque "auto-identificar integração manual" não estava habilitado
+pulls.cmd_instruction_merge_warning=Aviso: Esta operação não pode executar pedidos de integração porque a opção "auto-identificar integração manual" não está habilitada.
pulls.clear_merge_message=Apagar mensagem de integração
pulls.clear_merge_message_hint=Apagar a mensagem de integração apenas remove o conteúdo da mensagem de cometimento e mantém os rodapés do git, tais como "Co-Autorado-Por …".
@@ -2151,6 +2165,7 @@ settings.collaboration.write=Escrita
settings.collaboration.read=Leitura
settings.collaboration.owner=Proprietário(a)
settings.collaboration.undefined=Não definido
+settings.collaboration.per_unit=Permissões da Unidade
settings.hooks=Automatismos web
settings.githooks=Automatismos do Git
settings.basic_settings=Configurações básicas
@@ -2369,7 +2384,7 @@ settings.event_repository=Repositório
settings.event_repository_desc=Repositório criado ou eliminado.
settings.event_header_issue=Eventos da questão
settings.event_issues=Questões
-settings.event_issues_desc=Questão aberta, fechada, reaberta ou editada.
+settings.event_issues_desc=Questão aberta, fechada, reaberta, editada ou eliminada.
settings.event_issue_assign=Questão atribuída
settings.event_issue_assign_desc=Encarregado atribuído ou retirado à questão.
settings.event_issue_label=Questão com rótulo
@@ -2380,7 +2395,7 @@ settings.event_issue_comment=Comentário da questão
settings.event_issue_comment_desc=Comentário da questão criado, editado ou eliminado.
settings.event_header_pull_request=Eventos de pedidos de integração
settings.event_pull_request=Pedido de integração
-settings.event_pull_request_desc=Pedido de integração aberto, fechado, reaberto ou editado.
+settings.event_pull_request_desc=Pedido de integração aberto, fechado, reaberto, editado ou eliminado.
settings.event_pull_request_assign=Encarregado atribuído ao pedido de integração
settings.event_pull_request_assign_desc=Encarregado atribuído ou retirado ao pedido de integração.
settings.event_pull_request_label=Rótulo atribuído ao pedido de integração
@@ -2398,8 +2413,10 @@ settings.event_pull_request_review_request_desc=A revisão do pedido de integraÃ
settings.event_pull_request_approvals=Aprovações do pedido de integração
settings.event_pull_request_merge=Integração constante no pedido
settings.event_header_workflow=Eventos da sequência de trabalho
+settings.event_workflow_run=Execução da sequência de trabalho
+settings.event_workflow_run_desc=A execução da sequência de trabalho das operações do Gitea foi colocada em fila, está em espera, em andamento ou concluída.
settings.event_workflow_job=Trabalhos da sequência de trabalho
-settings.event_workflow_job_desc=O trabalho da sequência de trabalho das operações do Gitea foi colocado em fila, está em espera, em andamento ou concluída.
+settings.event_workflow_job_desc=O trabalho da sequência de trabalho das operações do Gitea foi colocado em fila, está em espera, em andamento ou concluído.
settings.event_package=Pacote
settings.event_package_desc=Pacote criado ou eliminado num repositório.
settings.branch_filter=Filtro de ramos
@@ -2752,6 +2769,8 @@ branch.new_branch_from=`Criar um novo ramo a partir do ramo "%s"`
branch.renamed=O ramo %s foi renomeado para %s.
branch.rename_default_or_protected_branch_error=Só os administradores é que podem renomear o ramo principal ou ramos protegidos.
branch.rename_protected_branch_failed=Este ramo está protegido por regras de salvaguarda baseadas em padrões glob.
+branch.commits_divergence_from=Divergência nos cometimentos: %[1]d atrás e %[2]d à frente de %[3]s
+branch.commits_no_divergence=Idêntico ao ramo %[1]s
tag.create_tag=Criar etiqueta %s
tag.create_tag_operation=Criar etiqueta
@@ -2765,6 +2784,7 @@ topic.done=Concluído
topic.count_prompt=Não pode escolher mais do que 25 tópicos
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') ou pontos ('.') e podem ter até 35 caracteres. As letras têm que ser minúsculas.
+find_file.follow_symlink=Seguir esta ligação simbólica para onde ela está apontando
find_file.go_to_file=Ir para o ficheiro
find_file.no_matching=Não foi encontrado qualquer ficheiro correspondente
@@ -2805,6 +2825,7 @@ team_permission_desc=Permissão
team_unit_desc=Permitir acesso às secções do repositório
team_unit_disabled=(desabilitada)
+form.name_been_taken=O nome da organização "%s" já foi tomado.
form.name_reserved=O nome de organização "%s" está reservado.
form.name_pattern_not_allowed=O padrão "%s" não é permitido no nome de uma organização.
form.create_org_not_allowed=Não tem permissão para criar uma organização.
@@ -2826,15 +2847,28 @@ settings.visibility.private_shortname=Privado
settings.update_settings=Modificar configurações
settings.update_setting_success=As configurações da organização foram modificadas.
-settings.change_orgname_prompt=Nota: Mudar o nome da organização também irá mudar o URL da organização e libertar o nome antigo.
-settings.change_orgname_redirect_prompt=O nome antigo, enquanto não for reivindicado, irá reencaminhar para o novo.
+
+settings.rename=Renomear organização
+settings.rename_desc=Mudar o nome da organização também irá mudar o URL da organização e libertar o nome antigo.
+settings.rename_success=A organização %[1]s foi renomeada para %[2]s com sucesso.
+settings.rename_no_change=O nome da organização não foi alterado.
+settings.rename_new_org_name=Novo nome da organização
+settings.rename_failed=A renomeação da organização falhou por causa de um erro interno
+settings.rename_notices_1=Esta operação <strong>NÃO PODERÃ</strong> ser revertida.
+settings.rename_notices_2=O antigo nome, enquanto não for reivindicado, irá reencaminhar para o novo.
+
settings.update_avatar_success=O avatar da organização foi modificado.
settings.delete=Eliminar organização
settings.delete_account=Eliminar esta organização
settings.delete_prompt=A organização será removida permanentemente. Essa operação <strong>NÃO PODERÃ</strong> ser revertida!
+settings.name_confirm=Insira o nome da organização para confirmar:
+settings.delete_notices_1=Esta operação <strong>NÃO PODERÃ</strong> ser revertida.
+settings.delete_notices_2=Esta operação irá eliminar de forma permanente todos os <strong>repositórios</strong> de <strong>%s</strong>, incluindo código-fonte, questões, comentários, dados do wiki e configurações dos colaboradores.
+settings.delete_notices_3=Esta operação irá eliminar de forma permanente todos os <strong>pacotes</strong> de <strong>%s</strong>.
+settings.delete_notices_4=Esta operação irá eliminar de forma permanente todos os <strong>planeamentos</strong> de <strong>%s</strong>.
settings.confirm_delete_account=Confirme a eliminação
-settings.delete_org_title=Eliminar organização
-settings.delete_org_desc=Esta organização será eliminada permanentemente. Quer continuar?
+settings.delete_failed=A eliminação da organização falhou por causa de um erro interno
+settings.delete_successful=A organização <b>%s</b> foi eliminada com sucesso.
settings.hooks_desc=Adicionar automatismos web que serão despoletados para <strong>todos os repositórios</strong> desta organização.
settings.labels_desc=Adicionar rótulos que possam ser usados em questões para <strong>todos os repositórios</strong> desta organização.
@@ -3721,13 +3755,18 @@ owner.settings.chef.keypair.description=É necessário um par de chaves para aut
secrets=Segredos
description=Os segredos serão transmitidos a certas operações e não poderão ser lidos de outra forma.
none=Ainda não há segredos.
-creation=Adicionar segredo
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Descrição
creation.name_placeholder=Só sublinhados ou alfanuméricos sem distinguir maiúsculas, sem começar com GITEA_ nem GITHUB_
creation.value_placeholder=Insira um conteúdo qualquer. Espaços em branco no início ou no fim serão omitidos.
creation.description_placeholder=Escreva uma descrição curta (opcional).
-creation.success=O segredo "%s" foi adicionado.
-creation.failed=Falhou ao adicionar o segredo.
+
+save_success=O segredo "%s" foi guardado.
+save_failed=Falhou ao guardar o segredo.
+
+add_secret=Adicionar segredo
+edit_secret=Editar segredo
deletion=Remover segredo
deletion.description=Remover um segredo é permanente e não pode ser revertido. Continuar?
deletion.success=O segredo foi removido.
@@ -3805,6 +3844,11 @@ runs.no_workflows.documentation=Para mais informação sobre o Gitea Actions vej
runs.no_runs=A sequência de trabalho ainda não foi executada.
runs.empty_commit_message=(mensagem de cometimento vazia)
runs.expire_log_message=Os registros foram removidos porque eram muito antigos.
+runs.delete=Eliminar execução da sequência de trabalho
+runs.cancel=Cancelar a execução da sequência de trabalho
+runs.delete.description=Tem a certeza que pretende eliminar permanentemente a execução desta sequência de trabalho? Esta operação não poderá ser desfeita.
+runs.not_done=A execução desta sequência de trabalho ainda não terminou.
+runs.view_workflow_file=Ver ficheiro da sequência de trabalho
workflow.disable=Desabilitar sequência de trabalho
workflow.disable_success=A sequência de trabalho '%s' foi desabilitada com sucesso.
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index 879d7c6145..a44365f43c 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -1169,7 +1169,6 @@ editor.update=Обновить %s
editor.delete=Удалить %s
editor.patch=Применить патч
editor.patching=ИÑправление:
-editor.fail_to_apply_patch=Ðевозможно применить патч «%s»
editor.new_patch=Ðовый патч
editor.commit_message_desc=Добавьте необÑзательное раÑширенное опиÑание…
editor.signoff_desc=Добавить трейлер Signed-off-by Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¾Ð¼ коммита в конце ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°.
@@ -1187,15 +1186,11 @@ editor.branch_already_exists=Ветка «%s» уже ÑущеÑтвует в Ñ
editor.directory_is_a_file=Ð˜Ð¼Ñ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ð° «%s» уже иÑпользуетÑÑ Ð² качеÑтве имени файла в Ñтом репозитории.
editor.file_is_a_symlink=`«%s» ÑвлÑетÑÑ ÑимволичеÑкой ÑÑылкой. СимволичеÑкие ÑÑылки невозможно отредактировать в веб-редакторе`
editor.filename_is_a_directory=Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° «%s» уже иÑпользуетÑÑ Ð² качеÑтве каталога в Ñтом репозитории.
-editor.file_editing_no_longer_exists=Редактируемый файл «%s» больше не ÑущеÑтвует в Ñтом репозитории.
-editor.file_deleting_no_longer_exists=УдалÑемый файл «%s» больше не ÑущеÑтвует в Ñтом репозитории.
editor.file_changed_while_editing=Содержимое файла изменилоÑÑŒ Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° начала редактированиÑ. <a target="_blank" rel="noopener noreferrer" href="%s">Ðажмите здеÑÑŒ</a>, чтобы увидеть, что было изменено, или <strong>ЗафикÑировать Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñнова</strong>, чтобы заменить их.
editor.file_already_exists=Файл Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ «%s» уже ÑущеÑтвует в репозитории.
editor.commit_empty_file_header=Закоммитить пуÑтой файл
editor.commit_empty_file_text=Файл, который вы ÑобираетеÑÑŒ зафикÑировать, пуÑÑ‚. Продолжить?
editor.no_changes_to_show=Ðет изменений.
-editor.fail_to_update_file=Ðе удалоÑÑŒ обновить/Ñоздать файл «%s».
-editor.fail_to_update_file_summary=Ошибка:
editor.push_rejected_no_message=Изменение отклонено Ñервером без ÑообщениÑ. ПожалуйÑта, проверьте хуки Git.
editor.push_rejected=Изменение отклонено Ñервером. ПожалуйÑта, проверьте хуки Git.
editor.push_rejected_summary=Полное Ñообщение об отклонении:
@@ -1210,6 +1205,7 @@ editor.require_signed_commit=Ветка ожидает подпиÑанный к
editor.cherry_pick=ПеренеÑти Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ %s в:
editor.revert=Откатить %s к:
+
commits.desc=ПроÑмотр иÑтории изменений иÑходного кода.
commits.commits=Коммитов
commits.no_commits=Ðет общих коммитов. «%s» и «%s» имеют Ñовершенно разные иÑтории.
@@ -2074,7 +2070,6 @@ settings.event_repository=Репозиторий
settings.event_repository_desc=Репозиторий Ñоздан или удален.
settings.event_header_issue=Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸
settings.event_issues=Задачи
-settings.event_issues_desc=Задача открыта, закрыта, переоткрыта или отредактирована.
settings.event_issue_assign=Ðазначена задача
settings.event_issue_assign_desc=Задача назначена или ÑнÑта Ñ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ.
settings.event_issue_label=Ярлык задачи
@@ -2085,7 +2080,6 @@ settings.event_issue_comment=Комментарии в задаче
settings.event_issue_comment_desc=Комментарий Ñоздан, изменён или удалён.
settings.event_header_pull_request=Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на ÑлиÑние
settings.event_pull_request=Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние
-settings.event_pull_request_desc=Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние открыт, закрыт, переоткрыт или отредактирован.
settings.event_pull_request_assign=ЗапроÑа на ÑлиÑние назначен
settings.event_pull_request_assign_desc=Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние назначен или не назначен.
settings.event_pull_request_label=Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние отмечен
@@ -2455,15 +2449,13 @@ settings.visibility.private_shortname=Приватный
settings.update_settings=Обновить наÑтройки
settings.update_setting_success=ÐаÑтройки организации обновлены.
-settings.change_orgname_prompt=Обратите внимание: изменение Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð°Ñ†Ð¸Ð¸ также изменит URL вашей организации и оÑвободит Ñтарое имÑ.
-settings.change_orgname_redirect_prompt=Старое Ð¸Ð¼Ñ Ð±ÑƒÐ´ÐµÑ‚ перенаправлено до тех пор, пока оно не будет введено.
+
+
settings.update_avatar_success=Ðватар организации обновлён.
settings.delete=Удалить организацию
settings.delete_account=Удалить Ñту организацию
settings.delete_prompt=Это дейÑтвие <strong>БЕЗВОЗВРÐТÐО</strong> удалит Ñту организацию навÑегда!
settings.confirm_delete_account=Подтвердить удаление
-settings.delete_org_title=Удалить организацию
-settings.delete_org_desc=Эта Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ безвозвратно удалена. Продолжить?
settings.hooks_desc=Добавьте веб-хуки, которые будет вызыватьÑÑ Ð´Ð»Ñ <strong>вÑех репозиториев</strong> под Ñтой организации.
settings.labels_desc=Добавьте метки, которые могут быть иÑпользованы в задачах Ð´Ð»Ñ <strong>вÑех репозиториев</strong> Ñтой организации.
@@ -3266,12 +3258,13 @@ owner.settings.chef.keypair=Создать пару ключей
secrets=Секреты
description=Секреты будут передаватьÑÑ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ñ‹Ð¼ дейÑтвиÑм и не могут быть прочитаны иначе.
none=Секретов пока нет.
-creation=Добавить Ñекрет
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=ОпиÑание
creation.name_placeholder=региÑтр не важен, только алфавитно-цифровые Ñимволы и подчёркиваниÑ, не может начинатьÑÑ Ñ GITEA_ или GITHUB_
creation.value_placeholder=Введите любое Ñодержимое. Пробельные Ñимволы в начале и конце будут опущены.
-creation.success=Секрет «%s» добавлен.
-creation.failed=Ðе удалоÑÑŒ добавить Ñекрет.
+
+
deletion=Удалить Ñекрет
deletion.description=Удаление Ñекрета необратимо, его Ð½ÐµÐ»ÑŒÐ·Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ. Продолжить?
deletion.success=Секрет удалён.
diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini
index 042e8ad21b..286433074e 100644
--- a/options/locale/locale_si-LK.ini
+++ b/options/locale/locale_si-LK.ini
@@ -917,13 +917,13 @@ editor.file_changed_while_editing=ඔබ සංස්කරණය කිරීà¶
editor.commit_empty_file_header=හිස් ගොනුවක් à¶šà·à¶´ කරන්න
editor.commit_empty_file_text=ඔබ à¶šà·à¶´ කිරීමට යන ගොනුව හිස් ය. ඉදිරියට?
editor.no_changes_to_show=පෙන්වීමට කිසිදු වෙනසක් à¶±à·à¶­.
-editor.fail_to_update_file_summary=දà·à·‚ පණිවිඩය:
editor.push_rejected_summary=පූර්ණ ප්රතික්ෂේප පණිවිඩය:
editor.add_subdir=ඩිරෙක්ටරියක් එක් කරන්න…
editor.no_commit_to_branch=à·à·à¶›à·à·€à¶§ කෙලින්ම à¶šà·à¶´à·€à·’ය නොහà·à¶šà·’ නිසà·:
editor.user_no_push_to_branch=පරිà·à·“ලකයà·à¶§ à·à·à¶›à·à·€à¶§ තල්ලු à¶šà·… නොහà·à¶š
editor.require_signed_commit=à·à·à¶›à·à·€à¶§ අත්සන් à¶šà·… à¶šà·à¶´à·€à·“මක් à¶…à·€à·à·Šà¶º වේ
+
commits.desc=මූලà·à·à·Šà¶» à¶šà·šà¶­ වෙනස් කිරීමේ ඉතිහà·à·ƒà¶º පිරික්සන්න.
commits.commits=විවරයන්
commits.nothing_to_compare=මෙම à·à·à¶›à· සමà·à¶± වේ.
@@ -1596,7 +1596,6 @@ settings.event_repository=à¶šà·à·‚්ඨය
settings.event_repository_desc=ගබඩà·à·€ නිර්මà·à¶«à¶º කරන ලද හ෠මක෠දමන ලදි.
settings.event_header_issue=නිකුත් කිරීමේ සිදුවීම්
settings.event_issues=à¶œà·à¶§à·…à·”
-settings.event_issues_desc=නිකුත් කිරීම විවෘත කිරීම, වස෠දà·à¶¸à·“ම, à¶±à·à·€à¶­ විවෘත කිරීම හ෠සංස්කරණය කිරීම.
settings.event_issue_assign=පවර෠ඇති නිකුතුව
settings.event_issue_assign_desc=පවර෠ඇති හ෠පවර෠නොමà·à¶­à·’ නිකුත් කිරීම.
settings.event_issue_label=ලේබල් නිකුත්
@@ -1607,7 +1606,6 @@ settings.event_issue_comment=නිකුතුව
settings.event_issue_comment_desc=නිකුත් අදහස් නිර්මà·à¶«à¶º, සංස්කරණය, හ෠මකà·.
settings.event_header_pull_request=ඉල්ලීම් සිදුවීම් අදින්න
settings.event_pull_request=ඉල්ලීම අදින්න
-settings.event_pull_request_desc=අදින්න ඉල්ලීම විවෘත, වසà·, à¶±à·à·€à¶­ විවෘත, හ෠සංස්කරණය.
settings.event_pull_request_assign=පවර෠ඉල්ලීම අදින්න
settings.event_pull_request_assign_desc=පවර෠ඇති හ෠පවර෠නොමà·à¶­à·’ ඉල්ලීම අදින්න.
settings.event_pull_request_label=ලේබල් ඉල්ලීම අදින්න
@@ -1882,14 +1880,13 @@ settings.visibility.private_shortname=පෞද්ගලික
settings.update_settings=à·ƒà·à¶šà·ƒà·”ම් යà·à·€à¶­à·Šà¶šà·à¶½ කරන්න
settings.update_setting_success=සංවිධà·à¶±à¶ºà·š à·ƒà·à¶šà·ƒà·”ම් යà·à·€à¶­à·Šà¶šà·à¶½ à¶šà¶» ඇත.
-settings.change_orgname_redirect_prompt=à¶´à·à¶»à¶«à·’ නම ඉල්ල෠සිටින තුරු à¶±à·à·€à¶­ හරව෠යවයි.
+
+
settings.update_avatar_success=සංවිධà·à¶±à¶ºà·š අවතà·à¶»à¶º යà·à·€à¶­à·Šà¶šà·à¶½à·“à¶± à¶šà¶» ඇත.
settings.delete=සංවිධà·à¶±à¶º මකන්න
settings.delete_account=මෙම සංවිධà·à¶±à¶º මකන්න
settings.delete_prompt=සංවිධà·à¶±à¶º ස්ථිරවම ඉවත් කරනු à¶½à·à¶¶à·š. මෙම <strong></strong> à¶…à·„à·à·ƒà·’ à¶šà·… නොහà·à¶š!
settings.confirm_delete_account=මකà·à¶¯à·à¶¸à·“ම තහවුරු කරන්න
-settings.delete_org_title=සංවිධà·à¶±à¶º මකන්න
-settings.delete_org_desc=මෙම සංවිධà·à¶±à¶º ස්ථිරවම මක෠දමනු ඇත. දිගටම?
settings.hooks_desc=මෙම සංවිධà·à¶±à¶º යටතේ <strong>සියලුම ගබඩà·à·€à¶±à·Š</strong> සඳහ෠මුලපුරනු ලබන වෙබ් කොකු à¶‘à¶šà¶­à·” කරන්න.
settings.labels_desc=මෙම සංවිධà·à¶±à¶º යටතේ <strong>සියලුම ගබඩà·à·€à¶½à¶¯à·“</strong> සඳහ෠ගà·à¶§à·…à·” සඳහ෠භà·à·€à·’à¶­à· à¶šà·… à·„à·à¶šà·’ ලේබල් à¶‘à¶šà¶­à·” කරන්න.
@@ -2447,8 +2444,12 @@ conan.details.repository=à¶šà·à·‚්ඨය
owner.settings.cleanuprules.enabled=සබල කර ඇත
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=සවිස්තරය
+
+
[actions]
diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini
index b1dae7c490..5ea6a7508e 100644
--- a/options/locale/locale_sk-SK.ini
+++ b/options/locale/locale_sk-SK.ini
@@ -1007,6 +1007,7 @@ editor.commit_empty_file_text=Súbor, ktorý sa chystáte odoslať, je prázdny.
editor.no_commit_to_branch=Nedá sa odoslať priamo do vetvy, pretože:
editor.require_signed_commit=Vetva vyžaduje podpísaný commit
+
commits.commits=Commity
commits.search_all=Všetky vetvy
commits.author=Autor
@@ -1219,6 +1220,8 @@ lower_repositories=repozitáre
settings.visibility.private=Súkromná ​​(viditeľné iba pre Älenov organizácie)
settings.visibility.private_shortname=Súkromný
+
+
settings.hooks_desc=Pridajte webhooky, ktoré sa spustia nad <strong>všetkými repozitármi</strong> v rámci tejto organizácie.
@@ -1321,6 +1324,10 @@ owner.settings.cleanuprules.enabled=Povolené
[secrets]
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
+
+
+
[actions]
diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini
index 6fb5a9c4cb..7041db9ac3 100644
--- a/options/locale/locale_sv-SE.ini
+++ b/options/locale/locale_sv-SE.ini
@@ -777,12 +777,12 @@ editor.file_changed_while_editing=Filens innehåll har ändrats sedan du påbör
editor.commit_empty_file_header=Committa en tom fil
editor.commit_empty_file_text=Filen du vill committa är tom. Vill du fortsätta?
editor.no_changes_to_show=Det finns inga ändringar att visa.
-editor.fail_to_update_file_summary=Felmeddelande:
editor.add_subdir=Lägga till en katalog…
editor.no_commit_to_branch=Det gick inte att committa direkt till branchen för:
editor.user_no_push_to_branch=Användaren kan inte pusha till branchen
editor.require_signed_commit=Branchen kräver en signerad commit
+
commits.desc=Bläddra i källkodens förändringshistorik.
commits.commits=Incheckningar
commits.search_all=Alla brancher
@@ -1524,13 +1524,13 @@ settings.visibility.private=Privat (synlig endast för organisationens medlemmar
settings.visibility.private_shortname=Privat
settings.update_settings=Uppdatera inställningar
+
+
settings.update_avatar_success=Organisationens avatar har uppdateras.
settings.delete=Tag bort organisation
settings.delete_account=Tag bort denna organisation
settings.delete_prompt=Organisationen kommer tas bort permanent, och det går <strong>INTE</strong> att ångra detta!
settings.confirm_delete_account=Bekräfta borttagning
-settings.delete_org_title=Ta bort organisation
-settings.delete_org_desc=Denna organisation kommer tas bort permanent. Vill du fortsätta?
settings.hooks_desc=Lägg till webbhook som triggas för <strong>alla utvecklingskataloger</strong> under denna organisationen.
settings.labels_desc=Lägg till etiketter som kan användas till ärenden för <strong>alla utvecklingskataloger</strong> under denna organisation.
@@ -1982,8 +1982,12 @@ conan.details.repository=Utvecklingskatalog
owner.settings.cleanuprules.enabled=Aktiv
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Beskrivning
+
+
[actions]
diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini
index acd0892eba..43802b3ac2 100644
--- a/options/locale/locale_tr-TR.ini
+++ b/options/locale/locale_tr-TR.ini
@@ -113,9 +113,11 @@ copy_type_unsupported=Bu dosya türü kopyalanamaz
write=Yaz
preview=Önizleme
loading=Yükleniyor…
+files=Dosyalar
error=Hata
error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>.
+error503=Sunucu isteğinizi gerçekleştiremedi. Lütfen daha sonra tekrar deneyin.
go_back=Geri Git
invalid_data=Geçersiz veri: %v
@@ -128,6 +130,7 @@ pin=Sabitle
unpin=Sabitlemeyi kaldır
artifacts=Yapılar
+expired=Süresi doldu
confirm_delete_artifact=%s yapısını silmek istediğinizden emin misiniz?
archived=ArÅŸivlenmiÅŸ
@@ -169,6 +172,10 @@ search=Ara...
type_tooltip=Arama türü
fuzzy=Bulanık
fuzzy_tooltip=Arama terimine benzeyen sonuçları da içer
+words=Kelimeler
+words_tooltip=Sadece arama terimi kelimeleriyle eşleşen sonuçları içer
+regexp=Regexp
+regexp_tooltip=Sadece regexp arama terimiyle tamamen eşleşen sonuçları içer
exact=Tam
exact_tooltip=Sadece arama terimiyle tamamen eşleşen sonuçları içer
repo_kind=Depoları ara...
@@ -235,13 +242,17 @@ network_error=Ağ hatası
[startpage]
app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git servisi
install=Kurulumu kolay
+install_desc=Platformunuz için <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">ikili dosyayı çalıştırın</a>, <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a> ile yükleyin veya <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">paket</a> olarak edinin.
platform=Farklı platformlarda çalışablir
+platform_desc=Gitea <a target="_blank" rel="noopener noreferrer" href="%s">Go</a> ile derleme yapılabilecek her yerde çalışmaktadır: Windows, macOS, Linux, ARM, vb. Hangisini seviyorsanız onu seçin!
lightweight=Hafif
lightweight_desc=Gitea'nın minimal gereksinimleri çok düşüktür ve ucuz bir Raspberry Pi üzerinde çalışabilmektedir. Makine enerjinizden tasarruf edin!
license=Açık Kaynak
+license_desc=Gidin ve <a target="_blank" rel="noopener noreferrer" href="https://code.gitea.io/gitea">code.gitea.io/gitea</a>'yı edinin! Bu projeyi daha da iyi yapmak için <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea">katkıda bulunarak</a> bize katılın. Katkıda bulunmaktan çekinmeyin!
[install]
install=Kurulum
+installing_desc=Şimdi kuruluyor, lütfen bekleyin...
title=Başlangıç Yapılandırması
docker_helper=Eğer Gitea'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce <a target="_blank" rel="noopener noreferrer" href="%s">belgeleri</a> okuyun.
require_db_desc=Gitea MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir.
@@ -352,6 +363,7 @@ enable_update_checker=Güncelleme Denetleyicisini Etkinleştir
enable_update_checker_helper=Düzenli olarak gitea.io'ya bağlanarak yeni yayınlanan sürümleri denetler.
env_config_keys=Ortam Yapılandırma
env_config_keys_prompt=Aşağıdaki ortam değişkenleri de yapılandırma dosyanıza eklenecektir:
+config_write_file_prompt=Bu yapılandırma seçenekleri şuraya yazılacak: %s
[home]
nav_menu=Gezinti Menüsü
@@ -380,6 +392,12 @@ show_only_public=Yalnızca açık olanlar gösteriliyor
issues.in_your_repos=Depolarınızda
+guide_title=Etkinlik yok
+guide_desc=Herhangi bir depo veya kullanıcı takip etmiyorsunuz, bu yüzden görüntülenecek bir içerik yok. Aşağıdaki bağlantıları kullanarak ilgi çekici depo ve kullanıcıları keşfedebilirsiniz.
+explore_repos=Depoları keşfet
+explore_users=Kullanıcıları keşfet
+empty_org=Henüz bir organizasyon yok.
+empty_repo=Henüz bir depo yok.
[explore]
repos=Depolar
@@ -403,6 +421,7 @@ remember_me.compromised=Oturum açma tokeni artık geçerli değil, bu ele geçi
forgot_password_title=Åžifremi unuttum
forgot_password=Åžifrenizi mi unuttunuz?
need_account=Bir hesaba mı ihtiyacın var?
+sign_up_tip=Sistemdeki yönetici ayrıcalıklarına sahip olan ilk hesabı oluşturuyorsunuz. Lütfen kullanıcı adınızı ve parolanızı unutmamaya dikkat edin. Kullanıcı adı veya parolayı unutursanız, hesabı kurtarmak için lütfen Gitea belgelerine bakın.
sign_up_now=Hemen kaydolun.
sign_up_successful=Hesap başarılı bir şekilde oluşturuldu. Hoşgeldiniz!
confirmation_mail_sent_prompt_ex=Yeni bir doğrulama e-postası <b>%s</b> adresine gönderildi. Lütfen kayıt sürecini tamamlamak için %s içinde gelen kutunuzu denetleyin. Eğer kayıt e-posta adresiniz hatalı ise, tekrar oturum açıp değiştirebilirsiniz.
@@ -433,6 +452,7 @@ use_scratch_code=Bir çizgi kodu kullanınız
twofa_scratch_used=Geçici kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada aygıt kaydınızı kaldırabilir veya yeni bir geçici kod oluşturabilirsiniz.
twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın.
twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir.
+twofa_required=Depolara erişmek için iki aşama doğrulama kullanmanız veya tekrar oturum açmayı denemeniz gereklidir.
login_userpass=Oturum Aç
login_openid=Açık Kimlik
oauth_signup_tab=Yeni Hesap OluÅŸtur
@@ -441,6 +461,7 @@ oauth_signup_submit=Hesabı Tamamla
oauth_signin_tab=Mevcut Hesaba BaÄŸla
oauth_signin_title=Bağlantılı Hesabı Yetkilendirmek için Giriş Yapın
oauth_signin_submit=Hesabı Bağla
+oauth.signin.error.general=Yetkilendirme isteğini işlerken bir hata oluştu: %s. Eğer hata devam ederse lütfen site yöneticisiyle bağlantıya geçin.
oauth.signin.error.access_denied=Yetkilendirme isteÄŸi reddedildi.
oauth.signin.error.temporarily_unavailable=Yetkilendirme sunucusu geçici olarak erişilemez olduğu için yetkilendirme başarısız oldu. Lütfen daha sonra tekrar deneyin.
oauth_callback_unable_auto_reg=Otomatik kayıt etkin ancak OAuth2 Sağlayıcı %[1] eksik sahalar döndürdü: %[2]s, otomatik olarak hesap oluşturulamıyor, lütfen bir hesap oluşturun veya bağlantı verin, veya site yöneticisiyle iletişim kurun.
@@ -457,10 +478,12 @@ authorize_application=Uygulamayı Yetkilendir
authorize_redirect_notice=Bu uygulamayı yetkilendirirseniz %s adresine yönlendirileceksiniz.
authorize_application_created_by=Bu uygulama %s tarafından oluşturuldu.
authorize_application_description=Erişime izin verirseniz, özel depolar ve organizasyonlar da dahil olmak üzere tüm hesap bilgilerinize erişebilir ve yazabilir.
+authorize_application_with_scopes=Kapsamlar: %s
authorize_title=Hesabınıza erişmesi için "%s" yetkilendirilsin mi?
authorization_failed=Yetkilendirme başarısız oldu
authorization_failed_desc=Geçersiz bir istek tespit ettiğimiz için yetkilendirme başarısız oldu. Lütfen izin vermeye çalıştığınız uygulamanın sağlayıcısı ile iletişim kurun.
sspi_auth_failed=SSPI kimlik doğrulaması başarısız oldu
+password_pwned=Seçtiğiniz parola, daha önce herkese açık veri ihlallerinde açığa çıkan bir <a target="_blank" rel="noopener noreferrer" href="%s">çalınan parola listesindedir</a>. Lütfen farklı bir parola ile tekrar deneyin ve başka yerlerde de bu parolayı değiştirmeyi düşünün.
password_pwned_err=HaveIBeenPwned'e yapılan istek tamamlanamadı
last_admin=Son yöneticiyi silemezsiniz. En azından bir yönetici olmalıdır.
signin_passkey=Bir parola anahtarı ile oturum aç
@@ -583,6 +606,8 @@ lang_select_error=Listeden bir dil seçin.
username_been_taken=Bu kullanıcı adı daha önce alınmış.
username_change_not_local_user=Yerel olmayan kullanıcılar kendi kullanıcı adlarını değiştiremezler.
+change_username_disabled=Kullanıcı adı değişikliği devre dışıdır.
+change_full_name_disabled=Tam ad değişikliği devre dışıdır.
username_has_not_been_changed=Kullanıcı adı değişmedi
repo_name_been_taken=Depo adı zaten kullanılıyor.
repository_force_private=Gizliyi Zorla devrede: gizli depolar herkese açık yapılamaz.
@@ -632,6 +657,7 @@ org_still_own_repo=Bu organizasyon hala bir veya daha fazla depoya sahip, önce
org_still_own_packages=Bu organizasyon hala bir veya daha fazla pakete sahip, önce onları silin.
target_branch_not_exist=Hedef dal mevcut deÄŸil.
+target_ref_not_exist=Hedef referans mevcut deÄŸil %s
admin_cannot_delete_self=Yöneticiyken kendinizi silemezsiniz. Lütfen önce yönetici haklarınızı kaldırın.
@@ -698,14 +724,18 @@ applications=Uygulamalar
orgs=Organizasyonları Yönet
repos=Depolar
delete=Hesabı Sil
+twofa=İki Aşamalı Kimlik Doğrulama (TOTP)
account_link=Bağlı Hesaplar
organization=Organizasyonlar
uid=UID
+webauthn=İki-Aşamalı Kimlik Doğrulama (Güvenlik Anahtarları)
public_profile=Herkese Açık Profil
biography_placeholder=Bize kendiniz hakkında birşeyler söyleyin! (Markdown kullanabilirsiniz)
location_placeholder=Yaklaşık konumunuzu başkalarıyla paylaşın
profile_desc=Profilinizin başkalarına nasıl gösterildiğini yönetin. Ana e-posta adresiniz bildirimler, parola kurtarma ve web tabanlı Git işlemleri için kullanılacaktır.
+password_username_disabled=Yerel olmayan kullanıcılara kullanıcı adlarını değiştirme izni verilmemiştir. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz.
+password_full_name_disabled=Tam adınızı değiştirme izniniz yoktur. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz.
full_name=Ad Soyad
website=Web Sitesi
location=Konum
@@ -755,6 +785,7 @@ uploaded_avatar_not_a_image=Yüklenen dosya bir resim dosyası değil.
uploaded_avatar_is_too_big=Yüklenen dosyanın boyutu (%d KiB), azami boyutu (%d KiB) aşıyor.
update_avatar_success=Profil resminiz deÄŸiÅŸtirildi.
update_user_avatar_success=Kullanıcının avatarı güncellendi.
+cropper_prompt=Kaydetmeden önce resmi düzenleyebilirsiniz. Düzenlenen resim PNG biçiminde kaydedilecektir.
change_password=Parolayı Güncelle
old_password=Mevcut Parola
@@ -797,6 +828,7 @@ add_email_success=Yeni e-posta adresi eklendi.
email_preference_set_success=E-posta tercihi başarıyla ayarlandı.
add_openid_success=Yeni OpenID adresi eklendi.
keep_email_private=E-posta Adresini Gizle
+keep_email_private_popup=Bu, e-posta adresinizi profilde, değişiklik isteği yaptığınızda veya web arayüzünde dosya düzenlediğinizde gizleyecektir. İtilen işlemeler değişmeyecektir.
openid_desc=OpenID, kimlik doğrulama işlemini harici bir sağlayıcıya devretmenize olanak sağlar.
manage_ssh_keys=SSH Anahtarlarını Yönet
@@ -898,6 +930,9 @@ permission_not_set=Ayarlanmadı
permission_no_access=EriÅŸim Yok
permission_read=OkunmuÅŸ
permission_write=Okuma ve Yazma
+permission_anonymous_read=Anonim Okuma
+permission_everyone_read=Herkes Okuyabilir
+permission_everyone_write=Herkes Yazabilir
access_token_desc=Seçili token izinleri, yetkilendirmeyi ilgili <a %s>API</a> yollarıyla sınırlandıracaktır. Daha fazla bilgi için <a %s>belgeleri</a> okuyun.
at_least_one_permission=Bir token oluşturmak için en azından bir izin seçmelisiniz
permissions_list=İzinler:
@@ -925,6 +960,7 @@ oauth2_client_secret_hint=Bu sayfadan ayrıldıktan veya yeniledikten sonra gizl
oauth2_application_edit=Düzenle
oauth2_application_create_description=OAuth2 uygulamaları, üçüncü taraf uygulamanıza bu durumda kullanıcı hesaplarına erişim sağlar.
oauth2_application_remove_description=Bir OAuth2 uygulamasının kaldırılması, bu sunucudaki yetkili kullanıcı hesaplarına erişmesini önler. Devam edilsin mi?
+oauth2_application_locked=Gitea kimi OAuth2 uygulamalarının başlangıçta ön kaydını, yapılandırmada etkinleştirilmişse yapabilir. Beklenmeyen davranışı önlemek için bunlar ne düzenlenmeli ne de kaldırılmalı. Daha fazla bilgi için OAuth2 belgesine bakın.
authorized_oauth2_applications=Yetkili OAuth2 Uygulamaları
authorized_oauth2_applications_description=Kişisel Gitea hesabınıza bu üçüncü parti uygulamalara erişim izni verdiniz. Lütfen artık ihtiyaç duyulmayan uygulamalara erişimi iptal edin.
@@ -933,20 +969,26 @@ revoke_oauth2_grant=Erişimi İptal Et
revoke_oauth2_grant_description=Bu üçüncü taraf uygulamasına erişimin iptal edilmesi bu uygulamanın verilerinize erişmesini önleyecektir. Emin misiniz?
revoke_oauth2_grant_success=Erişim başarıyla kaldırıldı.
+twofa_desc=İki aşamalı kimlik doğrulama, hesabınızın güvenliğini artırır.
twofa_recovery_tip=Aygıtınızı kaybetmeniz durumunda, hesabınıza tekrar erişmek için tek kullanımlık kurtarma anahtarını kullanabileceksiniz.
twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>.
twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş.
twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak
+twofa_scratch_token_regenerate=Geçici Kodu Yeniden Üret
+twofa_scratch_token_regenerated=Geçici kodunuz şimdi %s. Güvenli bir yerde saklayın, tekrar gösterilmeyecektir.
twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun
twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz.
twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi?
+regenerate_scratch_token_desc=Geçici kodunuzu kaybettiyseniz veya oturum açmak için kullandıysanız, buradan sıfırlayabilirsiniz.
twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı.
scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın:
or_enter_secret=Veya gizli ÅŸeyi girin: %s
then_enter_passcode=Ve uygulamada gösterilen şifreyi girin:
passcode_invalid=Şifre geçersiz. Tekrar deneyin.
+twofa_enrolled=Hesabınız başarıyla kaydedildi. Tek kullanımlık geçici kodunuz (%s) tekrar gösterilmeyeceği için güvenilir bir yerde saklayın.
twofa_failed_get_secret=Gizlilik elde edilemedi.
+webauthn_desc=Güvenlik anahtarları, şifreleme anahtarlarını içeren donanım aygıtlarıdır. İki aşamalı kimlik doğrulama için kullanılabilirler. Güvenlik anahtarları <a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a> standardını desteklemelidir.
webauthn_register_key=Güvenlik Anahtarı Ekle
webauthn_nickname=Takma Ad
webauthn_delete_key=Güvenlik Anahtarını Kaldır
@@ -993,6 +1035,9 @@ new_repo_helper=Bir depo, sürüm geçmişi dahil tüm proje dosyalarını içer
owner=Sahibi
owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir.
repo_name=Depo İsmi
+repo_name_profile_public_hint=.profile herkese açık organizasyonunuzun profiline herkesin görüntüleyebileceği bir README.md dosyası eklemek için kullanabileceğiniz özel bir depodur. Başlamak için herkese açık olduğundan ve profile dizininde README ile başladığınızdan emin olun.
+repo_name_profile_private_hint=.profile-private organizasyonunuzun üye profiline sadece organizasyon üyelerinin görüntüleyebileceği bir README.md eklemek için kullanabileceğiniz özel bir depodur. Başlamak için özel olduğundan ve profil dizininde README ile başladığınızdan emin olun.
+repo_name_helper=İyi depo isimleri kısa, akılda kalıcı ve benzersiz anahtar kelimeler kullanır. “.profile†veya ‘.profile-private’ adlı bir depo, kullanıcı/kuruluş profili için bir README.md eklemek için kullanılabilir.
repo_size=Depo Boyutu
template=Åžablon
template_select=Bir şablon seçin.
@@ -1011,6 +1056,8 @@ fork_to_different_account=Başka bir hesaba çatalla
fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez.
fork_branch=Çatala klonlanacak dal
all_branches=Tüm dallar
+view_all_branches=Tüm dalları görüntüle
+view_all_tags=Tüm etiketleri görüntüle
fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz.
fork.blocked_user=Depo çatallanamıyor, depo sahibi tarafından engellenmişsiniz.
use_template=Bu ÅŸablonu kullan
@@ -1022,6 +1069,8 @@ generate_repo=Depo OluÅŸtur
generate_from=Åžuradan OluÅŸtur
repo_desc=Açıklama
repo_desc_helper=Kısa açıklama girin (isteğe bağlı)
+repo_no_desc=Hiçbir açıklama sağlanmadı
+repo_lang=Dil
repo_gitignore_helper=.gitignore şablonlarını seç.
repo_gitignore_helper_desc=Sık kullanılan diller için bir şablon listesinden hangi dosyaların izlenmeyeceğini seçin. Her dilin oluşturma araçları tarafından oluşturulan tipik yapılar, varsayılan olarak .gitignore dosyasına dahil edilmiştir.
issue_labels=Konu Etiketleri
@@ -1029,6 +1078,7 @@ issue_labels_helper=Bir konu etiket seti seçin.
license=Lisans
license_helper=Bir lisans dosyası seçin.
license_helper_desc=Bir lisans, başkalarının kodunuzla neler yapıp yapamayacağını yönetir. Projeniz için hangisinin doğru olduğundan emin değil misiniz? <a target="_blank" rel="noopener noreferrer" href="%s">Lisans seçme</a> konusuna bakın
+multiple_licenses=Çoklu Lisans
object_format=Nesne Biçimi
object_format_helper=Deponun nesne biçimi. Daha sonra değiştirilemez. SHA1 en uyumlu olandır.
readme=README
@@ -1082,15 +1132,20 @@ delete_preexisting_success=%s içindeki kabul edilmeyen dosyalar silindi
blame_prior=Bu değişiklikten önceki suçu görüntüle
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>.
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
+user_search_tooltip=En fazla 30 kullanıcı görüntüler
+tree_path_not_found=%[1] yolu, %[2]s deposunda mevcut deÄŸil
transfer.accept=Aktarımı Kabul Et
+transfer.accept_desc=`"%s" tarafına aktar`
transfer.reject=Aktarımı Reddet
+transfer.reject_desc=`"%s" tarafına aktarımı iptal et`
transfer.no_permission_to_accept=Bu aktarımı kabul etme izniniz yok.
transfer.no_permission_to_reject=Bu aktarımı reddetme izniniz yok.
desc.private=Özel
desc.public=Genel
+desc.public_access=Herkese Açık Erişim
desc.template=Åžablon
desc.internal=Dahili
desc.archived=ArÅŸivlenmiÅŸ
@@ -1160,6 +1215,11 @@ migrate.gogs.description=Notabug.org veya diğer Gogs sunucularından veri aktar
migrate.onedev.description=Code.onedev.io ve diğer OneDev sunucularından veri aktar.
migrate.codebase.description=Codebasehq.com sitesinden veri aktar.
migrate.gitbucket.description=GitBucket sunucularından veri aktar.
+migrate.codecommit.description=AWS CodeCommit'ten veri aktar.
+migrate.codecommit.aws_access_key_id=AWS Erişim Anahtarı Kimliği
+migrate.codecommit.aws_secret_access_key=AWS Gizli Erişim Anahtarı
+migrate.codecommit.https_git_credentials_username=HTTPS Git Kimliği Kullanıcı Adı
+migrate.codecommit.https_git_credentials_password=HTTPS Git Kimliği Parolası
migrate.migrating_git=Git Verilerini Taşıma
migrate.migrating_topics=Konuları Taşıma
migrate.migrating_milestones=Kilometre Taşlarını Taşıma
@@ -1169,6 +1229,7 @@ migrate.migrating_issues=Konuları Taşıma
migrate.migrating_pulls=Değişiklik İsteklerini Taşıma
migrate.cancel_migrating_title=Göçü İptal Et
migrate.cancel_migrating_confirm=Bu göçü iptal etmek istiyor musunuz?
+migration_status=Aktarma durumu
mirror_from=şunun yansıması
forked_from=şundan çatallanmış
@@ -1193,6 +1254,7 @@ create_new_repo_command=Komut satırında yeni bir depo oluşturuluyor
push_exist_repo=Komut satırından mevcut bir depo itiliyor
empty_message=Bu depoda herhangi bir içerik yok.
broken_message=Bu deponun altındaki Git verisi okunamıyor. Bu sunucunun yöneticisiyle bağlantıya geçin veya bu depoyu silin.
+no_branch=Bu deponun hiç bir dalı yok.
code=Kod
code.desc=Kaynak koda, dosyalara, iÅŸlemelere ve dallara eriÅŸ.
@@ -1237,6 +1299,7 @@ ambiguous_runes_header=`Bu dosya muğlak Evrensel Kodlu karakter içeriyor`
ambiguous_runes_description=`Bu dosya, başka karakterlerle karıştırılabilecek evrensel kodlu karakter içeriyor. Eğer bunu kasıtlı olarak yaptıysanız bu uyarıyı yok sayabilirsiniz. Gizli karakterleri göstermek için Kaçış Karakterli düğmesine tıklayın.`
invisible_runes_line=`Bu satırda görünmez evrensel kodlu karakter var`
ambiguous_runes_line=`Bu satırda muğlak evrensel kodlu karakter var`
+ambiguous_character=`%[1]c [U+%04[1]X], %[2]c [U+%04[2]X] ile karıştırılabilir`
escape_control_characters=Kaçış Karakterli
unescape_control_characters=Kaçış Karaktersiz
@@ -1269,7 +1332,9 @@ editor.upload_file=Dosya Yükle
editor.edit_file=Dosyayı Düzenle
editor.preview_changes=Değişiklikleri Önizle
editor.cannot_edit_lfs_files=LFS dosyaları web arayüzünde düzenlenemez.
+editor.cannot_edit_too_large_file=Bu dosya düzenlenmek için çok büyük.
editor.cannot_edit_non_text_files=Bu tür dosyalar web arayüzünden düzenlenemez.
+editor.file_not_editable_hint=Ama yine de ismini değiştirebilir veya taşıyabilirsiniz.
editor.edit_this_file=Dosyayı Düzenle
editor.this_file_locked=Dosya kilitlendi
editor.must_be_on_a_branch=Bu dosyada değişiklik yapmak veya önermek için bir dalda olmalısınız.
@@ -1289,7 +1354,7 @@ editor.update=%s Güncelle
editor.delete=%s Sil
editor.patch=Yama Uygula
editor.patching=Yamalanıyor:
-editor.fail_to_apply_patch=`"%s" yaması uygulanamıyor`
+editor.fail_to_apply_patch=Yama uygulanamıyor
editor.new_patch=Yeni Yama
editor.commit_message_desc=İsteğe bağlı uzun bir açıklama ekleyin…
editor.signoff_desc=İşleme günlüğü mesajının sonuna işleyen tarafından imzalanan bir fragman ekleyin.
@@ -1302,13 +1367,14 @@ editor.new_branch_name_desc=Yeni dal ismi…
editor.cancel=İptal
editor.filename_cannot_be_empty=Dosya adı boş olamaz.
editor.filename_is_invalid=Dosya adı geçersiz: "%s".
+editor.commit_email=İşleme e-postası
+editor.invalid_commit_email=İşleme e-postası hatalı.
editor.branch_does_not_exist=Bu depoda "%s" dalı yok.
editor.branch_already_exists=Bu depoda "%s" dalı zaten var.
editor.directory_is_a_file=Dizin adı "%s" zaten bu depoda bir dosya adı olarak kullanılmaktadır.
editor.file_is_a_symlink=`"%s" sembolik bir bağlantıdır. Sembolik bağlantılar web düzenleyicisinde düzenlenemez`
editor.filename_is_a_directory=Dosya adı "%s" zaten bu depoda bir dizin adı olarak kullanılmaktadır.
-editor.file_editing_no_longer_exists=Düzenlenmekte olan "%s" dosyası artık bu depoda yer almıyor.
-editor.file_deleting_no_longer_exists=Silinen "%s" dosyası artık bu depoda yer almıyor.
+editor.file_modifying_no_longer_exists=Değiştirilmekte olan "%s" dosyası artık bu depoda yer almıyor.
editor.file_changed_while_editing=Düzenlemeye başladığınızdan beri dosya içeriği değişti. Görmek için <a target="_blank" rel="noopener noreferrer" href="%s">burayı tıklayın</a> veya üzerine yazmak için <strong>değişiklikleri yine de işleyin</strong>.
editor.file_already_exists=Bu depoda "%s" isimli bir dosya zaten var.
editor.commit_id_not_matching=İşleme ID'si, düzenlemeye başladığınız ID ile uyuşmuyor, bir yama dalına işleme yapın ve sonra birleştirin.
@@ -1316,8 +1382,6 @@ editor.push_out_of_date=İtme eskimiş.
editor.commit_empty_file_header=BoÅŸ bir dosya iÅŸle
editor.commit_empty_file_text=İşlemek üzere olduğunuz dosya boş. Devam edilsin mi?
editor.no_changes_to_show=Gösterilecek değişiklik yok.
-editor.fail_to_update_file=`"%s" dosyası güncellenemedi/oluşturulamadı.`
-editor.fail_to_update_file_summary=Hata Mesajı:
editor.push_rejected_no_message=Değişiklik, bir ileti olmadan sunucu tarafından reddedildi. Git Hooks'u kontrol edin.
editor.push_rejected=Değişiklik sunucu tarafından reddedildi. Lütfen Git Hooks'u kontrol edin.
editor.push_rejected_summary=Tam Red Mesajı:
@@ -1331,6 +1395,15 @@ editor.user_no_push_to_branch=Kullanıcı dala gönderemez
editor.require_signed_commit=Dal imzalı bir işleme gerektirir
editor.cherry_pick=%s şunun üzerine cımbızla:
editor.revert=%s şuna geri döndür:
+editor.failed_to_commit=Değişikliklerin işlenmesi başarısız oldu.
+editor.failed_to_commit_summary=Hata Mesajı:
+
+editor.fork_create=Değişiklikler Önermek için Depoyu Çatallayın
+editor.fork_create_description=Bu depoyu doğrudan düzenleyemezsiniz. Bunun yerine bir çatal oluşturabilir, düzenlemeler yapabilir ve bir değişiklik isteği oluşturabilirsiniz.
+editor.fork_edit_description=Bu depoyu doğrudan düzenleyemezsiniz. Değişiklikler <b>%s</b> çatalınıza yazılacak, böylece bir değişiklik isteği oluşturabilirsiniz.
+editor.fork_not_editable=Bu deponun çatalını oluşturdunuz ancak çatalınız düzenlenemez durumda.
+editor.fork_failed_to_push_branch=%s dalını deponuza gönderme başarısız oldu.
+editor.fork_branch_exists=`"%s" dalı çatalınızda zaten mevcut, lütfen yeni bir dal adı seçin.`
commits.desc=Kaynak kodu değişiklik geçmişine göz atın.
commits.commits=İşleme
@@ -1350,6 +1423,7 @@ commits.signed_by_untrusted_user_unmatched=İşleyici ile eşleşmeyen güvenilm
commits.gpg_key_id=GPG Anahtar KimliÄŸi
commits.ssh_key_fingerprint=SSH Anahtar Parmak İzi
commits.view_path=Geçmişte bu noktayı görüntüle
+commits.view_file_diff=Bu dosyanın bu işlemedeki değişikliklerini görüntüle
commit.operations=İşlemler
commit.revert=Geri Al
@@ -1410,6 +1484,8 @@ issues.filter_milestones=Kilometre Taşı Süzgeci
issues.filter_projects=Projeyi Süz
issues.filter_labels=Etiket Süzgeci
issues.filter_reviewers=Gözden Geçiren Süzgeci
+issues.filter_no_results=Sonuç yok
+issues.filter_no_results_placeholder=Arama filtrelerinizi ayarlamayı deneyin.
issues.new=Yeni Konu
issues.new.title_empty=Başlık boş olamaz
issues.new.labels=Etiketler
@@ -1427,6 +1503,7 @@ issues.new.clear_milestone=Kilometre Taşlarını Temizle
issues.new.assignees=Atananlar
issues.new.clear_assignees=Atamaları Temizle
issues.new.no_assignees=Atanan KiÅŸi Yok
+issues.new.no_reviewers=Gözden geçiren yok
issues.new.blocked_user=Konu oluşturulamıyor, depo sahibi tarafından engellenmişsiniz.
issues.edit.already_changed=Konuya yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
issues.edit.blocked_user=İçerik düzenlenemiyor, gönderen veya depo sahibi tarafından engellenmişsiniz.
@@ -1483,9 +1560,14 @@ issues.filter_project=Proje
issues.filter_project_all=Tüm projeler
issues.filter_project_none=Proje yok
issues.filter_assignee=Atanan
+issues.filter_assignee_no_assignee=Hiç kimseye atanmamış
+issues.filter_assignee_any_assignee=Herhangi bir kimseye atanmış
issues.filter_poster=Yazar
+issues.filter_user_placeholder=Kullanıcıları ara
+issues.filter_user_no_select=Tüm kullanıcılar
issues.filter_type=Tür
issues.filter_type.all_issues=Tüm konular
+issues.filter_type.all_pull_requests=Tüm değişiklik istekleri
issues.filter_type.assigned_to_you=Size atanan
issues.filter_type.created_by_you=Sizin oluşturduklarınız
issues.filter_type.mentioning_you=Sizden bahsedilen
@@ -1577,12 +1659,14 @@ issues.save=Kaydet
issues.label_title=Etiket adı
issues.label_description=Etiket açıklaması
issues.label_color=Etiket rengi
+issues.label_color_invalid=Geçersiz renk
issues.label_exclusive=Özel
issues.label_archive=Etiketi ArÅŸivle
issues.label_archived_filter=Arşivlenmiş etiketleri göster
issues.label_archive_tooltip=Arşivlenmiş etiketler, etiket araması yapılırken varsayılan olarak önerilerin dışında tutuluyor.
issues.label_exclusive_desc=<code>Kapsam/öğe</code> etiketini, diğer <code>kapsam/</code> etiketleriyle ayrışık olacak şekilde adlandırın.
issues.label_exclusive_warning=Çakışan kapsamlı etiketler, bir konu veya değişiklik isteği etiketleri düzenlenirken kaldırılacaktır.
+issues.label_exclusive_order_tooltip=Aynı bağlamdaki özel etiketler bu sayısal sıralamaya göre sıralanacaktır.
issues.label_count=%d etiket
issues.label_open_issues=%d açık konu
issues.label_edit=Düzenle
@@ -1627,12 +1711,25 @@ issues.delete.title=Bu konu silinsin mi?
issues.delete.text=Bu konuyu gerçekten silmek istiyor musunuz? (Bu işlem tüm içeriği kalıcı olarak silecektir. Arşivde tutma niyetiniz varsa silmek yerine kapatmayı düşünün)
issues.tracker=Zaman Takibi
-
+issues.timetracker_timer_start=Zamanlayıcıyı başlat
+issues.timetracker_timer_stop=Zamanlayıcıyı durdur
+issues.timetracker_timer_discard=Zamanlayıcıyı kaldır
+issues.timetracker_timer_manually_add=Zaman Ekle
+
+issues.time_estimate_set=Tahmini zaman ayarla
+issues.time_estimate_display=Tahmin: %s
+issues.change_time_estimate_at=zaman tahmini deÄŸiÅŸtirildi: <b>%[1]s</b>%[2]s
+issues.remove_time_estimate_at=zaman tahmini %s kaldırıldı
+issues.time_estimate_invalid=Zaman tahmini biçimi hatalı
issues.tracker_auto_close=Bu konu kapatıldığında zamanlayıcı otomatik olarak durur
issues.tracking_already_started=`<a href="%s">başka bir konuda</a> zaten zaman izleyici başlattınız!`
+issues.stop_tracking=Zamanlayıcıyı Durdur
+issues.stop_tracking_history=<b>%[1]s</b> %[2]s için çalıştı
+issues.cancel_tracking=Yoksay
issues.cancel_tracking_history=`%s zaman takibini iptal etti`
issues.del_time=Bu zaman kaydını sil
issues.del_time_history=`%s harcanan zaman silindi`
+issues.add_time_manually=El ile Zaman Ekle
issues.add_time_hours=Saat
issues.add_time_minutes=Dakika
issues.add_time_sum_to_small=Zaman girilmedi.
@@ -1692,6 +1789,7 @@ issues.dependency.add_error_dep_not_same_repo=Her iki konu da aynı depoda olmal
issues.review.self.approval=Kendi değişiklik isteğinizi onaylayamazsınız.
issues.review.self.rejection=Kendi deÄŸiÅŸiklik isteÄŸinizde deÄŸiÅŸiklik isteyemezsiniz.
issues.review.approve=%s bu değişiklikleri onayladı
+issues.review.comment=%s incelendi
issues.review.dismissed=%s incelemesini %s reddetti
issues.review.dismissed_label=Reddedildi
issues.review.left_comment=bir yorum yaptı
@@ -1716,7 +1814,12 @@ issues.review.hide_resolved=Çözülenleri gizle
issues.review.resolve_conversation=Konuşmayı çöz
issues.review.un_resolve_conversation=Konuşmayı çözme
issues.review.resolved_by=bu konuşmayı çözümlenmiş olarak işaretledi
-issues.review.commented=Yorum Yap
+issues.review.commented=Yorum
+issues.review.official=Onaylandı
+issues.review.requested=İnceleme bekliyor
+issues.review.rejected=DeÄŸiÅŸiklik istendi
+issues.review.stale=Onaydan sonra güncellendi
+issues.review.unofficial=Sayılmamış onay
issues.assignee.error=Beklenmeyen bir hata nedeniyle tüm atananlar eklenmedi.
issues.reference_issue.body=Gövde
issues.content_history.deleted=silindi
@@ -1784,11 +1887,14 @@ pulls.add_prefix=<strong>%s</strong> ön ekini ekle
pulls.remove_prefix=<strong>%s</strong> ön ekini kaldır
pulls.data_broken=Bu değişiklik isteği, çatallama bilgilerinin eksik olması nedeniyle bozuldu.
pulls.files_conflicted=Bu değişiklik isteğinde, hedef dalla çakışan değişiklikler var.
+pulls.is_checking=Birleştirme çakışması denetleniyor ...
pulls.is_ancestor=Bu dal zaten hedef dalda mevcut. BirleÅŸtirilecek bir ÅŸey yok.
pulls.is_empty=Bu daldaki değişiklikler zaten hedef dalda mevcut. Bu boş bir işleme olacaktır.
pulls.required_status_check_failed=Bazı gerekli denetimler başarılı olmadı.
pulls.required_status_check_missing=Gerekli bazı kontroller eksik.
pulls.required_status_check_administrator=Yönetici olarak, bu değişiklik isteğini yine de birleştirebilirsiniz.
+pulls.blocked_by_approvals=Bu değişiklik isteğinin henüz yeterli onayı yok. %d onay var, %d onay gerekiyor.
+pulls.blocked_by_approvals_whitelisted=Bu değişiklik isteği henüz yeterince onaya sahip değil. İzinli kullanıcılar veya takımlar %d/%d onay verdi.
pulls.blocked_by_rejection=Bu değişiklik isteğinde resmi bir gözden geçiren tarafından istenen değişiklikler var.
pulls.blocked_by_official_review_requests=Bu deÄŸiÅŸiklik isteÄŸinde resmi inceleme istekleri var.
pulls.blocked_by_outdated_branch=Bu değişiklik isteği eskidiği için engellendi.
@@ -1830,7 +1936,9 @@ pulls.unrelated_histories=Birleştirme Başarısız: Birleştirme başlığı ve
pulls.merge_out_of_date=Birleştirme Başarısız: Birleştirme oluşturulurken, taban güncellendi. İpucu: Tekrar deneyin.
pulls.head_out_of_date=Birleştirme Başarısız: Birleştirme oluşturulurken, ana güncellendi. İpucu: Tekrar deneyin.
pulls.has_merged=Başarısız: Değişiklik isteği birleştirildi, yeniden birleştiremez veya hedef dalı değiştiremezsiniz.
+pulls.push_rejected=Gönderme Başarısız Oldu: Gönderme reddedildi. Bu depo için Git İstemcilerini inceleyin.
pulls.push_rejected_summary=Tam Red Mesajı
+pulls.push_rejected_no_message=Gönderme Başarısız Oldu: Gönderme reddedildi, ancak uzak bir mesaj yoktu. Bu depo için Git İstemcilerini inceleyin
pulls.open_unmerged_pull_exists=`Aynı özelliklere sahip bekleyen bir değişiklik isteği (#%d) olduğundan yeniden açma işlemini gerçekleştiremezsiniz.`
pulls.status_checking=Bazı denetlemeler beklemede
pulls.status_checks_success=Tüm denetlemeler başarılı oldu
@@ -2029,6 +2137,7 @@ contributors.contribution_type.deletions=Silmeler
settings=Ayarlar
settings.desc=Ayarlar, deponun ayarlarını yönetebileceğiniz yerdir
settings.options=Depo
+settings.public_access=Herkese Açık Erişim
settings.collaboration=Katkıcılar
settings.collaboration.admin=Yönetici
settings.collaboration.write=Yazma
@@ -2249,7 +2358,6 @@ settings.event_repository=Depo
settings.event_repository_desc=Depo oluÅŸturuldu veya silindi.
settings.event_header_issue=Konu Olayları
settings.event_issues=Konular
-settings.event_issues_desc=Konu açıldı, kapatıldı, yeniden açıldı veya düzenlendi.
settings.event_issue_assign=Konu Atandı
settings.event_issue_assign_desc=Konu atandı veya atanmadı.
settings.event_issue_label=Konu Etiketlendi
@@ -2260,7 +2368,6 @@ settings.event_issue_comment=Konu Yorumu
settings.event_issue_comment_desc=Konu yorumu eklendiğinde, düzenlendiğinde veya silindiğinde.
settings.event_header_pull_request=Değişiklik İsteği Olayları
settings.event_pull_request=İstek Çek
-settings.event_pull_request_desc=Değişiklik isteği açıldı, kapatıldı, yeniden açıldı veya düzenlendi.
settings.event_pull_request_assign=Değişiklik İsteği Atandı
settings.event_pull_request_assign_desc=Değişiklik isteği atanmış veya atanmamış.
settings.event_pull_request_label=Değişiklik İsteği Etiketlendi
@@ -2280,6 +2387,7 @@ settings.event_pull_request_merge=Değişiklik İsteği Birleştirme
settings.event_package=Paket
settings.event_package_desc=Bir depoda paket oluÅŸturuldu veya silindi.
settings.branch_filter=Dal filtresi
+settings.branch_filter_desc=Gönderme, dal oluşturma ve dal silme olayları için glob deseni olarak belirtilen dal beyaz listesi. Boşsa veya <code>*</code> ise, tüm dallar için olaylar raporlanır. Sözdizimi için <a href="%[1]s">%[2]s</a> belgelerine bakın. Örnekler: <code>master</code>, <code>{master,release*}</code>.
settings.authorization_header=Yetkilendirme Başlığı
settings.authorization_header_desc=Mevcutsa isteklere yetkilendirme başlığı olarak eklenecektir. Örnekler: %s.
settings.active=Etkin
@@ -2666,15 +2774,13 @@ settings.visibility.private_shortname=Özel
settings.update_settings=Ayarları Güncelle
settings.update_setting_success=Organizasyon ayarları güncellendi.
-settings.change_orgname_prompt=Not: Organizasyon adını değiştirmek organizasyonunuzun URL'sini de değiştirecek ve eski ismi serbest bıracaktır.
-settings.change_orgname_redirect_prompt=Eski ad, talep edilene kadar yeniden yönlendirilecektir.
+
+
settings.update_avatar_success=Organizasyonun resmi güncellendi.
settings.delete=Organizasyonu Sil
settings.delete_account=Bu Organizasyonu Sil
settings.delete_prompt=Organizasyon kalıcı olarak kaldırılacaktır. Bu işlem <strong>GERİ ALINAMAZ</strong>!
settings.confirm_delete_account=Silmeyi Onaylıyorum
-settings.delete_org_title=Organizasyonu Sil
-settings.delete_org_desc=Bu organizasyon kalıcı olarak silinecektir. Devam edilsin mi?
settings.hooks_desc=Bu organizasyon altındaki <strong>tüm depolar</strong> için tetiklenecek webhook'lar ekle.
settings.labels_desc=Bu organizasyonun altındaki <strong>tüm depolar</strong> ile ilgili konularda kullanılabilecek etiketler ekleyin.
@@ -3525,12 +3631,13 @@ owner.settings.chef.keypair.description=Chef kütüğünde kimlik doğrulaması
secrets=Gizlilikler
description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz.
none=Henüz gizlilik yok.
-creation=Gizlilik Ekle
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Açıklama
creation.name_placeholder=küçük-büyük harfe duyarlı değil, alfanümerik karakterler veya sadece alt tire, GITEA_ veya GITHUB_ ile başlayamaz
creation.value_placeholder=Herhangi bir içerik girin. Baştaki ve sondaki boşluklar ihmal edilecektir.
-creation.success=Gizlilik "%s" eklendi.
-creation.failed=Gizlilik eklenemedi.
+
+
deletion=Gizliliği kaldır
deletion.description=Bir gizliliği kaldırma kalıcıdır ve geri alınamaz. Devam edilsin mi?
deletion.success=Gizlilik kaldırıldı.
diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini
index 3a6d1539fa..ebd178c87b 100644
--- a/options/locale/locale_uk-UA.ini
+++ b/options/locale/locale_uk-UA.ini
@@ -2,7 +2,9 @@ home=Головна
dashboard=Панель управліннÑ
explore=ОглÑд
help=Довідка
+logo=Логотип
sign_in=Увійти
+sign_in_with_provider=Увійдіть за допомогою %s
sign_in_or=або
sign_out=Вийти
sign_up=РеєÑтраціÑ
@@ -15,35 +17,50 @@ template=Шаблон
language=Мова
notifications=СповіщеннÑ
active_stopwatch=Трекер робочого чаÑу
+tracked_time_summary=ПідÑумок відÑтежуваного чаÑу на оÑнові фільтрів ÑпиÑку задач
create_new=Створити…
user_profile_and_more=Профіль Ñ– налаштуваннÑ…
signed_in_as=Увійшов Ñк
+enable_javascript=Ð”Ð»Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ цього Ñайту потрібен JavaScript.
toc=ЗміÑÑ‚
licenses=Ліцензії
return_to_gitea=ПовернутиÑÑ Ð´Ð¾ Gitea
+more_items=Більше елементів
username=Ім'Ñ ÐºÑ€Ð¸Ñтувача
email=ÐдреÑа електронної пошти
password=Пароль
-access_token=Токен ДоÑтупу
-re_type=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ
+access_token=Токен доÑтупу
+re_type=Підтвердити пароль
captcha=CAPTCHA
-twofa=Двофакторна авторизаціÑ
+twofa=Двофакторна автентифікаціÑ
twofa_scratch=Двофакторний одноразовий пароль
passcode=Код доÑтупу
+webauthn_insert_key=Ð’Ñтавте ключ безпеки
+webauthn_sign_in=ÐатиÑніть кнопку на вашому ключі безпеки. Якщо ваш ключ без фізичної кнопки, поновно вÑтавте ключ.
+webauthn_press_button=Будь лаÑка, натиÑніть кнопку на вашому ключі безпеки…
+webauthn_use_twofa=ВикориÑтовуйте двофакторний код із Вашого телефона
+webauthn_error=Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ ваш ключ безпеки.
+webauthn_unsupported_browser=Ваш браузер наразі не підтримує WebAuthn.
+webauthn_error_unknown=СталаÑÑ Ð½ÐµÐ²Ñ–Ð´Ð¾Ð¼Ð° помилка. Будь лаÑка, Ñпробуйте ще раз.
+webauthn_error_insecure=`WebAuthn підтримує тільки безпечні з’єднаннÑ. Ð”Ð»Ñ Ñ‚ÐµÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ HTTP, ви можете ÑкориÑтатиÑÑ "localhost" або "127.0.0.1"`
+webauthn_error_unable_to_process=Сервер не зміг обробити ваш запит.
+webauthn_error_duplicated=Ключ безпеки не підходить Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту. ПереконайтеÑÑ, що ключ ще не зареєÑтровано.
+webauthn_error_empty=Ви повинні вÑтановити назву Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ключа.
+webauthn_error_timeout=Ð§Ð°Ñ Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð°Ð½Ð¾, перш ніж ваш ключ було прочитано. Перезавантажте Ñторінку та Ñпробуйте ще раз.
webauthn_reload=Оновити
-repository=Репозиторій
+repository=Сховище
organization=ОрганізаціÑ
mirror=Дзеркало
issue_milestone=Етап
-new_repo=Ðовий репозиторій
+new_repo=Ðове Ñховище
new_migrate=Ðова міграціÑ
new_mirror=Ðове дзеркало
-new_fork=Ðовий репозиторій - копіÑ
new_org=Ðова організаціÑ
new_project=Ðовий проєкт
+new_project_column=Ðовий Ñтовпець
manage_org=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñми
admin_panel=Панель ÐдмініÑтратора
account_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу
@@ -53,7 +70,7 @@ your_starred=Обрані
your_settings=ÐалаштуваннÑ
all=УÑÑ–
-sources=ВлаÑні
+sources=Джерела
mirrors=Дзеркала
collaborative=Спільні
forks=Форки
@@ -65,303 +82,446 @@ milestones=Етапи
ok=OK
cancel=Відмінити
+retry=Повторіть Ñпробу
+rerun=ПерезапуÑтити
+rerun_all=ПерезапуÑтити вÑÑ– завданнÑ
save=Зберегти
add=Додати
add_all=Додати вÑе
remove=Видалити
remove_all=Видалити вÑе
+remove_label_str=`Видалити елемент "%s"`
edit=Редагувати
+view=ПереглÑнути
+test=ТеÑÑ‚
enabled=Увімкнено
disabled=Вимкнено
+locked=Заблоковано
copy=Копіювати
copy_url=Копіювати URL
+copy_hash=Копіювати хеш
+copy_content=Копіювати вміÑÑ‚
copy_branch=Копіювати назву гілки
+copy_path=Копіювати шлÑÑ…
copy_success=Скопійовано!
copy_error=Ðе вдалоÑÑ Ñкопіювати
+copy_type_unsupported=Цей тип файлу не можна Ñкопіювати
write=ПиÑати
preview=Попередній переглÑд
loading=ЗавантаженнÑ…
+files=Файли
error=Помилка
-error404=Сторінка, до Ñкої ви намагаєтеÑÑ Ð·Ð²ÐµÑ€Ð½ÑƒÑ‚Ð¸ÑÑ Ð°Ð±Ð¾ до <strong>, не Ñ–Ñнує</strong> або <strong>Ви не маєте права</strong> на Ñ—Ñ— переглÑд.
+error404=Сторінка, Ñку ви намагаєтеÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸, <strong>не Ñ–Ñнує</strong> або <strong>ви не маєте права</strong> на Ñ—Ñ— переглÑд.
+error503=Сервер не зміг виконати ваш запит. Будь лаÑка, Ñпробуйте пізніше.
+go_back=Ðазад
+invalid_data=ÐедійÑні дані: %v
never=Ðіколи
+unknown=Ðевідомо
+rss_feed=Стрічка RSS
+pin=Закріпити
+unpin=Відкріпити
+artifacts=Ðртефакти
+expired=ПроÑтрочено
+confirm_delete_artifact=Справді видалити артефакт '%s' ?
-archived=Ðрхівовані
+archived=Ðрхівовано
-concept_code_repository=Репозиторій
+concept_code_repository=Сховище
concept_user_organization=ОрганізаціÑ
+show_timestamps=Показувати чаÑові мітки
+show_log_seconds=Показувати Ñекунди
+show_full_screen=Показати на веÑÑŒ екран
+download_logs=Завантажити журнали
+confirm_delete_selected=Підтверджуєте Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… вибраних елементів?
name=Ðазва
+value=ЗначеннÑ
+readme=Файл readme
filter=Фільтр
-filter.is_archived=Ðрхівовані
+filter.clear=ОчиÑтити фільтр
+filter.is_archived=Ðрхівовано
+filter.not_archived=Ðе архівовано
+filter.is_fork=Відгалужено
+filter.not_fork=Ðе відгалужено
+filter.is_mirror=Віддзеркалено
+filter.not_mirror=Ðе віддзеркалено
filter.is_template=Шаблон
-filter.public=Публічний
+filter.not_template=Ðе шаблон
+filter.public=Публічна
filter.private=Приватний
+no_results_found=Ðічого не знайдено.
+internal_error_skipped=ТрапилаÑÑŒ Ð²Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°, але пропущена: %s
[search]
+search=Пошук...
+type_tooltip=Тип пошуку
+fuzzy=Ðеточний
+fuzzy_tooltip=Включити результати, Ñкі також близько відповідають пошуковому запиту
+words=Слова
+words_tooltip=Включати тільки результати, Ñкі відповідають пошуковим Ñловам
+regexp=РегулÑрний вираз
+regexp_tooltip=Включати тільки результати, Ñкі відповідають пошуковому запиту регулÑрного виразу
+exact=Точний
+exact_tooltip=Включати тільки результати, Ñкі відповідають точному пошуковому запиту
+repo_kind=Пошук Ñховищ...
+user_kind=Пошук кориÑтувачів...
+org_kind=Пошук організацій...
+team_kind=Пошук команд...
+code_kind=Пошук коду...
+code_search_unavailable=Пошук коду наразі недоÑтупний. Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту.
+code_search_by_git_grep=Поточні результати пошуку коду надаютьÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¾ÑŽ "git grep". Результати можуть бути кращими, Ñкщо адмініÑтратор Ñайту увімкне індекÑатор Ñховища.
+package_kind=Пошук пакетів...
+project_kind=Пошук проєктів...
+branch_kind=Пошук гілок...
+tag_kind=Пошук за мітками...
+commit_kind=Пошук комітів...
+no_results=Ðе знайдено жодного збігу.
+issue_kind=Пошук задач...
+pull_kind=Пошук запитів на злиттÑ...
+keyword_search_unavailable=Пошук за ключовими Ñловами наразі недоÑтупний. Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту.
[aria]
+navbar=Панель навігації
+footer=Ðижній колонтитул
+footer.software=Про програмне забезпеченнÑ
+footer.links=ПоÑиланнÑ
[heatmap]
+number_of_contributions_in_the_last_12_months=%s внеÑків за оÑтанні 12 міÑÑців
+no_contributions=Ðемає внеÑків
+less=Менше
+more=Більше
[editor]
+buttons.heading.tooltip=Додати заголовок
+buttons.bold.tooltip=Додати грубий текÑÑ‚
+buttons.italic.tooltip=Додати курÑивний текÑÑ‚
+buttons.quote.tooltip=Цитувати текÑÑ‚
+buttons.code.tooltip=Додати код
+buttons.link.tooltip=Додати поÑиланнÑ
+buttons.list.unordered.tooltip=Додати ÑпиÑок
+buttons.list.ordered.tooltip=Додати нумерований ÑпиÑок
+buttons.list.task.tooltip=Додати ÑпиÑок завдань
+buttons.table.add.tooltip=Додати таблицю
buttons.table.add.insert=Додати
+buttons.table.rows=РÑдки
+buttons.table.cols=Стовпці
+buttons.mention.tooltip=Згадати кориÑтувача або команду
+buttons.ref.tooltip=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° задачу або запит на злиттÑ
+buttons.switch_to_legacy.tooltip=ВикориÑтовувати заÑтарілий редактор
+buttons.enable_monospace_font=Увімкнути моноширинний шрифт
+buttons.disable_monospace_font=Вимкнути моноширинний шрифт
[filter]
+string.asc=Р- Я
+string.desc=Я - Ð
[error]
occurred=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°
+report_message=Якщо ви вважаєте, що це помилка Gitea, будь лаÑка, Ñпробуйте відшукати відповідну проблему на <a href="https://github.com/go-gitea/gitea/issues">GitHub</a> або Ñтворіть нову, Ñкщо необхідно.
+not_found=Ціль не знайдено.
network_error=Помилка мережі
[startpage]
app_desc=Зручний влаÑний ÑÐµÑ€Ð²Ñ–Ñ Ñ…Ð¾Ñтингу репозиторіїв Git
install=Легко вÑтановити
+install_desc=ПроÑто <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">запуÑтіть двійковий файл</a> Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— платформи, ÑкориÑтайтеÑÑ <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a>, або вÑтановіть <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">ÑиÑтемою ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°ÐºÑƒÐ½ÐºÐ°Ð¼Ð¸</a>.
platform=ПлатформонезалежніÑть
+platform_desc=Gitea запуÑкаєтьÑÑ Ð±ÑƒÐ´ÑŒ-де, де <a target=«_blank» rel=«noopener noreferrer» href=«%s»>Go</a> може компілюватиÑÑŒ: на Windows, macOS, Linux, ARM тощо. Виберіть платформу, Ñку любите!
lightweight=ÐевибагливіÑть
-lightweight_desc=Gitea має низькі вимоги до реÑурÑів та може працювати на недорогому Raspberry Pi. Збережіть Ñвою машину енергію!
+lightweight_desc=Gitea має мінімальні вимоги Ñ– може працювати на недорогому Raspberry Pi. Заощаджуйте реÑурÑи вашої машини!
license=Відкритий вихідний код
[install]
install=Ð’ÑтановленнÑ
+installing_desc=Ð’ÑтановленнÑ, будь лаÑка, зачекайте...
title=Початкова конфігураціÑ
-docker_helper=Якщо ви запуÑкаєте Gitea вÑередині Docker, будь лаÑка уважно прочитайте <a target="_blank" rel="noopener" href="%s">документацію</a> перед тим, Ñк щоÑÑŒ змінити на цій Ñторінці.
+docker_helper=Якщо ви запуÑкаєте Gitea у Docker, будь лаÑка, прочитайте <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a> перед тим, Ñк змінювати будь-Ñкі налаштуваннÑ.
+require_db_desc=Gitea потребує MySQL, PostgreSQL, MSSQL, SQLite3 або TiDB (протокол MySQL).
db_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð°Ð·Ð¸ даних
db_type=Тип бази даних
host=ХоÑÑ‚
user=Ім'Ñ ÐºÑ€Ð¸Ñтувача
password=Пароль
-db_name=Ім'Ñ Ð±Ð°Ð·Ð¸ даних
+db_name=Ðазва бази даних
db_schema=Схема
-db_schema_helper=Залиште пуÑтим Ð´Ð»Ñ Ð±Ð°Ð·Ð¸ даних за замовчуваннÑм ("публічна").
+db_schema_helper=Залиште пуÑтим Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð¾Ð²Ð¾Ñ— Ñхеми бази даних ("публічна").
ssl_mode=SSL
path=ШлÑÑ…
-sqlite_helper=ШлÑÑ… до файлу Ð´Ð»Ñ Ð±Ð°Ð·Ð¸ даних SQLite3.<br>Введіть абÑолютний шлÑÑ…, Ñкщо ви запуÑкаєте GÑ–tea Ñк ÑервіÑ.
+sqlite_helper=ШлÑÑ… до файлу бази даних SQLite3.<br>Введіть абÑолютний шлÑÑ…, Ñкщо ви запуÑкаєте GÑ–tea Ñк ÑервіÑ.
reinstall_error=Ви намагаєтеÑÑ Ð²Ñтановити в наÑвну базу даних Gitea
reinstall_confirm_message=Повторне вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð² наÑвну базу даних Gitea може Ñпричинити багато проблем. Ð’ більшоÑті випадків, ви повинні викориÑтовувати Ñвій наÑвний "app.ini" Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку Gitea. Якщо ви знаєте, що робите, Ñпробуйте наÑтупне:
-reinstall_confirm_check_1=Дані зашифровані з викориÑтаннÑм SECRET_KEY з app.ini можуть бути втрачені: кориÑтувачі не зможуть увійти з 2FA/OTP Ñ– дзеркала можуть працювати некоректно. Ð’Ñтановлюючи цей прапорець, ви підтверджуєте, що в поточному файлі app.ini вказано правильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ SECRET_KEY.
-reinstall_confirm_check_2=Репозиторії та Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð½ÐµÐ¾Ð±Ñ…Ñ–Ð´Ð½Ð¾ повторно Ñинхронізувати. Ð’Ñтановлюючи цей прапорець, ви підтверджуєте, що ви Ñинхронізуватимете хуки репозиторіїв та authorized_keys вручну. Ви підтверджуєте, що Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ Ñ– дзеркала Ñ” правильними.
-reinstall_confirm_check_3=Ви підтверджуєте, що повніÑтю впевнені в тому, що Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ екземплÑра Gitea вказано правильне Ñ€Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ app.ini та екземплÑÑ€ Ñлід вÑтановити повторно. Ви підтверджуєте, що уÑвідомлюєте вищенаведені ризики.
+reinstall_confirm_check_1=Дані, зашифровані за допомогою SECRET_KEY в app.ini, можуть бути втрачені: кориÑтувачі не зможуть увійти за допомогою 2FA/OTP, а дзеркала можуть працювати некоректно. Ð’Ñтановивши цей прапорець, ви підтверджуєте, що поточний файл app.ini міÑтить правильний SECRET_KEY.
+reinstall_confirm_check_2=Можливо, потрібно буде повторно Ñинхронізувати Ñховища та налаштуваннÑ. Ð’Ñтановивши цей прапорець, ви підтверджуєте, що будете реÑинхронізувати хуки Ð´Ð»Ñ Ñховищ Ñ– файл authorized_keys вручну. Ви підтверджуєте, що забезпечите правильніÑть налаштувань Ñховища Ñ– дзеркала.
+reinstall_confirm_check_3=Ви підтверджуєте, що абÑолютно впевнені, що Gitea працює з правильним розташуваннÑм файлу app.ini, Ñ– що вам потрібно перевÑтановити програму. Ви підтверджуєте, що уÑвідомлюєте вищевказані ризики.
err_empty_db_path=ШлÑÑ… до файлу бази даних SQLite3 не може бути порожнім.
-no_admin_and_disable_registration=Ви не можете вимкнути реєÑтрацію до ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора.
+no_admin_and_disable_registration=Ви не можете вимкнути реєÑтрацію без ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора.
err_empty_admin_password=Пароль адмініÑтратора не може бути порожнім.
err_empty_admin_email=Електронна адреÑа адмініÑтратора не може бути порожньою.
-err_admin_name_is_reserved=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача-адмініÑтратора - ім'Ñ Ð·Ð°Ñ€ÐµÐ·ÐµÑ€Ð²Ð¾Ð²Ð°Ð½Ðµ
-err_admin_name_pattern_not_allowed=Ім'Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора недійÑне, це ім'Ñ Ð¿Ñ–Ð´Ð¿Ð°Ð´Ð°Ñ” під зарезервований шаблон
-err_admin_name_is_invalid=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача-адмініÑтратора
+err_admin_name_is_reserved=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача адмініÑтратора - ім'Ñ Ð·Ð°Ñ€ÐµÐ·ÐµÑ€Ð²Ð¾Ð²Ð°Ð½Ð¾
+err_admin_name_pattern_not_allowed=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача адмініÑтратора недійÑне, воно відповідає зарезервованому шаблону
+err_admin_name_is_invalid=ÐедійÑне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача адмініÑтратора
general_title=Загальні налаштуваннÑ
app_name=Ðазва Ñайту
app_name_helper=Тут ви можете ввеÑти назву Ñвоєї компанії.
-repo_path=Кореневий шлÑÑ… репозиторіÑ
-repo_path_helper=Ð’ÑÑ– вилучені Git репозиторії будуть збережені в цей каталог.
-lfs_path=Кореневої шлÑÑ… Git LFS
-lfs_path_helper=У цій папці будуть зберігатиÑÑ Ñ„Ð°Ð¹Ð»Ð¸ Git LFS. Залиште порожнім, щоб вимкнути LFS.
-run_user=ЗапуÑк від імені КориÑтувача
+repo_path=Кореневий шлÑÑ… Ñховища
+repo_path_helper=До цього каталогу буде збережено віддалені Ñховища Git.
+lfs_path=Кореневий шлÑÑ… Git LFS
+lfs_path_helper=У цій теці будуть зберігатиÑÑ Ñ„Ð°Ð¹Ð»Ð¸ Git LFS. Залиште порожнім, щоб вимкнути.
+run_user=Виконати Ñк
+run_user_helper=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача операційної ÑиÑтеми, від імені Ñкого запуÑкаєтьÑÑ Gitea. Зауважте, що цей кориÑтувач повинен мати доÑтуп до кореневого шлÑху Ñховища.
domain=Домен Ñервера
-domain_helper=Домен або адреÑа хоÑта Ñервера.
+domain_helper=Домен або хоÑÑ‚-адреÑа Ñервера.
ssh_port=Порт SSH Ñервера
ssh_port_helper=Ðомер порту, Ñкий викориÑтовує SSH Ñервер. Залиште порожнім, щоб вимкнути SSH.
http_port=Gitea HTTP порт
-http_port_helper=Ðомер порту, Ñкий буде проÑлуховуватиÑÑ Giteas веб-Ñервером.
+http_port_helper=Ðомер порту, Ñкий буде проÑлуховуватиÑÑ Ñервером Giteas.
app_url=Базова URL-адреÑа Gitea
-app_url_helper=Базова адреÑа Ð´Ð»Ñ HTTP(S) ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· URL та повідомлень електронної пошти.
-log_root_path=ШлÑÑ… до лог файлу
-log_root_path_helper=Файли журналу будуть запиÑані в цей каталог.
-
-optional_title=Додаткові налаштуваннÑ
-email_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Email
-smtp_addr=SMTP хоÑÑ‚
-smtp_port=SMTP порт
-smtp_from=ВідправлÑти Email від імені
-smtp_from_helper=Електронна пошта Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð² GÑ–tea. Введіть звичайну електронну адреÑу або викориÑтовуйте формат: "Ім'Ñ" <email@example.com>.
-mailer_user=SMTP Ім'Ñ ÐºÑ€Ð¸Ñтувача
-mailer_password=SMTP Пароль
-register_confirm=Потрібно підтвердити електронну пошту Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації
+app_url_helper=Базова адреÑа Ð´Ð»Ñ URL-Ð°Ð´Ñ€ÐµÑ ÐºÐ»Ð¾Ð½Ñ–Ð² HTTP(S) та Ñповіщень електронною поштою.
+log_root_path=ШлÑÑ… до журналу
+log_root_path_helper=Файли журналу будуть запиÑані в цю теку.
+
+optional_title=Ðеобов'Ñзкові налаштуваннÑ
+email_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти
+smtp_addr=Сервер SMTP
+smtp_port=Порт SMTP
+smtp_from=Відправити лиÑта від імені
+smtp_from_invalid=ÐдреÑа "ÐадіÑлати лиÑта Ñк" недійÑна
+smtp_from_helper=ÐдреÑа електронної пошти, Ñку буде викориÑтовувати Gitea. Введіть звичайну адреÑу електронної пошти або викориÑтовуйте формат «Ім'Ñ» <email@example.com>.
+mailer_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача SMTP
+mailer_password=Пароль SMTP
+register_confirm=Вимагати Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації
mail_notify=Увімкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою
-server_service_title=Сервер Ñ– Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… Ñлужб
+server_service_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñервера Ñ– Ñторонніх ÑервіÑів
offline_mode=Увімкнути локальний режим
-offline_mode_popup=Відключити Ñторонні мережі доÑтавки контенту Ñ– обÑлуговувати вÑÑ– реÑурÑи локально.
+offline_mode_popup=Вимкнути Ñторонні мережі доÑтавки контенту та обÑлуговувати вÑÑ– реÑурÑи локально.
disable_gravatar=Вимкнути Gravatar
-disable_gravatar_popup=Відключити Gravatar Ñ– Ñторонні джерела аватарів. Якщо кориÑтувач не завантажить аватар локально то за замовчуваннÑм буде викориÑтовуватиÑÑ Ñтандартний аватар.
-federated_avatar_lookup=Увімкнути федеративні аватари
-federated_avatar_lookup_popup=Увімкнути зовнішний Ðватар за допомогою Libravatar.
-disable_registration=Вимкнути ÑамоÑтійну реєÑтрацію
-disable_registration_popup=Вимкнути ÑамоÑтійну реєÑтрацію кориÑтувачів, тільки адмініÑтратор може Ñтворювати нові облікові запиÑи.
+disable_gravatar_popup=Вимкнути Gravatar та Ñторонні джерела аватарок. Якщо кориÑтувач локально не завантажить аватар, буде викориÑтовуватиÑÑ Ñ‚Ð¸Ð¿Ð¾Ð²Ð¸Ð¹ аватар.
+federated_avatar_lookup=Увімкнути зовнішні аватари
+federated_avatar_lookup_popup=Увімкнути пошук об'єднаних аватарів за допомогою Libravatar.
+disable_registration=Вимкнути реєÑтрацію
+disable_registration_popup=Вимкнути реєÑтрацію кориÑтувачів, тільки адмініÑтратор може Ñтворювати нові облікові запиÑи.
allow_only_external_registration_popup=Дозволити реєÑтрацію тільки через Ñторонні ÑервіÑи
-openid_signin=Увімкнути реєÑтрацію за допомогою OpenID
-openid_signin_popup=Увімкнути вхід за допомогою OpenID.
+openid_signin=Увімкнути вхід за допомогою OpenID
+openid_signin_popup=Увімкнути вхід кориÑтувачів за допомогою OpenID.
openid_signup=Увімкнути ÑамоÑтійну реєÑтрацію за допомогою OpenID
openid_signup_popup=Увімкнути ÑамореєÑтрацію кориÑтувачів на оÑнові OpenID.
-enable_captcha=Увімкнути CAPTCHA при реєÑтрації
-enable_captcha_popup=Вимагати перевірку CAPTCHA при ÑамоÑтійній реєÑтрації кориÑтувача.
+enable_captcha=Увімкнути CAPTCHA Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації
+enable_captcha_popup=Вимагати CAPTCHA Ð´Ð»Ñ ÑамореєÑтрації кориÑтувачів.
require_sign_in_view=Вимагати авторизації Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду Ñторінок
+require_sign_in_view_popup=Обмежити доÑтуп до Ñторінки лише Ð´Ð»Ñ Ð·Ð°Ñ€ÐµÑ”Ñтрованих кориÑтувачів. Відвідувачі побачать тільки Ñторінки входу Ñ– реєÑтрації.
admin_setting_desc=Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора необов'Ñзково. Перший зареєÑтрований кориÑтувач автоматично Ñтає адмініÑтратором.
admin_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора
admin_name=Ім'Ñ ÐºÑ€Ð¸Ñтувача ÐдмініÑтратора
admin_password=Пароль
-confirm_password=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ
+confirm_password=Підтвердити пароль
admin_email=ÐдреÑа електронної пошти
-install_btn_confirm=Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Gitea
-test_git_failed=Ðе в змозі перевірити 'git' команду: %v
+install_btn_confirm=Ð’Ñтановити Gitea
+test_git_failed=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ команду 'git': %v
sqlite3_not_available=Ð¦Ñ Ð²ÐµÑ€ÑÑ–Ñ Gitea не підтримує SQLite3. Будь лаÑка, завантажте офіційну бінарну верÑÑ–ÑŽ з %s (не верÑÑ–ÑŽ gobuild).
-invalid_db_setting=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð°Ð·Ð¸ даних Ñ” некоректними: %v
-invalid_repo_path=Помилковий шлÑÑ… до ÐºÐ¾Ñ€ÐµÐ½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ: %v
-invalid_app_data_path=Ðекоректний шлÑÑ… до даних програми: %v
-run_user_not_match=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача 'run as' не Ñ” поточним ім'Ñм кориÑтувача: %s -> %s
-internal_token_failed=Ðе вдалоÑÑ Ð·Ð³ÐµÐ½ÐµÑ€ÑƒÐ²Ð°Ñ‚Ð¸ внутрішній токен: %v
-secret_key_failed=Ðе вдалоÑÑ Ð·Ð³ÐµÐ½ÐµÑ€ÑƒÐ²Ð°Ñ‚Ð¸ Ñекретний ключ: %v
-save_config_failed=Ðе в змозі зберегти конфігурацію: %v
-invalid_admin_setting=ÐеприпуÑтимі Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора: %v
-invalid_log_root_path=ÐеприпуÑтимий шлÑÑ… Ð´Ð»Ñ Ð»Ð¾Ð³Ñ–Ð²: %v
-default_keep_email_private=Приховати адреÑу електронної пошти за замовчуваннÑм
-default_keep_email_private_popup=Приховати адреÑу електронної пошти нових облікових запиÑів за замовчуваннÑм.
-default_allow_create_organization=Дозволити ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ð¹ за замовчуваннÑм
-default_allow_create_organization_popup=Дозволити новим обліковим запиÑам кориÑтувачів Ñтворювати організації за замовчуваннÑм.
-default_enable_timetracking=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу за замовчуваннÑм
-default_enable_timetracking_popup=Включити відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу Ð´Ð»Ñ Ð½Ð¾Ð²Ð¸Ñ… репозиторіїв за замовчуваннÑм.
+invalid_db_setting=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð°Ð·Ð¸ даних недійÑні: %v
+invalid_db_table=База даних таблиці "%s" Ñ” недійÑною: %v
+invalid_repo_path=Кореневий шлÑÑ… до Ñховища невірний: %v
+invalid_app_data_path=ШлÑÑ… до даних додатка невірний: %v
+run_user_not_match=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача “Виконати Ñк†не Ñ” поточним ім'Ñм кориÑтувача: %s -> %s
+internal_token_failed=Ðе вдалоÑÑ Ñтворити внутрішній токен: %v
+secret_key_failed=Ðе вдалоÑÑ Ñтворити Ñекретний ключ: %v
+save_config_failed=Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ конфігурацію: %v
+invalid_admin_setting=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора Ñ” недійÑним: %v
+invalid_log_root_path=Ðеправильний шлÑÑ… до журналу: %v
+default_keep_email_private=Типово приховувати адреÑи електронної пошти
+default_keep_email_private_popup=Типово приховувати адреÑи електронної пошти нових облікових запиÑів.
+default_allow_create_organization=За замовчуваннÑм дозволити ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ð¹
+default_allow_create_organization_popup=За замовчуваннÑм дозволити новим кориÑтувачам Ñтворювати організації.
+default_enable_timetracking=За замовчуваннÑм увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу
+default_enable_timetracking_popup=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу Ð´Ð»Ñ Ð½Ð¾Ð²Ð¸Ñ… Ñховищ за замовчуваннÑм.
no_reply_address=Прихований поштовий домен
-no_reply_address_helper=Доменне ім'Ñ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів із прихованою електронною адреÑою. Ðаприклад, ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача 'joe' буде входити в Git Ñк 'joe@noreply.example.org', Ñкщо Ð´Ð»Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð²Ð°Ð½Ð¾Ð³Ð¾ домену електронної пошти вÑтановлено 'noreply.example.org'.
+no_reply_address_helper=Доменне ім'Ñ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів із прихованою електронною адреÑою. Ðаприклад, ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача 'Joe' буде зображатиÑÑ Ð² Git Ñк 'joe@noreply.example.org', Ñкщо прихований домен електронної пошти вÑтановлено 'noreply.example.org'.
password_algorithm=Ðлгоритм Ñ…ÐµÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ
+invalid_password_algorithm=ÐедійÑний хеш-алгоритм паролÑ
+password_algorithm_helper=Ð’Ñтановіть алгоритм Ñ…ÐµÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ. Ðлгоритми мають різні вимоги та ÑтійкіÑть. Ðлгоритм argon2 Ñ” доÑить безпечним, але викориÑтовує багато пам'Ñті Ñ– може бути недоречним Ð´Ð»Ñ Ð¼Ð°Ð»Ð¸Ñ… ÑиÑтем.
+enable_update_checker=Увімкнути перевірку оновлень
+enable_update_checker_helper=Періодично перевірÑти наÑвніÑть нових верÑій, підключаючиÑÑŒ до gitea.io.
+env_config_keys=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ñередовища
+env_config_keys_prompt=ÐаÑтупні змінні Ñередовища також будуть заÑтоÑовані до вашого файлу конфігурації:
+config_write_file_prompt=Ці параметри будуть запиÑані в: %s
[home]
-uname_holder=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або Ел. пошта
+nav_menu=Меню навігації
+uname_holder=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або адреÑа електронної пошти
password_holder=Пароль
switch_dashboard_context=Переключити контекÑÑ‚ панелі управліннÑ
-my_repos=Репозиторії
-show_more_repos=Показати більше репозиторіїв…
-collaborative_repos=Спільні репозиторії
+my_repos=Сховища
+show_more_repos=Показати більше Ñховищ…
+collaborative_repos=Спільні Ñховища
my_orgs=Мої організації
my_mirrors=Мої дзеркала
view_home=ПереглÑнути %s
filter=Інші фільтри
-filter_by_team_repositories=Фільтрувати за репозиторіÑми команд
+filter_by_team_repositories=Фільтрувати за Ñховищами команд
feed_of=`Стрічка "%s"`
show_archived=Ðрхівовані
-show_both_archived_unarchived=Показано архівовані і не архівовані
+show_both_archived_unarchived=Показано архівовані і неархівовані
show_only_archived=Показано тільки архівовані
-show_only_unarchived=Показано тільки не архівовані
+show_only_unarchived=Показано тільки неархівовані
show_private=Приватні
show_both_private_public=Показано публічні та приватні
show_only_private=Показано тільки приватні
show_only_public=Показано тільки публічні
-issues.in_your_repos=Ð’ ваших репозиторіÑÑ…
+issues.in_your_repos=У ваших Ñховищах
+guide_title=Жодної активноÑті
+guide_desc=Ðаразі ви не Ñтежите за жодним Ñховищем або кориÑтувачем, тому нема чого відображати. Ви можете переглÑнути Ñховища або кориÑтувачів, Ñкі Ð²Ð°Ñ Ñ†Ñ–ÐºÐ°Ð²Ð»Ñть, за поÑиланнÑми нижче.
+explore_repos=ОглÑд Ñховищ
+explore_users=ОглÑд кориÑтувачів
+empty_org=Організацій поки що немає.
+empty_repo=Сховищ поки що немає.
[explore]
-repos=Репозиторії
+repos=Сховища
users=КориÑтувачі
organizations=Організації
+go_to=Перейти до
code=Код
code_last_indexed_at=ОÑтанні індекÑовані %s
+relevant_repositories_tooltip=Сховища, Ñкі Ñ” відгалуженими або не мають теми, піктограми й опиÑу, приховуютьÑÑ.
+relevant_repositories=Показано лише важливі Ñховища, <a href="%s">показати нефільтровані результати</a>.
[auth]
create_new_account=РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу
-disable_register_prompt=Вибачте, можливіÑть реєÑтрації відключена. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту.
+already_have_account=Вже зареєÑтровані?
+sign_in_now=Увійдіть зараз!
+disable_register_prompt=РеєÑтрацію вимкнено. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту.
disable_register_mail=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÑ”Ñтрації електронною поштою вимкнено.
+manual_activation_only=ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ—.
remember_me=Запам’Ñтати цей приÑтрій
+remember_me.compromised=Токен Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ більше не дійÑний, що може Ñвідчити про Ñкомпрометований обліковий запиÑ. Перевірте Ñвій обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° наÑвніÑть незвичайних дій.
forgot_password_title=Забув пароль
forgot_password=Забули пароль?
-must_change_password=Оновіть Ñвій пароль
-allow_password_change=Вимагати в кориÑтувача змінити пароль (рекомендуєтьÑÑ)
-reset_password_mail_sent_prompt=Електронний лиÑÑ‚ із підтвердженнÑм надіÑлано <b>%s</b>. Перевірте папку 'Вхідні' в межах наÑтупних %s, щоб завершити Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу.
+need_account=Потрібен обліковий запиÑ?
+sign_up_tip=Ви реєÑтруєте перший обліковий Ð·Ð°Ð¿Ð¸Ñ Ñƒ ÑиÑтемі, з правами адмініÑтратора. Будь лаÑка, уважно запам'Ñтайте Ñвоє ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача та пароль. Якщо ви Ñ—Ñ… забудете, звернітьÑÑ Ð´Ð¾ документації Gitea, щоб відновити обліковий запиÑ.
+sign_up_now=ЗареєÑтруватиÑÑ.
+sign_up_successful=Обліковий Ð·Ð°Ð¿Ð¸Ñ Ñтворено уÑпішно. Вітаю!
+confirmation_mail_sent_prompt_ex=Ðовий лиÑÑ‚ з підтвердженнÑм було надіÑлано на <b>%s</b>. Будь лаÑка, перевірте Ñвою поштову Ñкриньку протÑгом наÑтупних %s, щоб завершити Ð¿Ñ€Ð¾Ñ†ÐµÑ Ñ€ÐµÑ”Ñтрації. Якщо ви вказали невірну адреÑу електронної пошти, ви можете увійти ще раз Ñ– змінити Ñ—Ñ—.
+must_change_password=Оновити пароль
+allow_password_change=Вимагати від кориÑтувача змінити пароль (рекомендовано)
+reset_password_mail_sent_prompt=Ðа адреÑу <b>%s</b> було надіÑлано лиÑÑ‚ із підтвердженнÑм. Будь лаÑка, перевірте Ñвою поштову Ñкриньку протÑгом наÑтупних %s, щоб завершити Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу.
active_your_account=Ðктивувати обліковий запиÑ
account_activated=Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð°ÐºÑ‚Ð¸Ð²Ð¾Ð²Ð°Ð½Ð¾
-prohibit_login=Вхід заборонений
-resent_limit_prompt=Вибачте, ви вже запроÑили активацію по електронній пошті нещодавно. Будь лаÑка, зачекайте 3 хвилини, а потім Ñпробуйте ще раз.
-has_unconfirmed_mail=Привіт %s, у Ð²Ð°Ñ Ñ” непідтверджена електронна адреÑа (<b>%s </b>). Якщо ви не отримали електронний лиÑÑ‚ із підтвердженнÑм або вам потрібно надіÑлати новий, натиÑніть на кнопку нижче.
-resend_mail=ÐатиÑніть тут, щоб виÑлати лиÑÑ‚ активації знову
-email_not_associate=Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° пошта не пов'Ñзана ні з одним обліковим запиÑом.
-send_reset_mail=ÐадіÑлати електронний лиÑÑ‚ Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу
+prohibit_login=Вхід заборонено
+prohibit_login_desc=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту.
+resent_limit_prompt=Ви вже надÑилали запит на активацію нещодавно. Зачекайте 3 хвилини Ñ– Ñпробуйте ще раз.
+has_unconfirmed_mail=Привіт %s, у Ð²Ð°Ñ Ð½ÐµÐ¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð° адреÑа електронної пошти (<b>%s</b>). Якщо ви не отримали лиÑта з підтвердженнÑм або вам потрібно надіÑлати новий, будь лаÑка, натиÑніть кнопку нижче.
+change_unconfirmed_mail_address=Якщо ваша адреÑа електронної пошти Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації невірна, ви можете змінити Ñ—Ñ— тут Ñ– надіÑлати новий лиÑÑ‚ з підтвердженнÑм.
+resend_mail=ÐатиÑніть тут, щоб повторно надіÑлати лиÑÑ‚ з активацією
+email_not_associate=Ð¦Ñ Ð°Ð´Ñ€ÐµÑа електронної пошти не пов'Ñзана з жодним обліковим запиÑом.
+send_reset_mail=ÐадіÑлати лиÑÑ‚ Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу
reset_password=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу
-invalid_code=Цей код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний або закінчивÑÑ.
+invalid_code=Ваш код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний або його термін дії закінчивÑÑ.
+invalid_code_forgot_password=Ваш код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний або термін дії минув. ÐатиÑніть <a href="%s">тут</a>, щоб почати новий ÑеанÑ.
+invalid_password=Ваш пароль не збігаєтьÑÑ Ð· паролем, Ñкий викориÑтовувавÑÑ Ð¿Ñ€Ð¸ Ñтворенні облікового запиÑу.
reset_password_helper=Відновити обліковий запиÑ
+reset_password_wrong_user=Ви увійшли Ñк %s, але поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу призначене Ð´Ð»Ñ %s
password_too_short=Довжина Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð½Ðµ може бути меншою за %d Ñимволів.
-non_local_account=Ðелокальні акаунти не можуть змінити пароль через Gitea.
+non_local_account=Ðелокальні кориÑтувачі не можуть оновити Ñвій пароль через Ð²ÐµÐ±Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Gitea.
verify=Підтвердити
scratch_code=Одноразовий пароль
-use_scratch_code=ВикориÑтовувати одноразовий пароль
-twofa_scratch_used=Ви викориÑтовували одноразовий пароль. Ви були перенаправлені на Ñторінку налаштувань Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ñ–Ñ— нового коду або Ð²Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð²ÑƒÑ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації.
-twofa_passcode_incorrect=Ваш пароль Ñ” невірним. Якщо ви втратили приÑтрій, викориÑтовуйте ваш одноразовий пароль.
+use_scratch_code=СкориÑтатиÑÑŒ одноразовим паролем
+twofa_scratch_used=Ви ÑкориÑталиÑÑ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ð¼ паролем. Ð’Ð°Ñ Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¾ на Ñторінку налаштувань двофакторної автентифікації, щоб видалити приÑтрій або згенерувати новий одноразовий пароль.
+twofa_passcode_incorrect=Ваш пароль неправильний. Якщо ви загубили Ñвій приÑтрій, ÑкориÑтайтеÑÑ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ð¼ паролем.
twofa_scratch_token_incorrect=Ðевірний одноразовий пароль.
login_userpass=Увійти
login_openid=OpenID
oauth_signup_tab=ЗареєÑтрувати обліковий запиÑ
-oauth_signup_title=Повний новий обліковий запиÑ
-oauth_signup_submit=Повний обліковий запиÑ
-oauth_signin_tab=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° Ñ–Ñнуючий обліковий запиÑ
+oauth_signup_title=Завершити реєÑтрацію облікового запиÑу
+oauth_signup_submit=Поповнити обліковий запиÑ
+oauth_signin_tab=Прив'Ñзати до Ñ–Ñнуючого облікового запиÑу
oauth_signin_title=Увійдіть щоб авторизувати пов'Ñзаний обліковий запиÑ
oauth_signin_submit=Прив'Ñзати обліковий запиÑ
+oauth.signin.error.general=Під Ñ‡Ð°Ñ Ð¾Ð±Ñ€Ð¾Ð±ÐºÐ¸ запиту на авторизацію ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°: %s. Якщо Ñ†Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° повторитьÑÑ, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту.
+oauth.signin.error.access_denied=Запит на авторизацію відхилено.
+oauth.signin.error.temporarily_unavailable=Помилка авторизації, Ñервер автентифікації тимчаÑово недоÑтупний. Спробуйте пізніше.
openid_connect_submit=Під’єднатиÑÑ
openid_connect_title=ПідключитиÑÑ Ð´Ð¾ Ñ–Ñнуючого облікового запиÑу
-openid_connect_desc=Вибраний OpenID URI невідомий. Пов'Ñжіть його з новим обліковим запиÑом тут.
+openid_connect_desc=Обраний OpenID URI невідомий. Зв'Ñжіть його тут з новим обліковим запиÑом.
openid_register_title=Створити новий обліковий запиÑ
-openid_register_desc=Вибраний OpenID URI невідомий. Пов'Ñжіть йогоз новим обліковим запиÑом тут.
+openid_register_desc=Обраний OpenID URI невідомий. Зв'Ñжіть його тут з новим обліковим запиÑом.
+openid_signin_desc=Введіть Ñвій OpenID URI. Ðаприклад: alice.openid.example.org або https://openid.example.org/alice.
disable_forgot_password_mail=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу вимкнено, оÑкільки не налаштована електронна пошта. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту.
-disable_forgot_password_mail_admin=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу доÑтупне лише піÑÐ»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти. Будь лаÑка, налаштуйте ел. пошту Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу.
-email_domain_blacklisted=З вказаним email реєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð°.
+disable_forgot_password_mail_admin=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу доÑтупне лише за наÑвноÑті електронної пошти. Будь лаÑка, налаштуйте електронну пошту, щоб увімкнути Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу.
+email_domain_blacklisted=Ви не можете зареєÑтруватиÑÑ Ð· адреÑою електронної пошти.
authorize_application=Ðвторизувати програму
authorize_redirect_notice=Ð’Ð°Ñ Ð±ÑƒÐ´Ðµ переадреÑовано до %s, Ñкщо ви авторизуєте цю програму.
authorize_application_created_by=Ð¦Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð° Ñтворена %s.
-authorize_application_description=Якщо ви надаÑте цей доÑтуп, то він матиме доÑтуп до вÑÑ–Ñ… ваших даних облікового запиÑу, включаючи приватні репозиторії та організації.
-authorize_title=Ðвторизуйвати "%s" Ð´Ð»Ñ Ð´Ð¾Ñтупу до вашого облікового запиÑу?
+authorize_application_description=Якщо ви авторизуєте цю програму, їй буде надано дозвіл на Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð²Ñієї інформації вашого облікового запиÑу, включно з приватними Ñховищами та організаціÑми.
+authorize_title=Ðвторизувати "%s" Ð´Ð»Ñ Ð´Ð¾Ñтупу до вашого облікового запиÑу?
authorization_failed=Помилка авторизації
-sspi_auth_failed=Помилка SSPI-автентифікації
+authorization_failed_desc=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ, оÑкільки ми виÑвили недійÑний запит. ЗвернітьÑÑ Ð´Ð¾ розробника програми, Ñку ви намагалиÑÑ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·ÑƒÐ²Ð°Ñ‚Ð¸.
+sspi_auth_failed=Помилка автентифікації SSPI
+password_pwned=Пароль, Ñкий ви обрали, знаходитьÑÑ Ð² <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">ÑпиÑку викрадених паролів</a>, раніше викритих в публічних витоках даних. Спробуйте ще раз з іншим паролем Ñ– подумайте про зміну цього Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð´ÐµÑ–Ð½Ð´Ðµ.
password_pwned_err=Ðе вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ запит до HaveIBeenPwed
+last_admin=Ðе можна видалити оÑтаннього адмініÑтратора. Повинен бути хоча б один адмініÑтратор.
+signin_passkey=Увійти за допомогою ключа доÑтупу
+back_to_sign_in=ПовернутиÑÑ Ð´Ð¾ авторизації
[mail]
view_it_on=ПереглÑнути на %s
-link_not_working_do_paste=Ðе працює? Спробуйте Ñкопіювати та вÑтавити його в Ñвій браузер.
+reply=або надішліть відповідь безпоÑередньо на цей електронний лиÑÑ‚
+link_not_working_do_paste=Ðе працює? Спробуйте Ñкопіювати та вÑтавити його у браузер.
hi_user_x=Привіт <b>%s</b>,
activate_account=Будь лаÑка, активуйте ваш обліковий запиÑ
activate_account.title=%s, будь лаÑка, активуйте Ñвій обліковий запиÑ
activate_account.text_1=Привіт, <b>%[1]s</b>, дÑкуємо за реєÑтрацію на %[2]s!
-activate_account.text_2=Перейдіть за цим поÑиланнÑм, щоб активувати ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð² <b>%s</b>:
+activate_account.text_2=Будь лаÑка, перейдіть за наÑтупним поÑиланнÑм, щоб активувати Ñвій обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ñ€Ð¾Ñ‚Ñгом <b>%s</b>:
-activate_email=Підтвердить вашу адреÑу електронної пошти
-activate_email.text=Перейдіть за цим поÑиланнÑм, щоб підтвердити вашу електронну адреÑу в <b>%s</b>:
+activate_email=Підтвердіть адреÑу електронної пошти
+activate_email.title=%s, будь лаÑка, підтвердіть вашу адреÑу електронної пошти
+activate_email.text=Будь лаÑка, перейдіть за наÑтупним поÑиланнÑм протÑгом <b>%s</b>, щоб підтвердити Ñвою електронну адреÑу:
+register_notify=ЛаÑкаво проÑимо до %s
register_notify.title=%[1]s, лаÑкаво проÑимо до %[2]s
-register_notify.text_1=це ваша е-пошта Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÑ”Ñтрації Ð´Ð»Ñ %s!
+register_notify.text_1=це лиÑÑ‚ з підтвердженнÑм реєÑтрації на %s!
register_notify.text_2=Тепер ви можете увійти Ñк: %s.
-register_notify.text_3=Якщо цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð±ÑƒÐ»Ð¾ Ñтворено Ð´Ð»Ñ Ð²Ð°Ñ, будь лаÑка, Ñпочатку <a href="%s">вÑтановіть Ñвій пароль</a>.
+register_notify.text_3=Якщо цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ñтворено Ð´Ð»Ñ Ð²Ð°Ñ, будь лаÑка, Ñпершу <a href="%s">вÑтановіть пароль</a>.
-reset_password=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ облікового запиÑу
-reset_password.title=%s, ви відправили запит на Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу
-reset_password.text=Перейдіть за цим поÑиланнÑм, щоб відновити ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð² <b>%s</b>:
+reset_password=Відновити обліковий запиÑ
+reset_password.title=%s, ви надіÑлали запит на Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу
+reset_password.text=Щоб відновити обліковий запиÑ, перейдіть за наÑтупним поÑиланнÑм протÑгом <b>%s</b>:
register_success=РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ ÑƒÑпішна
-issue_assigned.pull=@%[1]s призначив вам запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[2]s в репозиторії %[3]s.
-issue_assigned.issue=@%[1]s призначив вам задачу %[2]s у репозиторії %[3]s.
+issue_assigned.pull=@%[1]s призначив вам запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[2]s в Ñховищі %[3]s.
+issue_assigned.issue=@%[1]s призначив вам задачу %[2]s в Ñховищі %[3]s.
issue.x_mentioned_you=<b>@%s</b> згадав ваÑ:
-issue.action.force_push=<b>%[1]s</b> force-pushed <b>%[2]s</b> з %[3]s в %[4]s.
-issue.action.push_1=<b>@%[1]s</b> надіÑлав %[3]d коміти %[2]s
-issue.action.push_n=<b>@%[1]s</b> відправив %[3]d коміти до %[2]s
-issue.action.close=<b>@%[1]s</b> закрито #%[2]d.
+issue.action.push_1=<b>@%[1]s</b> надіÑлав %[3]d коміт в %[2]s
+issue.action.push_n=<b>@%[1]s</b> надіÑлав %[3]d коміти до %[2]s
+issue.action.close=<b>@%[1]s</b> закрив #%[2]d.
issue.action.reopen=<b>@%[1]s</b> заново відкрив #%[2]d.
issue.action.merge=<b>@%[1]s</b> об'єднав #%[2]d до %[3]s.
-issue.action.approve=<b>@%[1]s</b> затвердили цей запит на злиттÑ.
-issue.action.reject=<b>@%[1]s</b> запитують зміни на цей запит на злиттÑ.
-issue.action.review=<b>@%[1]s</b> прокоментували цей запит на злиттÑ.
-issue.action.review_dismissed=<b>@%[1]s</b> відхилено оÑтанній відгук від %[2]s Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту на злиттÑ.
-issue.action.ready_for_review=<b>@%[1]s</b> позначили цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñк готовий до розглÑду.
-issue.action.new=<b>@%[1]s</b> Ñтворили #%[2]d.
+issue.action.approve=<b>@%[1]s</b> затвердив цей запит на злиттÑ.
+issue.action.reject=<b>@%[1]s</b> запроÑив зміни у цьому запиті на злиттÑ.
+issue.action.review=<b>@%[1]s</b> прокоментував цей запит на злиттÑ.
+issue.action.review_dismissed=<b>@%[1]s</b> відхилив оÑтанній відгук від %[2]s на цей запит на злиттÑ.
+issue.action.ready_for_review=<b>@%[1]s</b> позначив цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñк готовий до розглÑду.
+issue.action.new=<b>@%[1]s</b> Ñтворив #%[2]d.
issue.in_tree_path=Ð’ %s:
release.new.subject=%s в %s випущено
@@ -372,35 +532,40 @@ release.downloads=ЗвантаженнÑ:
release.download.zip=Вихідний код (ZIP)
release.download.targz=Вихідний код (TAR.GZ)
-repo.transfer.subject_to=%s бажає передати"%s" в %s
-repo.transfer.subject_to_you=%s бажає передати"%s" вам
+repo.transfer.subject_to=%s хоче перенеÑти "%s" в %s
+repo.transfer.subject_to_you=%s хоче передати"%s" вам
repo.transfer.to_you=вам
repo.transfer.body=Щоб прийнÑти або відхилити перейдіть до %s або проÑто ігноруйте.
repo.collaborator.added.subject=%s додав Ð²Ð°Ñ Ð´Ð¾ %s
-repo.collaborator.added.text=Ви були додані в ÑкоÑті Ñпівавтора репозиторію:
+repo.collaborator.added.text=Ð’Ð°Ñ Ð´Ð¾Ð´Ð°Ð»Ð¸ Ñк Ñпівавтора до Ñховища:
+team_invite.subject=%[1]s запрошує Ð²Ð°Ñ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ñ‚Ð¸ÑÑ Ð´Ð¾ організації %[2]s
+team_invite.text_1=%[1]s запрошує Ð²Ð°Ñ Ð´Ð¾ команди %[2]s в організації %[3]s.
+team_invite.text_2=Перейдіть за поÑиланнÑм, щоб приєднатиÑÑ Ð´Ð¾ команди:
+team_invite.text_3=Примітка: Це Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ðµ Ð´Ð»Ñ %[1]s. Якщо ви не очікували цього запрошеннÑ, ви можете проігнорувати це повідомленнÑ.
[modal]
yes=Так
no=ÐÑ–
+confirm=Підтвердити
cancel=Відмінити
-modify=ОновленнÑ
+modify=Оновити
[form]
UserName=Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача
-RepoName=Ðазва репозиторію
+RepoName=Ðазва Ñховища
Email=ÐдреÑа електронної пошти
Password=Пароль
-Retype=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ
-SSHTitle=Iм'Ñ SSH ключа
+Retype=Підтвердити пароль
+SSHTitle=Ðазва ключа SSH
HttpsUrl=ÐдреÑа HTTPS
PayloadUrl=URL обробника
TeamName=Ðазва команди
AuthName=Ðазва авторизації
-AdminEmail=Email адмініÑтратора
+AdminEmail=ÐдреÑа електронної пошти адмініÑтратора
-NewBranchName=Ім'Ñ Ð½Ð¾Ð²Ð¾Ñ— гілки
+NewBranchName=Ðазва нової гілки
CommitSummary=Резюме коміту
CommitMessage=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ
CommitChoice=Вибір коміта
@@ -410,76 +575,127 @@ Content=ЗміÑÑ‚
SSPISeparatorReplacement=Розділювач
SSPIDefaultLanguage=Типова мова
-require_error=` не може бути пуÑтим.`
-alpha_dash_error=` повинен міÑтити тільки літерно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-') та підкреÑÐ»ÐµÐ½Ð½Ñ ('_'). `
-alpha_dash_dot_error=` повинен міÑтити тільки літерно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-') , підкреÑÐ»ÐµÐ½Ð½Ñ ('_') та точки ('.'). `
-git_ref_name_error=` повинен бути правильним поÑилальним ім'Ñм Git.`
+require_error=` не може бути порожнім.`
+alpha_dash_error=` повинен міÑтити тільки алфавітно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-') та підкреÑÐ»ÐµÐ½Ð½Ñ ('_').`
+alpha_dash_dot_error=` повинен міÑтити тільки алфавітно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-'), підкреÑÐ»ÐµÐ½Ð½Ñ ('_') та крапку ('.').`
+git_ref_name_error=` повинен бути правильно Ñформованим ім'Ñм-поÑиланнÑм на Git.`
size_error=` повинен бути розмір %s.`
-min_size_error=` повинен бути принаймні %s Ñимволів.`
-max_size_error=` повинен бути не більш Ñк %s Ñимволів.`
-email_error=` не Ñ” адреÑою електронної пошти.`
-glob_pattern_error=` неприпуÑтимий шаблон glob: %s.`
-regex_pattern_error=` неприпуÑтимий шаблон regex: %s.`
+min_size_error=` має міÑтити принаймні %s Ñимволів.`
+max_size_error=` має міÑтити не більше %s Ñимволів.`
+email_error=` не Ñ” дійÑною адреÑою електронної пошти.`
+url_error=`"%s" не Ñ” дійÑною URL-адреÑою.`
+include_error=` повинен міÑтити підрÑдок "%s".`
+glob_pattern_error=` недійÑний шаблон glob: %s.`
+regex_pattern_error=` недійÑний шаблон регулÑрного виразу: %s.`
+username_error=` може міÑтити лише алфавітно-цифрові Ñимволи ('0-9', 'a-z', 'A-Z'), Ð´ÐµÑ„Ñ–Ñ ('-'), підкреÑÐ»ÐµÐ½Ð½Ñ ('_') та крапку ('.'). Ðе може починатиÑÑ Ð°Ð±Ð¾ закінчуватиÑÑ Ð½ÐµÐ°Ð»Ñ„Ð°Ð²Ñ–Ñ‚Ð½Ð¸Ð¼Ð¸ Ñимволами; поÑлідовні неалфавітні Ñимволи також заборонені.`
unknown_error=Ðевідома помилка:
captcha_incorrect=Код CAPTCHA неправильний.
-password_not_match=Паролі не Ñпівпадають.
-lang_select_error=Оберіть мову з переліку.
-
-username_been_taken=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача вже зайнÑто.
-username_change_not_local_user=Ðелокальні кориÑтувачі не можуть змінити Ñвоє ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача.
-repo_name_been_taken=Ім'Ñ Ñ€ÐµÐ¿Ð¾Ð·Ñ–Ñ‚Ð¾Ñ€Ñ–ÑŽ вже викориÑтовуєтьÑÑ.
-repository_files_already_exist=Файли вже Ñ–Ñнують Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ репозитарію. ЗвернітьÑÑ Ð´Ð¾ ÑиÑтемного адмініÑтратора.
-repository_files_already_exist.adopt=Файли вже Ñ–Ñнують Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ репозиторію Ñ– можуть бути лише прийнÑті.
-repository_files_already_exist.delete=Файли вже Ñ–Ñнують Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища. Ви повинні видалити Ñ—Ñ….
-repository_files_already_exist.adopt_or_delete=Файли вже Ñ–Ñнують Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ репозиторію. Їх можливо прийнÑти або видалити.
-visit_rate_limit=ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ ÑˆÐ²Ð¸Ð´ÐºÐ¾Ñті віддаленого доÑтупу.
-2fa_auth_required=Ð”Ð»Ñ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¾Ð³Ð¾ доÑтупу необхідна двуфакторна аутентифікаціÑ.
-org_name_been_taken=Ðазва організації вже зайнÑто.
-team_name_been_taken=Ðазва команди вже зайнÑто.
-team_no_units_error=Дозволити доÑтуп до принаймні одного розділу репозитарію.
-email_been_used=Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° адреÑа вже викориÑтовуєтьÑÑ.
-email_invalid=ÐдреÑа електронної пошти помилкова.
+password_not_match=Паролі не збігаютьÑÑ.
+lang_select_error=Оберіть мову зі ÑпиÑку.
+
+username_been_taken=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача вже зайнÑте.
+username_change_not_local_user=Ðелокальні кориÑтувачі не можуть змінювати Ñвоє ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача.
+change_username_disabled=Зміна імені кориÑтувача відключена.
+change_full_name_disabled=Зміна повного імені відключена.
+username_has_not_been_changed=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не змінено
+repo_name_been_taken=Ðазва Ñховища вже викориÑтовуєтьÑÑ.
+repository_force_private=ПримуÑову приватніÑть ввімкнено: приватні Ñховища не можна зробити загальнодоÑтупними.
+repository_files_already_exist=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища вже Ñ–Ñнують файли. ЗвернітьÑÑ Ð´Ð¾ ÑиÑтемного адмініÑтратора.
+repository_files_already_exist.adopt=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища вже Ñ–Ñнують файли, Ñ– Ñ—Ñ… можна лише прийнÑти.
+repository_files_already_exist.delete=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища вже Ñ–Ñнують файли. Ви повинні видалити Ñ—Ñ….
+repository_files_already_exist.adopt_or_delete=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища вже Ñ–Ñнують файли. Прийміть Ñ—Ñ… або видаліть.
+visit_rate_limit=Віддалений доÑтуп відхилено у зв'Ñзку з обмеженнÑм кількоÑті Ñпроб.
+2fa_auth_required=Ð”Ð»Ñ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¾Ð³Ð¾ доÑтупу необхідна двофакторна автентифікаціÑ.
+org_name_been_taken=Ðазва організації вже зайнÑта.
+team_name_been_taken=Ðазва команди вже зайнÑта.
+team_no_units_error=Дозволити доÑтуп до принаймні одного розділу Ñховища.
+email_been_used=ÐдреÑа електронної пошти вже викориÑтовуєтьÑÑ.
+email_invalid=ÐдреÑа електронної пошти недійÑна.
+email_domain_is_not_allowed=Домен електронної пошти кориÑтувача <b>%s</b> конфліктує з EMAIL_DOMAIN_ALLOWLIST або EMAIL_DOMAIN_BLOCKLIST. ПереконайтеÑÑ, що ваша Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð°.
+openid_been_used=ÐдреÑа OpenID '%s' вже викориÑтовуєтьÑÑ.
username_password_incorrect=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або пароль.
-password_complexity=Пароль не відповідає вимогам до ÑкладноÑті:
-password_lowercase_one=Принаймні одна буква в нижньому регіÑтрі
-password_uppercase_one=Принаймні одна буква в верхньому регіÑтрі
+password_complexity=Пароль не відповідає вимогам ÑкладноÑті:
+password_lowercase_one=Принаймні один Ñимвол нижнього регіÑтру
+password_uppercase_one=Принаймні один Ñимвол верхнього регіÑтру
password_digit_one=Принаймні одна цифра
-password_special_one=Принаймні один Ñпеціальний Ñимвол (пунктуаціÑ, дужки, лапки тощо)
-enterred_invalid_repo_name=Ðевірно введено ім'Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ.
-enterred_invalid_org_name=Ðевірно введено ім'Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ—.
-enterred_invalid_owner_name=Ім'Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ влаÑника не Ñ” дійÑним.
-enterred_invalid_password=Введений вами пароль некоректний.
-user_not_exist=Даний кориÑтувач не Ñ–Ñнує.
+password_special_one=Принаймні один Ñпеціальний Ñимвол (розділові знаки, дужки, лапки тощо)
+enterred_invalid_repo_name=Ви ввели неправильну назву Ñховища.
+enterred_invalid_org_name=Ви ввели неправильну назву організації.
+enterred_invalid_owner_name=Ім'Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ влаÑника недійÑне.
+enterred_invalid_password=Ви ввели неправильний пароль.
+unset_password=КориÑтувач не вÑтановив пароль.
+unsupported_login_type=Тип входу не підтримуєтьÑÑ Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу.
+user_not_exist=КориÑтувач не Ñ–Ñнує.
team_not_exist=Команда не Ñ–Ñнує.
-last_org_owner=Ви не можете видалити оÑтаннього кориÑтувача з команди 'влаÑники'. У кожній команді має бути принаймні один влаÑник.
+last_org_owner=Ви не можете видалити оÑтаннього кориÑтувача з групи 'влаÑників'. Ð’ організації має бути принаймні один влаÑник.
cannot_add_org_to_team=Організацію неможливо додати Ñк учаÑника команди.
-
-invalid_ssh_key=Ðеможливо перевірити ваш SSH ключ: %s
-invalid_gpg_key=Ðеможливо перевірити ваш GPG ключ: %s
-invalid_ssh_principal=Ðекоректний відповідальний: %s
+duplicate_invite_to_team=КориÑтувача вже запрошено Ñк члена команди.
+organization_leave_success=Ви уÑпішно покинули організацію %s.
+
+invalid_ssh_key=Ðе вдаєтьÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ключ SSH: %s
+invalid_gpg_key=Ðе вдаєтьÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ключ GPG: %s
+invalid_ssh_principal=Ðевірна ідентичніÑть: %s
+must_use_public_key=Ðаданий вами ключ — приватний. Будь лаÑка, нікуди не завантажуйте Ñвій приватний ключ. ÐатоміÑть викориÑтовуйте публічний ключ.
+unable_verify_ssh_key=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ключ SSH, перевірте його на наÑвніÑть помилок.
auth_failed=Помилка автентифікації: %v
+still_own_repo=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¾Ð»Ð¾Ð´Ñ–Ñ” одним або декількома Ñховищами, Ñпершу видаліть або перенеÑіть Ñ—Ñ….
+still_has_org=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ” учаÑником однієї або декількох організацій, Ñпершу залиште Ñ—Ñ….
+still_own_packages=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¾Ð»Ð¾Ð´Ñ–Ñ” одним або декількома пакетами, Ñпершу видаліть Ñ—Ñ….
+org_still_own_repo=Ð¦Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð²Ñе ще володіє одним або декількома Ñховищами, Ñпочатку видаліть або перенеÑіть Ñ—Ñ….
+org_still_own_packages=Ð¦Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð²Ñе ще має один або декілька пакетів, Ñпочатку видаліть Ñ—Ñ….
target_branch_not_exist=Цільової гілки не Ñ–Ñнує.
+target_ref_not_exist=Цільове поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ðµ Ñ–Ñнує %s
+admin_cannot_delete_self=Ви не можете видалити Ñебе, допоки ви адмініÑтратор. Будь лаÑка, Ñпочатку видаліть права адмініÑтратора.
[user]
-change_avatar=Змінити Ñвій аватар…
-repositories=Репозиторії
+change_avatar=Змінити аватар…
+joined_on=ПриєднавÑÑ(-лаÑÑŒ) %s
+repositories=Сховища
activity=Публічна активніÑть
-followers=Читачі
+followers=ПоÑлідовники
show_more=Показати більше
-starred=Обрані Репозиторії
-watched=ВідÑтежувані репозиторії
-projects=Проєкт
+starred=Обрані Ñховища
+watched=ВідÑтежувані Ñховища
+code=Код
+projects=Проєкти
overview=ОглÑд
-following=Читає
-follow=ПідпиÑатиÑÑ
-unfollow=ВідпиÑатиÑÑ
+following=ВідÑтежувані
+follow=Стежити
+unfollow=Ðе Ñтежити
user_bio=БіографіÑ
-disabled_public_activity=Цей кориÑтувач вимкнув публічний показ діÑльноÑті.
-
-
+disabled_public_activity=Цей кориÑтувач вимкнув публічну видиміÑть активноÑті.
+email_visibility.limited=Ваша електронна пошта видима Ð´Ð»Ñ Ð²ÑÑ–Ñ… автентифікованих кориÑтувачів
+email_visibility.private=Вашу адреÑу електронної пошти бачитимете лише ви та адмініÑтратори
+show_on_map=Показати це міÑце на карті
+settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача
+
+form.name_reserved=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача "%s" зарезервовано.
+form.name_pattern_not_allowed=Шаблон "%s" не дозволено в імені кориÑтувача.
+form.name_chars_not_allowed=Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача "%s" міÑтить неприпуÑтимі Ñимволи.
+
+block.block=Заблокувати
+block.block.user=Заблокувати кориÑтувача
+block.block.org=Заблокувати кориÑтувача Ð´Ð»Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ—
+block.block.failure=Ðе вдалоÑÑ Ð·Ð°Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ñ‚Ð¸ кориÑтувача: %s
+block.unblock=Розблокувати
+block.unblock.failure=Ðе вдалоÑÑ Ñ€Ð¾Ð·Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ñ‚Ð¸ кориÑтувача: %s
+block.blocked=Ви заблокували цього кориÑтувача.
+block.title=Заблокувати кориÑтувача
+block.info=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не дозволÑÑ” йому взаємодіÑти зі Ñховищами, наприклад, відкривати або коментувати запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð°Ð±Ð¾ задачі. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача.
+block.info_1=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача запобігає наÑтупним діÑм у вашому обліковому запиÑÑ– та ваших Ñховищах:
+block.info_2=Ñлідкують за вашим обліковим запиÑом
+block.info_3=надÑилати вам ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ @згадавши ваше ім'Ñ
+block.info_6=Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° ÐºÐ¾Ð¼ÐµÐ½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡ або запитів на злиттÑ
+block.user_to_block=Блокувати КориÑтувача
+block.note=Примітка
+block.note.title=Ðеобов’Ñзкова примітка:
+block.note.info=Ðотатка не видима Ð´Ð»Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾Ð³Ð¾ кориÑтувача.
+block.note.edit=Редагувати нотатку
+block.list=Заблоковані кориÑтувачі
+block.list.none=Ви не заблокували жодного кориÑтувача.
[settings]
profile=Профіль
@@ -488,336 +704,457 @@ appearance=Зовнішній виглÑд
password=Пароль
security=Безпека
avatar=Ðватар
-ssh_gpg_keys=SSH / GPG ключі
+ssh_gpg_keys=Ключі SSH / GPG
social=Соціальні облікові запиÑи
applications=Додатки
orgs=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñми
repos=Репозиторії
delete=Видалити обліковий запиÑ
+twofa=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ (TOTP)
account_link=Прив'Ñзані облікові запиÑи
organization=Організації
+uid=UID
+webauthn=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ (ключі безпеки)
public_profile=ЗагальнодоÑтупний профіль
+biography_placeholder=Розкажіть нам трохи про Ñебе! (Ви можете викориÑтовувати Markdown)
+location_placeholder=ДілитиÑÑ Ñвоїм приблизним географічним положеннÑм з іншими
+profile_desc=Керуйте тим, Ñк ваш профіль буде показано іншим кориÑтувачам. Ваша оÑновна адреÑа електронної пошти викориÑтовуватиметьÑÑ Ð´Ð»Ñ Ñповіщень, Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ñ‚Ð° веб-операцій Git.
+password_username_disabled=Вам не дозволено змінювати ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð±Ñ–Ð»ÑŒÑˆ докладної інформації.
+password_full_name_disabled=Вам не дозволено змінювати ваше ім'Ñ. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту Ð´Ð»Ñ Ð±Ñ–Ð»ÑŒÑˆ докладної інформації.
full_name=Повне ім'Ñ
website=Веб-Ñайт
location=МіÑцезнаходженнÑ
update_theme=Оновити тему
update_profile=Оновити профіль
update_language=Оновити мову
+update_language_not_found=Мова "%s" недоÑтупна.
update_language_success=Мову оновлено.
update_profile_success=Профіль уÑпішно оновлено.
-change_username=Ваше Ім'Ñ ÐºÑ€Ð¸Ñтувача було змінено.
+change_username=Ваше ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача змінено.
+change_username_prompt=Примітка: Зміна імені кориÑтувача також змінює URL-адреÑу облікового запиÑу.
+change_username_redirect_prompt=Старе ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача буде перенаправлÑтиÑÑ Ð½Ð° нове, поки хтоÑÑŒ не викориÑтає його.
continue=Продовжити
cancel=Відмінити
language=Мова
ui=Тема
+hidden_comment_types=Приховані типи коментарів
+hidden_comment_types_description=Позначені тут типи коментарів не будуть показані на Ñторінках проблем. Ðаприклад, позначка «Мітка» вилучає вÑÑ– коментарі «{user} додав/вилучив {label}».
+hidden_comment_types.ref_tooltip=Коментарі, де на цю задачу було зроблено поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð· іншого випуÑку/коміту/…
+hidden_comment_types.issue_ref_tooltip=Коментарі, в Ñких кориÑтувач змінює гілку/тег, пов'Ñзані з нею
+comment_type_group_reference=ПоÑиланнÑ
comment_type_group_label=Мітка
comment_type_group_milestone=Етап
comment_type_group_assignee=Виконавець
comment_type_group_title=Заголовок
comment_type_group_branch=Гілка
+comment_type_group_time_tracking=Облік чаÑу
+comment_type_group_deadline=Крайній Ñтрок
+comment_type_group_dependency=ЗалежніÑть
+comment_type_group_lock=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ
+comment_type_group_review_request=Запит на перевірку
+comment_type_group_pull_request_push=Додані коміти
comment_type_group_project=Проєкт
+comment_type_group_issue_ref=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° задачу
+saved_successfully=Ваші Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑƒÑпішно збережено.
privacy=ПриватніÑть
+keep_activity_private=Приховати активніÑть зі Ñторінки профілю
keep_activity_private_popup=Показувати вашу активніÑть лише Вам та адмініÑтраторам
lookup_avatar_by_mail=Знайти Ðватар за адреÑою електронної пошти
federated_avatar_lookup=Знайти зовнішній аватар
-enable_custom_avatar=Увімкнути кориÑтувацькі аватари
-choose_new_avatar=Оберіть новий аватар
+enable_custom_avatar=Увімкнути кориÑтувацький аватар
+choose_new_avatar=Обрати новий аватар
update_avatar=Оновити аватар
delete_current_avatar=Видалити поточний аватар
uploaded_avatar_not_a_image=Завантажений файл не Ñ” зображеннÑм.
-update_avatar_success=Ваш аватар був змінений.
+uploaded_avatar_is_too_big=Розмір завантаженого файлу (%d KiB) перевищує макÑимальний розмір (%d KiB).
+update_avatar_success=Ваш аватар оновлено.
update_user_avatar_success=Ðватар кориÑтувача оновлено.
+cropper_prompt=Ви можете відредагувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´ збереженнÑм. Відредаговане Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ´Ðµ збережено Ñк PNG.
change_password=Оновити пароль
old_password=Поточний пароль
new_password=Ðовий пароль
+retype_new_password=Підтвердити новий пароль
password_incorrect=Поточний пароль неправильний.
-change_password_success=Ваш пароль був оновлений. Тепер увійдіть в ÑиÑтему, викориÑтовуючи новий пароль.
-password_change_disabled=Ðелокальні акаунти не можуть змінити пароль через Gitea.
+change_password_success=Ваш пароль оновлено. Відтепер входьте в ÑиÑтему, викориÑтовуючи новий пароль.
+password_change_disabled=Ðелокальні кориÑтувачі не можуть оновити Ñвій пароль через Ð²ÐµÐ±Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Gitea.
emails=ÐдреÑа електронної пошти
-manage_emails=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами ел. пошти
-manage_themes=Виберіть тему за замовчуваннÑм
-manage_openid=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ OpenID
+manage_emails=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами електронної пошти
+manage_themes=Обрати типову тему
+manage_openid=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами OpenID
+email_desc=Ваша оÑновна адреÑа електронної пошти викориÑтовуватиметьÑÑ Ð´Ð»Ñ Ñповіщень, Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ñ–, за умови, що вона не прихована, Ð´Ð»Ñ Ð²ÐµÐ±-операцій з Git.
theme_desc=Ð¦Ñ Ñ‚ÐµÐ¼Ð° буде типовою Ð´Ð»Ñ Ð²Ñього Ñайту.
+theme_colorblindness_help=Підтримка тем колірної Ñліпоти
+theme_colorblindness_prompt=Gitea щойно отримала деÑкі теми з базовою підтримкою колірної Ñліпоти, в Ñких визначено лише кілька кольорів. Робота над вÑе ще триває. Ще більше покращень можна зробити, визначивши більше кольорів у CSS-файлах теми.
primary=ОÑновний
activated=Ðктивовано
requires_activation=Потрібна активаціÑ
-primary_email=Зробити оÑновним
+primary_email=Зробити оÑновною
activate_email=ÐадіÑлати активацію
-activations_pending=Ðктивації в очікуванні
+activations_pending=ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ—
+can_not_add_email_activations_pending=ВідбуваєтьÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ, Ñпробуйте ще раз через кілька хвилин, Ñкщо хочете додати нову адреÑу електронної пошти.
delete_email=Видалити
email_deletion=Видалити адреÑу електронної пошти
-email_deletion_desc=Електронна адреÑа та пов'Ñзана з нею Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ видалена з вашого облікового запиÑу. Git коміти, здійÑнені через цю електронну адреÑу, залишитьÑÑ Ð±ÐµÐ· змін. Продовжити?
-email_deletion_success=ÐдреÑу електронної пошти було видалено.
+email_deletion_desc=ÐдреÑа електронної пошти та пов'Ñзана з нею Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ видалена з вашого облікового запиÑу. Коміти Git, здійÑнені через цю адреÑу електронну пошту, залишитьÑÑ Ð±ÐµÐ· змін. Продовжити?
+email_deletion_success=ÐдреÑу електронної пошти видалено.
theme_update_success=Тему оновлено.
-theme_update_error=Вибрана тема не Ñ–Ñнує.
+theme_update_error=Обрана тема не Ñ–Ñнує.
openid_deletion=Видалити адреÑу OpenID
-openid_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— OpenID-адреÑи з вашого облікового запиÑу заборонÑÑ” вам входити з ним. Продовжити?
-openid_deletion_success=ÐдреÑа OpenID була видалена.
+openid_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— адреÑи OpenID не дозволить вам увійти за нею. Продовжити?
+openid_deletion_success=ÐдреÑу OpenID видалено.
add_new_email=Додати нову адреÑу електронної пошти
add_new_openid=Додати новий OpenID URI
add_email=Додати адреÑу електронної пошти
add_openid=Додати OpenID URI
+add_email_confirmation_sent=Електронний лиÑÑ‚ із підтвердженнÑм було відправлено на '%s', будь лаÑка, перевірте вашу поштову Ñкриньку протÑгом наÑтупних %s, щоб підтвердити адреÑу.
add_email_success=Додано нову адреÑу електронної пошти.
-email_preference_set_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти уÑпішно вÑтановлені.
-add_openid_success=Ðова адреÑа OpenID була додана.
+email_preference_set_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти уÑпішно вÑтановлено.
+add_openid_success=Додано нову адреÑу OpenID.
keep_email_private=Приховати адреÑу електронної пошти
-openid_desc=OpenID дозволÑÑ” делегувати аутентифікацію зовнішньому поÑтачальнику поÑлуг.
+openid_desc=OpenID дозволÑÑ” делегувати автентифікацію зовнішньому поÑтачальнику поÑлуг.
-manage_ssh_keys=Керувати SSH ключами
-manage_ssh_principals=Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ SSH Ñертифікатами кориÑтувачів
-manage_gpg_keys=Керувати GPG ключами
+manage_ssh_keys=Керувати ключами SSH
+manage_ssh_principals=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ‡Ð½Ð¾ÑÑ‚Ñми Ñертифікатів SSH
+manage_gpg_keys=Керувати ключами SSH
add_key=Додати ключ
-ssh_desc=Ці відкриті SSH-ключі пов'Ñзані з вашим обліковим запиÑом. Відповідні приватні ключі дозволÑють отримати повний доÑтуп до ваших репозиторіїв.
-principal_desc=Ці наÑтройки SSH Ñертифікатів вказані у вашому обліковому запиÑÑ– та надають повний доÑтуп до ваших репозиторіїв.
-gpg_desc=Ці публічні ключі GPG пов'Ñзані з вашим обліковим запиÑом. Тримайте Ñвої приватні ключі в безпеці, оÑкільки вони дозволÑють здійÑнювати перевірку комітів.
-ssh_helper=<strong>Потрібна допомога?</strong> ДивітьÑÑ Ð³Ñ–Ð´ на GitHub з <a href="%s"> генерації ключів SSH</a> або Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ <a href="%s">типових неполадок SSH</a>.
-gpg_helper=<strong> Потрібна допомога? </strong> ПереглÑньте поÑібник GitHub <a href="%s"> про GPG </a>.
-add_new_key=Додати SSH ключ
-add_new_gpg_key=Додати GPG ключ
+ssh_desc=Ці публічні ключі SSH пов'Ñзані з вашим обліковим запиÑом. Відповідні приватні ключі надають повний доÑтуп до ваших Ñховищ.
+principal_desc=Ці ідентифікатори Ñертифікатів SSH прив'Ñзані до вашого облікового запиÑу Ñ– надають повний доÑтуп до ваших Ñховищ.
+gpg_desc=Ці публічні ключі GPG пов'Ñзані з вашим обліковим запиÑом. Зберігайте Ñвої приватні ключі в безпеці, оÑкільки вони дозволÑють підтверджувати коміти.
+ssh_helper=<strong>Потрібна допомога?</strong> ОзнайомтеÑÑ Ð· інÑтрукцією GitHub щодо <a href="%s">ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð²Ð»Ð°Ñних ключів SSH</a> або Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ <a href="%s">типових неполадок SSH.</a>
+gpg_helper=<strong>Потрібна допомога?</strong> ПереглÑньте поÑібник GitHub <a href="%s">про GPG</a>.
+add_new_key=Додати ключ SSH
+add_new_gpg_key=Додати ключ GPG
key_content_ssh_placeholder=ПочинаєтьÑÑ Ð· 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', або 'sk-ssh-ed25519@openssh.com'
key_content_gpg_placeholder=ПочинаєтьÑÑ Ð· '-----BEGIN PGP PUBLIC KEY BLOCK-----'
-add_new_principal=Додати кориÑтувача
-ssh_key_been_used=Цей SSH ключ вже був додано до Ñервера.
+add_new_principal=Додати ідентичніÑть
+ssh_key_been_used=Цей ключ SSH вже було додано до Ñервера.
ssh_key_name_used=Ключ SSH з таким ім'Ñм вже Ñ–Ñнує у вашому обліковому запиÑÑ–.
-ssh_principal_been_used=Цей кориÑтувач вже був доданий на Ñервер.
+ssh_principal_been_used=Цю ідентичніÑть вже було додано до Ñервера.
gpg_key_id_used=Публічний ключ GPG з таким Ñамим ідентифікатором вже Ñ–Ñнує.
-gpg_no_key_email_found=Цей ключ GPG не відповідає жодній активованій поштовій адреÑÑ–, Ñка пов'Ñзана з вашим обліковим запиÑом. Його вÑе рівно можна додати, Ñкщо ви підпишете наданий токен.
-gpg_key_matched_identities=Відповідні отримувачі:
-gpg_key_matched_identities_long=Вбудовані ідентифікатори цього ключа збігаютьÑÑ Ð· наÑтупними активованими адреÑами електронної пошти вказаного кориÑтувача. Коміти, Ñкі відповідають цим адреÑам, можуть бути підтверджені цим ключем.
+gpg_no_key_email_found=Цей ключ GPG не відповідає жодній активованій адреÑÑ– електронної пошти, пов'Ñзаній з вашим обліковим запиÑом. Його вÑе одно можна додати, Ñкщо ви підпишете наданий токен.
+gpg_key_matched_identities=Відповідні ідентичноÑті:
+gpg_key_matched_identities_long=Вбудовані ідентифікатори цього ключа збігаютьÑÑ Ð· наÑтупними активованими адреÑами електронної пошти цього кориÑтувача. За допомогою цього ключа можна перевірÑти коміти, що відповідають цим адреÑам електронної пошти.
gpg_key_verified=Перевірений ключ
-gpg_key_verified_long=Ключ перевірений за допомогою токена Ñ– може бути викориÑтано Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð², Ñкі відповідають будь-Ñкій з активованих Ð°Ð´Ñ€ÐµÑ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача, на додачу до будь-Ñких відповідних ідентифікацій Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ключа.
+gpg_key_verified_long=Ключ був перевірений токеном Ñ– може бути викориÑтаний Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ комітів, що відповідають будь-Ñким активованим адреÑам електронної пошти Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача, а також будь-Ñких ідентифікаторів, що відповідають цьому ключу.
gpg_key_verify=Підтвердити
-gpg_invalid_token_signature=Ðаданий ключ GPG, Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñ– токен не Ñпівпадають або токен заÑтарів.
+gpg_invalid_token_signature=Ðадані ключ GPG, Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñ– токен не збігаютьÑÑ Ð°Ð±Ð¾ токен заÑтарілий.
gpg_token_required=Вам потрібно надати Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð´Ð»Ñ Ð½Ð¸Ð¶Ñ‡ÐµÐ²ÐºÐ°Ð·Ð°Ð½Ð¾Ð³Ð¾ токена
gpg_token=Токен
gpg_token_help=Ви можете Ñтворити Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð° допомогою:
gpg_token_signature=ТекÑтовий (armored) Ð¿Ñ–Ð´Ð¿Ð¸Ñ GPG
key_signature_gpg_placeholder=`ПочинаєтьÑÑ Ð· "-----BEGIN PGP SIGNATURE-----"`
+verify_gpg_key_success=Ключ GPG '%s' перевірено.
ssh_key_verified=Перевірений ключ
-ssh_key_verify=Підтвердити
+ssh_key_verified_long=Ключ було перевірено за допомогою токена. Його можна викориÑтовувати Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ комітів, що відповідають будь-Ñким активованим адреÑам електронної пошти цього кориÑтувача.
+ssh_key_verify=Перевірити
+ssh_invalid_token_signature=Ключ SSH, Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñ– токен не збігаютьÑÑ Ð°Ð±Ð¾ токен заÑтарілий.
ssh_token_required=Вам потрібно надати Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð´Ð»Ñ Ð½Ð¸Ð¶Ñ‡ÐµÐ²ÐºÐ°Ð·Ð°Ð½Ð¾Ð³Ð¾ токена
ssh_token=Токен
ssh_token_help=Ви можете Ñтворити Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð° допомогою:
+ssh_token_signature=ТекÑтовий Ð¿Ñ–Ð´Ð¿Ð¸Ñ SSH
+key_signature_ssh_placeholder=`ПочинаєтьÑÑ Ð· "-----BEGIN SSH SIGNATURE-----"`
+verify_ssh_key_success=Ключ SSH '%s' перевірено.
subkeys=Підключі
-key_id=ID ключа
-key_name=Ім'Ñ ÐºÐ»ÑŽÑ‡Ð°
+key_id=Ідентифікатор ключа
+key_name=Ðазва ключа
key_content=ЗміÑÑ‚
principal_content=ЗміÑÑ‚
+add_key_success=Ключ SSH '%s' додано.
+add_gpg_key_success=Ключ GPG '%s' додано.
+add_principal_success=Було додано SSH Ñертифікат ідентичноÑті '%s'.
delete_key=Видалити
-ssh_key_deletion=Видалити SSH ключ
-gpg_key_deletion=Видалити GPG ключ
-ssh_principal_deletion=Видалити SSH Ñертифікат кориÑтувача
+ssh_key_deletion=Видалити ключ SSH
+gpg_key_deletion=Видалити ключ GPG
+ssh_principal_deletion=Видалити ідентичніÑть Ñертифікату SSH
ssh_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° SSH ÑкаÑовує доÑтуп до вашого облікового запиÑу. Продовжити?
-gpg_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ GPG ключа ÑкаÑовує перевірку підпиÑаних ним комітів. Продовжити?
-ssh_principal_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° SSH ÑкаÑовує доÑтуп до вашого облікового запиÑу. Продовжити?
-ssh_key_deletion_success=SSH ключ був видалений.
-gpg_key_deletion_success=GPG було видалено.
-ssh_principal_deletion_success=КориÑтувача видалено.
+gpg_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° GPG ÑкаÑовує перевірку підпиÑаних ним комітів. Продовжити?
+ssh_principal_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ‡Ð½Ð¾Ñті Ñертифіката SSH ÑкаÑовує доÑтуп до вашого облікового запиÑу. Продовжити?
+ssh_key_deletion_success=Ключ SSH видалено.
+gpg_key_deletion_success=Ключ GPG видалено.
+ssh_principal_deletion_success=ІдентичніÑть видалено.
+added_on=Додано %s
+valid_until_date=ДійÑно до %s
valid_forever=ДійÑний завжди
-last_used=ОÑтаннє викориÑтаннÑ
-no_activity=Жодної діÑльноÑті
-can_read_info=Читати
-can_write_info=ÐапиÑати
-key_state_desc=Цей ключ викориÑтовувавÑÑ Ð² оÑтанні 7 днів
-token_state_desc=Цей токен викориÑтовувавÑÑ Ð² оÑтанні 7 днів
-principal_state_desc=УчаÑтник був на Ñайті в оÑтанні 7 днів
+last_used=ВоÑтаннє викориÑтано
+no_activity=ÐÐµÑ‰Ð¾Ð´Ð°Ð²Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–Ñть відÑутнÑ
+can_read_info=ЧитаннÑ
+can_write_info=ЗапиÑ
+key_state_desc=Цей ключ викориÑтовувавÑÑ Ð¿Ñ€Ð¾Ñ‚Ñгом оÑтанніх 7 днів
+token_state_desc=Цей токен викориÑтовувавÑÑ Ð¿Ñ€Ð¾Ñ‚Ñгом оÑтанніх 7 днів
+principal_state_desc=Ð¦Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ‡Ð½Ñ–Ñть викориÑтовувалаÑÑ Ð¿Ñ€Ð¾Ñ‚Ñгом оÑтанніх 7 днів
show_openid=Показати у профілю
-hide_openid=Ðе показувати у профілі
+hide_openid=Приховати з профілю
ssh_disabled=SSH вимкнено
-ssh_externally_managed=Цей ключ SSH має зовнішнє ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача
-manage_social=Керувати зв'Ñзаними обліковими запиÑами Ñоціальних мереж
+ssh_signonly=SSH наразі вимкнено, тому ці ключі викориÑтовуютьÑÑ Ð»Ð¸ÑˆÐµ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ підпиÑу комітів.
+ssh_externally_managed=Цей ключ SSH керуєтьÑÑ Ð·Ð·Ð¾Ð²Ð½Ñ– Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача
+manage_social=Керувати пов'Ñзаними обліковими запиÑами Ñоціальних мереж
+social_desc=Ці облікові запиÑи Ñоціальних мереж можна викориÑтовувати Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ в ваш обліковий запиÑ. ПереконайтеÑÑ, що вÑÑ– вони належать вам.
unbind=Від'єднати
+unbind_success=Соціальний обліковий Ð·Ð°Ð¿Ð¸Ñ ÑƒÑпішно видалено.
-manage_access_token=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð¾ÐºÐµÐ½Ð°Ð¼Ð¸ доÑтупу
-generate_new_token=Згенерувати новий токен
-tokens_desc=Ці токени надають доÑтуп до вашого облікового запиÑу за допомогою Gitea API.
-token_name=Ім'Ñ Ñ‚Ð¾ÐºÐµÐ½Ñƒ
+manage_access_token=Керувати токенами доÑтупу
+generate_new_token=Створити новий токен
+tokens_desc=Ці токени надають доÑтуп до вашого облікового запиÑу за допомогою API Gitea.
+token_name=Ðазва токену
generate_token=Згенерувати токен
-generate_token_success=Ваш новий токен був Ñтворений. Скопіюйте його зараз, оÑкільки він не буде показаний знову.
+generate_token_success=Ваш новий токен Ñтворено. Скопіюйте його зараз, оÑкільки він не буде показаний знову.
generate_token_name_duplicate=Ðазва програми <strong>%s</strong> вже викориÑтовуєтьÑÑ. Будь лаÑка, викориÑтайте нову.
delete_token=Видалити
access_token_deletion=Видалити токен доÑтупу
access_token_deletion_cancel_action=Відмінити
access_token_deletion_confirm_action=Видалити
-delete_token_success=Токен був знищений. Програми, що викориÑтовують його, більше не мають доÑтупу до вашого облікового запиÑу.
-permission_read=Прочитані
-
-manage_oauth2_applications=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð°Ð¼Ð¸ OAuth2
-edit_oauth2_application=Редагувати програму OAuth2
-oauth2_applications_desc=Програми OAuth2 дають можливіÑть вашим Ñтороннім програмам надійно аутентифікувати кориÑтувачів у цьому екземплÑрі Gitea.
-remove_oauth2_application=Видалити програму OAuth2
-remove_oauth2_application_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¸ OAuth2 ÑкаÑовує доÑтуп до вÑÑ–Ñ… підпиÑаних маркерів доÑтупу. Продовжити?
-remove_oauth2_application_success=Програму видалено.
-create_oauth2_application=Створити нову програму OAuth2
-create_oauth2_application_button=Створити програму
-oauth2_application_name=Ðазва програми
+access_token_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ‚Ð¾ÐºÐµÐ½Ð° призведе до Ð²Ñ–Ð´ÐºÐ»Ð¸ÐºÐ°Ð½Ð½Ñ Ð´Ð¾Ñтупу до вашого облікового запиÑу Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÑ–Ð², Ñкі його викориÑтовують. Це неможливо ÑкаÑувати. Продовжити?
+delete_token_success=Токен знищено. Додатки, що викориÑтовують його, більше не мають доÑтупу до вашого облікового запиÑу.
+repo_and_org_access=ДоÑтуп до Ñховища та організації
+permissions_public_only=Лише загальнодоÑтупні
+permissions_access_all=Ð’ÑÑ– (загальнодоÑтупні, приватні та з обмеженим доÑтупом)
+permission_not_set=Ðе вÑтановлено
+permission_no_access=Ðемає доÑтупу
+permission_read=ЧитаннÑ
+permission_write=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ– запиÑ
+permission_anonymous_read=Ðнонімне читаннÑ
+permission_everyone_read=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ…
+permission_everyone_write=Ð—Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ…
+access_token_desc=Обрані дозволи токена обмежують авторизацію лише відповідними маршрутами <a %s>API</a>. Читайте <a %s>документацію</a> Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації.
+at_least_one_permission=Ðеобхідно вибрати хоча б одне право доÑтупу Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ‚Ð¾ÐºÐµÐ½Ð°
+permissions_list=Дозволи:
+
+manage_oauth2_applications=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ°Ð¼Ð¸ OAuth2
+edit_oauth2_application=Редагувати додаток OAuth2
+oauth2_applications_desc=Додатки OAuth2 дозволÑють вашому Ñторонньому додатку безпечно автентифікувати кориÑтувачів у цьому екземплÑрі Gitea.
+remove_oauth2_application=Видалити додаток OAuth2
+remove_oauth2_application_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ° OAuth2 ÑкаÑує доÑтуп до вÑÑ–Ñ… підпиÑаних токенів доÑтупу. Продовжити?
+remove_oauth2_application_success=Додаток видалено.
+create_oauth2_application=Створити новий додаток OAuth2
+create_oauth2_application_button=Створити додаток
+create_oauth2_application_success=Ви уÑпішно Ñтворили новий додаток OAuth2.
+update_oauth2_application_success=Ви уÑпішно оновили додаток OAuth2.
+oauth2_application_name=Ðазва додатка
+oauth2_confidential_client=Конфіденційний клієнт. Виберіть Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼, Ñкі зберігають конфіденційніÑть, наприклад, веб-програм. Ðе обирайте Ð´Ð»Ñ Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¸Ñ… додатків, зокрема ПК та мобільних додатків.
+oauth2_skip_secondary_authorization=ПропуÑтити авторизацію Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ñ–Ñ‡Ð½Ð¸Ñ… клієнтів піÑÐ»Ñ Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу один раз. <strong>Може Ñтановити ризик Ð´Ð»Ñ Ð±ÐµÐ·Ð¿ÐµÐºÐ¸.</strong>
+oauth2_redirect_uris=URI Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ. Будь лаÑка, викориÑтовуйте новий Ñ€Ñдок Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ URI.
save_application=Зберегти
-oauth2_client_id=ID Клієнта
+oauth2_client_id=Ідентифікатор клієнта
oauth2_client_secret=Ключ клієнта
oauth2_regenerate_secret=Відновити ключ
-oauth2_regenerate_secret_hint=Ви втратили Ñвій ключ?
+oauth2_regenerate_secret_hint=Втратили ключ?
oauth2_application_edit=Редагувати
oauth2_application_create_description=Програми OAuth2 надають вашим Ñтороннім програмам доÑтуп до облікових запиÑів кориÑтувачів у цьому екземплÑрі.
+oauth2_application_remove_description=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ OAuth2 не дозволить додатку отримати доÑтуп до авторизованих облікових запиÑів кориÑтувачів на цьому Ñервері. Продовжити?
+oauth2_application_locked=Gitea попередньо реєÑтрує деÑкі програми OAuth2 під Ñ‡Ð°Ñ Ð·Ð°Ð¿ÑƒÑку, Ñкщо це ввімкнено в конфігурації. Щоб запобігти неÑподіваній поведінці, Ñ—Ñ… не можна ні редагувати, ні видалÑти. Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації звернітьÑÑ Ð´Ð¾ документації OAuth2.
authorized_oauth2_applications=Ðвторизовані програми OAuth2
+authorized_oauth2_applications_description=Ви надали цим Ñтороннім додаткам доÑтуп до Ñвого облікового запиÑу Gitea. СкаÑуйте доÑтуп Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÑ–Ð², Ñкі вам більше не потрібні.
revoke_key=Відкликати
revoke_oauth2_grant=СкаÑувати доÑтуп
-revoke_oauth2_grant_description=СкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— програми третьої Ñторони не дозволить їй отримувати доÑтуп до ваших даних. Ви впевнені?
+revoke_oauth2_grant_description=СкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñтороннього додатка не дозволить йому отримувати доÑтуп до ваших даних. Ви впевнені?
+revoke_oauth2_grant_success=ДоÑтуп уÑпішно ÑкаÑовано.
-twofa_is_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° даний Ñ‡Ð°Ñ <strong>викориÑтовує</strong> двофакторну автентифікацію.
-twofa_not_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– не викориÑтовує двофакторну автентифікаціїю.
+twofa_desc=Щоб захиÑтити Ñвій обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñ–Ð´ крадіжки паролÑ, ви можете викориÑтовувати Ñмартфон або інший приÑтрій Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ñ… паролів, прив'Ñзаних до чаÑу (TOTP).
+twofa_recovery_tip=Якщо ви втратите Ñвій приÑтрій, ви зможете ÑкориÑтатиÑÑ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ð¼ ключем відновленнÑ, щоб відновити доÑтуп до Ñвого облікового запиÑу.
+twofa_is_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– <strong>викориÑтовує</strong> двофакторну автентифікацію.
+twofa_not_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– не викориÑтовує двофакторну автентифікацію.
twofa_disable=Вимкнути двофакторну автентифікацію
+twofa_scratch_token_regenerate=Регенерувати одноразовий ключ відновленнÑ
+twofa_scratch_token_regenerated=Ваш одноразовий ключ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚ÐµÐ¿ÐµÑ€ %s. Зберігайте його у безпечному міÑці, оÑкільки його більше не буде показано.
twofa_enroll=Увімкнути двофакторну автентифікацію
-twofa_disable_note=При необхідноÑті можна відключити двофакторну автентифікацію.
+twofa_disable_note=За потреби ви можете вимкнути двофакторну автентифікацію.
twofa_disable_desc=Ð’Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації зробить ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼ÐµÐ½Ñˆ безпечним. Продовжити?
-twofa_disabled=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð°.
-scan_this_image=ПроÑкануйте це Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¸Ð¼ додатком Ð´Ð»Ñ Ð´Ð²ÑƒÑ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації:
-or_enter_secret=Ðбо введіть Ñекрет: %s
+regenerate_scratch_token_desc=Якщо ви втратили ключ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð°Ð±Ð¾ вже викориÑтовували його Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ, ви можете Ñкинути його тут.
+twofa_disabled=Двофакторну автентифікацію вимкнено.
+scan_this_image=ВідÑкануйте це Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¸Ð¼ додатком Ð´Ð»Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації:
+or_enter_secret=Ðбо введіть код: %s
then_enter_passcode=І введіть пароль, Ñкий відображаєтьÑÑ Ð² додатку:
passcode_invalid=Ðекоректний пароль. Спробуй ще раз.
-twofa_failed_get_secret=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ Ñекрет.
+twofa_failed_get_secret=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ код.
+webauthn_register_key=Додати ключ безпеки
+webauthn_nickname=ПÑевдонім
+webauthn_delete_key=Видалити ключ безпеки
+webauthn_delete_key_desc=Якщо ви видалите ключ безпеки, ви більше не зможете ввійти за його допомогою. Продовжити?
+webauthn_key_loss_warning=Якщо ви втратите ключі безпеки, ви втратите доÑтуп до Ñвого облікового запиÑу.
+webauthn_alternative_tip=Ви можете налаштувати додатковий метод автентифікації.
-manage_account_links=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¸Ð¼Ð¸ запиÑами
-manage_account_links_desc=Ці зовнішні акаунти прив'Ñзані до вашого аккаунту Gitea.
+manage_account_links=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²'Ñзаними обліковими запиÑами
+manage_account_links_desc=Ці зовнішні облікові запиÑи прив'Ñзані до вашого облікового запиÑу Gitea.
account_links_not_available=Ðаразі немає зовнішніх облікових запиÑів, пов'Ñзаних із вашим обліковим запиÑом Gitea.
link_account=Прив'Ñзати обліковий запиÑ
-remove_account_link=Видалити облікові запиÑи
+remove_account_link=Видалити обліковий запиÑ
remove_account_link_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð²'Ñзаного облікового запиÑу відкликає його доÑтуп до вашого облікового запиÑу Gitea. Продовжити?
-remove_account_link_success=Зв'Ñзаний обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾.
+remove_account_link_success=Прив'Ñзаний обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾.
+hooks.desc=Додайте веб-хуки, Ñкі запуÑкатимутьÑÑ Ð´Ð»Ñ <strong>уÑÑ–Ñ… репозиторіїв</strong>, Ñкими ви володієте.
-orgs_none=Ви не Ñ” учаÑником будь-Ñкої організації.
+orgs_none=Ви не є членом організації.
+repos_none=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” Ñховищ.
-delete_account=Видалити ваш обліковий запиÑ
-delete_prompt=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñтаточно видалить обліковий Ð·Ð°Ð¿Ð¸Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. Це <strong>ÐЕ МОЖЛИВО</strong> відмінити.
-delete_with_all_comments=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼Ð¾Ð»Ð¾Ð´ÑˆÐ¸Ð¹ за %s днів. Щоб уникнути коментарів-привидів, вÑÑ– запити/PR коментрарі будуть видалені з ним.
-confirm_delete_account=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ
-delete_account_title=Видалити цей обліковий запиÑ
+delete_account=Видалити обліковий запиÑ
+delete_prompt=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñтаточно видалить ваш обліковий запиÑ. Її <strong>ÐЕ МОЖЛИВО</strong> ÑкаÑувати.
+delete_with_all_comments=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼Ð¾Ð»Ð¾Ð´ÑˆÐ¸Ð¹ за %s днів. Щоб уникнути коментарів-привидів, уÑÑ– ваші коментарі будуть видалені разом з ним.
+confirm_delete_account=Підтвердити видаленнÑ
+delete_account_title=Видалити обліковий запиÑ
delete_account_desc=Ви впевнені, що хочете оÑтаточно видалити цей обліковий запиÑ?
-email_notifications.enable=Увімкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ email
-email_notifications.onmention=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ email тільки коли згадують
-email_notifications.disable=Вимкнути email ÑповіщеннÑ
-email_notifications.submit=Ðалаштувати параметри email
+email_notifications.enable=Увімкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою
+email_notifications.onmention=ПовідомлÑти електронною поштою коли згадують
+email_notifications.disable=Вимкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою
+email_notifications.submit=Ðалаштувати параметри електронної пошти
+email_notifications.andyourown=І ваші влаÑні повідомленнÑ
visibility=ВидиміÑть кориÑтувача
visibility.public=Публічний
visibility.limited=Обмежений
+visibility.limited_tooltip=ДоÑтупно лише Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ… кориÑтувачів
visibility.private=Приватний
+visibility.private_tooltip=ДоÑтупно лише Ð´Ð»Ñ Ñ‡Ð»ÐµÐ½Ñ–Ð² організацій, до Ñких ви долучилиÑÑ
[repo]
+new_repo_helper=Сховище міÑтить уÑÑ– файли проєкту, включно з Ñ–Ñторією ревізій. Ви вже розміщуєте його деінде? <a href="%s">ПеренеÑти Ñховище.</a>
owner=ВлаÑник
-owner_helper=ДеÑкі організації можуть не відображатиÑÑ Ñƒ випадаючому ÑпиÑку через макÑимальну кількіÑть репозиторііїв.
-repo_name=Ðазва репозиторію
-repo_size=Розмір репозиторію
+owner_helper=ДеÑкі організації можуть не відображатиÑÑ Ñƒ ÑпиÑку через Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ð° макÑимальну кількіÑть Ñховищ.
+repo_name=Ðазва Ñховища
+repo_name_profile_public_hint=.profile - це Ñпеціальне Ñховище, за допомогою Ñкого ви можете додати README.md до профілю вашої публічної організації, Ñкий буде видимим Ð´Ð»Ñ Ð²ÑÑ–Ñ…. ПереконайтеÑÑ, що він Ñ” публічним, та ініціалізуйте його за допомогою README у каталозі профілю.
+repo_name_helper=У хороших назвах Ñховищ викориÑтовуютьÑÑ ÐºÐ¾Ñ€Ð¾Ñ‚ÐºÑ– ключові Ñлова, Ñкі легко запам'ÑтовуютьÑÑ Ñ‚Ð° Ñ” унікальними. Сховище з назвою «.profile» або «.profile-private» можна викориÑтовувати Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ README.md Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ„Ñ–Ð»ÑŽ кориÑтувача/організації.
+repo_size=Розмір Ñховища
template=Шаблон
-template_select=Оберіть шаблон.
-template_helper=Зробити репозиторій шаблоном
-template_description=Шаблонні репозиторії дозволÑють кориÑтувачам генерувати нові репозиторії із такою ж Ñтруктурою директорій, файлами та додатковими налаштуваннÑми.
+template_select=Обрати шаблон.
+template_helper=Зробити Ñховище шаблоном
+template_description=Шаблонні Ñховища дозволÑють кориÑтувачам Ñтворювати нові Ñховища з такою ж Ñтруктурою каталогів, файлами та додатковими налаштуваннÑми.
visibility=ВидиміÑть
-visibility_description=Тільки влаÑник або члени організації Ñкі мають віповідні права, зможуть побачити.
+visibility_description=Тільки влаÑник або члени організації, Ñкщо вони мають дозвіл, зможуть його побачити.
+visibility_helper=Зробити Ñховище приватним
visibility_helper_forced=ÐдмініÑтратор вашого Ñайту налаштував параметри: вÑÑ– нові репозиторії будуть приватними.
visibility_fork_helper=(Ці зміни вплинуть на вÑÑ– форки.)
clone_helper=Потрібна допомога у клонуванні? Відвідайте Ñторінку <a target="_blank" rel="noopener" href="%s">Допомога</a>.
fork_repo=Форкнути репозиторій
fork_from=Форк з
-fork_visibility_helper=Ðеможливо змінити видиміÑть форкнутого репозиторію.
+fork_visibility_helper=Ðеможливо змінити видиміÑть розгалуженого Ñховища.
+all_branches=УÑÑ– гілки
+view_all_branches=ПереглÑнути вÑÑ– гілки
+view_all_tags=ПереглÑнути вÑÑ– мітки
use_template=ЗаÑтоÑувати цей шаблон
+open_with_editor=Відкрити в %s
download_zip=Завантажити ZIP
download_tar=Завантажити TAR.GZ
download_bundle=Завантажити BUNDLE
-generate_repo=Згенерувати репозиторій
-generate_from=Генерувати з
+generate_repo=Створити Ñховище
+generate_from=Створити з
repo_desc=ОпиÑ
-repo_desc_helper=Введіть короткий Ð¾Ð¿Ð¸Ñ (опціонально)
-repo_gitignore_helper=Виберіть шаблон .gitignore.
-repo_gitignore_helper_desc=Оберіть з ÑпиÑку мовних шаблонів файли, Ñкі не будуть відÑтежуватиÑÑŒ. Типові артефакти, Ñкі генеруютьÑÑ Ð·Ð° допомогою інÑтрументів побудови кожної мови, за замовчуваннÑм включені до .gitignor.
+repo_desc_helper=Введіть короткий Ð¾Ð¿Ð¸Ñ (необов'Ñзково)
+repo_no_desc=Ðемає опиÑу
+repo_lang=Мови
+repo_gitignore_helper=Обрати шаблон .gitignore.
+repo_gitignore_helper_desc=Оберіть з ÑпиÑку мовних шаблонів файли, Ñкі не Ñлід відÑтежувати. Типові артефакти, що генеруютьÑÑ Ñ–Ð½Ñтрументами збірки кожної мови, за замовчуваннÑм включені до .gitignor.
issue_labels=Мітки задачі
-issue_labels_helper=Вибрати мітку Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ–.
+issue_labels_helper=Виберіть набір міток задачі.
license=ЛіцензіÑ
-license_helper=Виберіть ліцензійний файл.
-license_helper_desc=Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ Ñ€ÐµÐ³ÑƒÐ»ÑŽÑ” те, що інші можуть Ñ– не можуть робити з вашим кодом. Ðе впевнені, що Ñаме підходить Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту? ДивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">Виберіть ліцензію.</a>
+license_helper=Обрати файл ліцензії.
+license_helper_desc=Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ Ð²Ð¸Ð·Ð½Ð°Ñ‡Ð°Ñ”, що інші можуть робити з вашим кодом, а що ні. Ðе впевнені, Ñка підходить Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту? ДивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">Вибір ліцензії.</a>
+multiple_licenses=Кілька ліцензій
+object_format=Формат об'єкту
+object_format_helper=Формат об'єкту Ñховища. Ðеможливо буде змінити пізніше. SHA1 Ñ” найбільш ÑуміÑним.
readme=README
readme_helper=Виберіть шаблон README.
-readme_helper_desc=Це міÑце, де ви можете напиÑати повний Ð¾Ð¿Ð¸Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту.
-auto_init=Ініціалізувати репозиторій (Додає .gitignore, LICENSE та README)
+readme_helper_desc=Тут ви можете повніÑтю опиÑати ваш проєкт.
+auto_init=Ініціалізувати Ñховище (додає файли .gitignore, ліцензію та README)
trust_model_helper=Виберіть модель довіри Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñу. Можливі варіанти:
-trust_model_helper_collaborator=Співавтор: підпиÑи довіри від Ñпівавторів
-trust_model_helper_committer=УчаÑник: довірені підпиÑи учаÑтників
-trust_model_helper_collaborator_committer=Співавтор+Комітер: довірчі підпиÑи від Ñпівавторів, Ñкі відповідають комітеру
-trust_model_helper_default=За замовчуваннÑм: викориÑтовувати Ñтандартну модель довіри Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— уÑтановки
-create_repo=Створити репозиторій
+trust_model_helper_collaborator=Співавтор: довірÑти підпиÑам Ñпівавторів
+trust_model_helper_committer=Комітер: довірÑти підпиÑам, Ñкі відповідають комітерам
+trust_model_helper_collaborator_committer=Співавтор+Комітер: довірÑти підпиÑам Ñпівавторів, Ñкі відповідають комітеру
+trust_model_helper_default=Типово: викориÑтовувати Ñтандартну модель довіри Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— уÑтановки
+create_repo=Створити Ñховище
default_branch=Головна гілка
-default_branch_helper=Гілка за замовчуваннÑм Ñ” базовою гілкою Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° комітів коду.
+default_branch_label=типово
+default_branch_helper=Типова гілка Ñ” базовою гілкою Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° комітів.
mirror_prune=ОчиÑтити
-mirror_prune_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ñтарілих поÑилань Ñкі ви відÑлідковуєте
-mirror_interval_invalid=Інтервал Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ñ” неприпуÑтимим.
-mirror_address=ÐšÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð· URL-адреÑи
-mirror_address_desc=ПоміÑтіть будь-Ñкі необхідні облікові дані у розділі ÐвторизаціÑ.
-mirror_lfs=Склад великих файлів (LFS)
-mirror_lfs_desc=Ðктивувати дзеркальне Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… LFS.
+mirror_prune_desc=Видалити заÑтарілі поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° віддалені відÑтеженнÑ
+mirror_interval=Інтервал Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ (допуÑтимі одиниці виміру чаÑу 'h', 'm', 's'). 0 - щоб вимкнути періодичну Ñинхронізацію. (Мінімальний інтервал: %s)
+mirror_interval_invalid=Інтервал Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний.
+mirror_sync=Ñинхронізовано
+mirror_sync_on_commit=Синхронізувати, коли надÑилаютьÑÑ ÐºÐ¾Ð¼Ñ–Ñ‚Ð¸
+mirror_address=Клонувати з URL-адреÑи
+mirror_address_desc=Введіть необхідні облікові дані в розділі ÐвторизаціÑ.
+mirror_address_url_invalid=URL-адреÑа недійÑна. Ðеобхідно правильно екранувати вÑÑ– компоненти URL-адреÑи.
+mirror_address_protocol_invalid=URL-адреÑа недійÑна. ДопуÑтимі лише адреÑи http(s):// або git://.
+mirror_lfs=Сховище великих файлів (LFS)
+mirror_lfs_desc=Ðктивувати Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… LFS.
mirror_lfs_endpoint=Кінцева точка LFS
-mirror_lfs_endpoint_desc=Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ñпробує викориÑтовувати url Ð´Ð»Ñ ÐºÐ»Ð¾Ð½Ñƒ щоб <a target="_blank" rel="noopener noreferrer" href="%s">визначити LFS-Ñервер</a>. Ви також можете вказати кінцеву точку кориÑтувача, Ñкщо дані репозиторію LFS зберігаютьÑÑ Ð² іншому міÑці.
+mirror_lfs_endpoint_desc=Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ñпробує викориÑтати URL-адреÑу клону Ð´Ð»Ñ <a target="_blank" rel="noopener noreferrer" href="%s">Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñервера LFS</a>. Ви також можете вказати влаÑну кінцеву точку, Ñкщо дані LFS Ñховища зберігаютьÑÑ Ð² іншому міÑці.
mirror_last_synced=ОÑÑ‚Ð°Ð½Ð½Ñ ÑинхронізаціÑ
mirror_password_placeholder=(без змін)
-mirror_password_blank_placeholder=(відключено)
+mirror_password_blank_placeholder=(Ðе вÑтановлено)
mirror_password_help=Змініть ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача, щоб видалити збережений пароль.
watchers=СпоÑтерігачі
-stargazers=Зацікавлені
+stargazers=Шанувальники
+stars_remove_warning=Це видалить уÑÑ– зірки з цього Ñховища.
forks=Форки
-reactions_more=додати %d більше
-unit_disabled=ÐдмініÑтратор Ñайту вимкнув цей розділ репозиторію.
+stars=Зірки
+reactions_more=і ще %d
+unit_disabled=ÐдмініÑтратор Ñайту вимкнув цей розділ Ñховища.
language_other=Інші
-adopt_search=Введіть ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ неприйнÑтних репозиторіїв... (залиште порожнім, щоб знайти вÑÑ–)
-adopt_preexisting_label=ПрийнÑті файли
-adopt_preexisting=ПрийнÑти вже Ñ–Ñнуючі файли
-adopt_preexisting_content=Створити репозиторій з %s
-adopt_preexisting_success=ПрийнÑти файли та Ñтворити репозиторій з %s
+adopt_search=Введіть ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ неприйнÑтих Ñховищ... (залиште порожнім, щоб знайти вÑÑ–)
+adopt_preexisting_label=ПрийнÑти файли
+adopt_preexisting=ПрийнÑти попередньо Ñтворені файли
+adopt_preexisting_content=Створити Ñховище з %s
+adopt_preexisting_success=ПрийнÑти файли та Ñтворити Ñховище з %s
delete_preexisting_label=Видалити
-delete_preexisting=Видалити Ñ–Ñнуючі файли
+delete_preexisting=Видалити попередньо Ñтворені файли
delete_preexisting_content=Видалити файли з %s
delete_preexisting_success=Видалено неприйнÑті файли в %s
blame_prior=ПереглÑнути анотацію, що передує цій зміні
+blame.ignore_revs.failed=Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ–Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ñ‚Ð¸ ревізії у <a href="%s">.git-blame-ignore-revs</a>.
+user_search_tooltip=Показує не більше 30 кориÑтувачів
+tree_path_not_found=ШлÑÑ… %[1]s не Ñ–Ñнує в %[2]s
-transfer.accept=Дозволити транÑфер
-transfer.reject=Відхилити транÑфер
+transfer.accept=Дозволити переміщеннÑ
+transfer.accept_desc=`ПереміÑтити до "%s"`
+transfer.reject=Відхилити переміщеннÑ
+transfer.reject_desc=`СкаÑувати Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð´Ð¾ "%s"`
+transfer.no_permission_to_accept=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” дозволу приймати цю передачу.
+transfer.no_permission_to_reject=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” дозволу на Ð²Ñ–Ð´Ñ…Ð¸Ð»ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— передачі.
desc.private=Приватний
desc.public=Публічний
+desc.public_access=Публічний доÑтуп
desc.template=Шаблон
desc.internal=Внутрішній
-desc.archived=Ðрхівний
+desc.archived=Ðрхівований
+desc.sha256=SHA256
template.items=Елементи шаблону
template.git_content=ВміÑÑ‚ Git (типова гілка)
-template.git_hooks=Перехоплювачі Git
-template.webhooks=Webhook'и
+template.git_hooks=Хуки Git
+template.git_hooks_tooltip=Ðаразі ви не можете змінювати або видалÑти Git-хуки піÑÐ»Ñ Ñ—Ñ… додаваннÑ. Виберіть це лише Ñкщо ви довірÑєте Ñховищу шаблонів.
+template.webhooks=Веб-хуки
template.topics=Теми
template.avatar=Ðватар
template.issue_labels=Мітки задачі
template.one_item=Слід обрати хоча б один елемент шаблону
-template.invalid=Слід обрати шаблонний репозиторій
+template.invalid=Слід обрати шаблонне Ñховище
-archive.issue.nocomment=Цей репозиторій архівовано. Ви не можете коментувати задачі.
-archive.pull.nocomment=Це архівний репозитарій. Ви не можете коментувати пулл-реквеÑти.
+archive.title=Це архівне Ñховище. Ви можете переглÑдати й клонувати файли, але не можете завантажувати Ñвої зміни або відкривати задачі чи запити на злиттÑ.
+archive.title_date=Це архівне Ñховище на %s. Ви можете переглÑдати файли й клонувати його, але не можете завантажувати Ñвої зміни або відкривати задачі чи запити на злиттÑ.
+archive.issue.nocomment=Це Ñховище архівовано. Ви не можете коментувати задачі.
+archive.pull.nocomment=Це Ñховище архівовано. Ви не можете коментувати запити на злиттÑ.
-form.reach_limit_of_creation_1=Ви вже доÑÑгли ліміту в %d репозиторіїв.
-form.reach_limit_of_creation_n=Ви доÑÑгли макÑимальної кількоÑті %d Ñтворених репозиторіїв.
+form.reach_limit_of_creation_1=Ви доÑÑгли макÑимальної кількоÑті %d Ñховища.
+form.reach_limit_of_creation_n=Ви доÑÑгли макÑимальної кількоÑті %d Ñховищ.
+form.name_reserved=Ðазву Ñховища '%s' зарезервовано.
+form.name_pattern_not_allowed=Шаблон '%s' не дозволено в назві Ñховища.
need_auth=ÐвторизаціÑ
migrate_options=Параметри міграції
migrate_service=Ð¡ÐµÑ€Ð²Ñ–Ñ Ð¼Ñ–Ð³Ñ€Ð°Ñ†Ñ–Ñ—
-migrate_options_lfs=ПеренеÑÐµÐ½Ð½Ñ LFS файлів
+migrate_options_mirror_helper=Це Ñховище буде дзеркалом
+migrate_options_lfs=ПеренеÑÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð² LFS
migrate_options_lfs_endpoint.label=Кінцева точка LFS
-migrate_options_lfs_endpoint.description=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ намагатиÑÑ Ð²Ð¸ÐºÐ¾Ñ€Ð¸Ñтовувати ваш Git віддалено, щоб <a target="_blank" rel="noopener noreferrer" href="%s">визначати LFS Ñервер</a>. Ви також можете вказати Ñвою кінцеву точку, Ñкщо дані репозиторію LFS зберігаютьÑÑ Ð² іншому міÑці.
-migrate_options_lfs_endpoint.description.local=Також підтримуютьÑÑ ÑˆÐ»Ñхи на локальному Ñервері.
-migrate_items=Деталі міграції
+migrate_options_lfs_endpoint.description=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñпробує викориÑтати ваш Git віддалено, щоб <a target="_blank" rel="noopener noreferrer" href="%s">визначати Ñервер LFS</a>. Ви також можете вказати влаÑну кінцеву точку, Ñкщо дані Ñховища LFS зберігаютьÑÑ Ð² іншому міÑці.
+migrate_options_lfs_endpoint.description.local=Також підтримуєтьÑÑ ÑˆÐ»ÑÑ… до локального Ñервера.
+migrate_items=Елементи міграції
migrate_items_wiki=Вікі
migrate_items_milestones=Етапи
migrate_items_labels=Мітки
@@ -827,26 +1164,34 @@ migrate_items_merge_requests=Запити на злиттÑ
migrate_items_releases=Релізи
migrate_repo=ПеренеÑти репозиторій
migrate.clone_address=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ / клонувати з URL-адреÑи
-migrate.clone_address_desc=URL-адреÑа HTTP(S) або Git "clone" Ñ–Ñнуючого репозиторіÑ
+migrate.clone_address_desc=URL-адреÑа HTTP(S) або Git "clone" Ñ–Ñнуючого Ñховища
+migrate.github_token_desc=Ви можете додати один або декілька токенів через кому, щоб пришвидшити міграцію через Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ ÑˆÐ²Ð¸Ð´ÐºÐ¾Ñті API GitHub. ПОПЕРЕДЖЕÐÐЯ: Ð—Ð»Ð¾Ð²Ð¶Ð¸Ð²Ð°Ð½Ð½Ñ Ñ†Ñ–Ñ”ÑŽ функцією може порушити політику поÑтачальника поÑлуг Ñ– призвеÑти до Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу.
migrate.clone_local_path=або шлÑÑ… до локального Ñерверу
migrate.permission_denied=Вам не дозволено імпортувати локальні репозиторії.
migrate.permission_denied_blocked=Ви не можете імпортувати з заборонених вузлів, будь лаÑка, попроÑіть адмініÑтратора перевірити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS.
-migrate.invalid_lfs_endpoint=Помилкова кінцева точка LFS.
+migrate.invalid_local_path=Локальний шлÑÑ… недійÑний. Він не Ñ–Ñнує або не Ñ” каталогом.
+migrate.invalid_lfs_endpoint=Кінцева точка LFS недійÑна.
migrate.failed=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ: %v
migrate.migrate_items_options=Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ½ÐµÑÐµÐ½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¸Ñ… елементів потрібен токен доÑтупу
migrated_from=ПеренеÑено з <a href="%[1]s">%[2]s</a>
migrated_from_fake=ПеренеÑено з %[1]s
-migrate.migrate=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð· %s
+migrate.migrate=Мігрувати з %s
migrate.migrating=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· <b>%s</b>...
migrate.migrating_failed=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· <b>%s</b> не вдалаÑÑ.
+migrate.migrating_failed.error=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ½ÐµÑти: %s
migrate.migrating_failed_no_addr=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ.
-migrate.git.description=ПеренеÑÐµÐ½Ð½Ñ Ð»Ð¸ÑˆÐµ репозиторію з будь-Ñкої Ñлужби Git.
+migrate.github.description=ПеренеÑти дані з github.com чи інших Ñерверів Github.
+migrate.git.description=ПеренеÑти Ñховище з будь-Ñкого ÑервіÑу Git'у.
migrate.gitlab.description=ПеренеÑти дані з gitlab.com та інших екземплÑрів GitLab.
migrate.gitea.description=ПеренеÑти дані з gitea.com та інших екземплÑрів Gitea.
migrate.gogs.description=ПеренеÑти дані з notabug.org та інших екземплÑрів Gogs.
migrate.onedev.description=ПеренеÑти дані з code.onedev.io та інших екземплÑрів OneDev.
migrate.codebase.description=ПеренеÑти дані з codebasehq.com.
migrate.gitbucket.description=ПеренеÑти дані з екземплÑрів GitBucket.
+migrate.codecommit.description=ПеренеÑÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… з AWS CodeCommit.
+migrate.codecommit.aws_access_key_id=ID ключа доÑтупу AWS
+migrate.codecommit.https_git_credentials_username=Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача HTTPS Git
+migrate.codecommit.https_git_credentials_password=Пароль кориÑтувача HTTPS Git
migrate.migrating_git=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Git даних
migrate.migrating_topics=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ‚ÐµÐ¼
migrate.migrating_milestones=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ ÐµÑ‚Ð°Ð¿Ñ–Ð²
@@ -854,26 +1199,34 @@ migrate.migrating_labels=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð¼Ñ–Ñ‚Ð¾Ðº
migrate.migrating_releases=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ€ÐµÐ»Ñ–Ð·Ñ–Ð²
migrate.migrating_issues=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð·Ð°Ð´Ð°Ñ‡
migrate.migrating_pulls=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на злиттÑ
+migrate.cancel_migrating_title=СкаÑувати міграцію
+migrate.cancel_migrating_confirm=Ви хочете ÑкаÑувати міграцію?
+migration_status=CÑ‚Ð°Ñ‚ÑƒÑ Ð¼Ñ–Ð³Ñ€Ð°Ñ†Ñ–Ñ—
mirror_from=дзеркало
forked_from=форк від
generated_from=згенеровано з
-fork_from_self=Ви не можете форкнути репозиторій, так Ñк ви його влаÑник.
+fork_from_self=Ви не можете форкнути влаÑне Ñховище.
fork_guest_user=Увійдіть, щоб зробити форк репозитарію.
watch_guest_user=Увійдіть, щоб Ñлідкувати за цим репозиторієм.
star_guest_user=Увійдіть, щоб додати в обране цей репозиторій.
unwatch=Ðе Ñтежити
-watch=Слідкувати
+watch=Стежити
unstar=Видалити із обраних
star=В обрані
fork=Форк
+action.blocked_user=Ðеможливо виконати дію, оÑкільки ви заблоковані влаÑником Ñховища.
download_archive=Скачати репозиторій
+more_operations=Інші операції
quick_guide=Короткий поÑібник
clone_this_repo=Кнонувати цей репозиторій
+cite_this_repo=ПоÑлатиÑÑ Ð½Ð° це Ñховище
create_new_repo_command=Створити новий репозиторій з командного Ñ€Ñдка
push_exist_repo=Опублікувати Ñ–Ñнуючий репозиторій з командного Ñ€Ñдка
-empty_message=Цей репозиторій порожній.
+empty_message=Це Ñховище порожнє.
+broken_message=Ðеможливо прочитати дані Git, що лежать в оÑнові цього Ñховища. ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора Ñервера або видаліть Ñховище.
+no_branch=Це Ñховище не має гілок.
code=Код
code.desc=ДоÑтуп до коду, файлів, комітів та гілок.
@@ -887,96 +1240,153 @@ tags=Теги
issues=Задачі
pulls=Запити на злиттÑ
projects=Проєкти
+packages=Пакети
+actions=Дії
labels=Мітки
org_labels_desc=Мітки Ñ€Ñ–Ð²Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— можуть викориÑтовуватиÑÑ <strong>в уÑÑ–Ñ… репозиторіÑÑ…</strong> цієї організації
org_labels_desc_manage=керувати
milestone=Етап
-milestones=Етап
+milestones=Етапи
commits=Коміти
commit=Коміт
release=Реліз
releases=Релізи
tag=Тег
-released_this=випущені релізи
+released_this=випуÑтив(-ла)
+file.title=%s в %s
file_raw=Ðеформатований
file_history=ІÑторіÑ
file_view_source=ПереглÑнути вихідний код
-file_view_rendered=ПереглÑнути відрендерено
file_view_raw=ПереглÑд Raw
file_permalink=ПоÑтійне поÑиланнÑ
-file_too_large=Цей файл завеликий щоб бути показаним.
-
+file_too_large=Файл занадто великий Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ.
+file_is_empty=Файл порожній.
+code_preview_line_from_to=РÑдки від %[1]d до %[2]d в %[3]s
+code_preview_line_in=РÑдок %[1]d в %[2]s
+invisible_runes_header=`Цей файл міÑтить невидимі Ñимволи Юнікоду`
+invisible_runes_description=`Цей файл міÑтить невидимі Ñимволи Юнікоду, Ñкі не розрізнÑютьÑÑ Ð»ÑŽÐ´Ð¸Ð½Ð¾ÑŽ, але можуть по-різному оброблÑтиÑÑ ÐºÐ¾Ð¼Ð¿'ютером. Якщо ви вважаєте, що це зроблено навмиÑно, можете Ñміливо ігнорувати це попередженнÑ. Щоб показати Ñ—Ñ…, ÑкориÑтайтеÑÑ ÐºÐ½Ð¾Ð¿ÐºÐ¾ÑŽ Escape.`
+ambiguous_runes_header=`Цей файл міÑтить неоднозначні Ñимволи Юнікоду`
+ambiguous_runes_description=`Цей файл міÑтить Ñимволи Юнікоду, Ñкі можна Ñплутати з іншими Ñимволами. Якщо ви вважаєте, що це зроблено навмиÑно, можете Ñміливо ігнорувати це попередженнÑ. Щоб показати Ñ—Ñ…, ÑкориÑтайтеÑÑ ÐºÐ½Ð¾Ð¿ÐºÐ¾ÑŽ Escape.`
+invisible_runes_line=`Цей Ñ€Ñдок міÑтить невидимі Ñимволи Юнікоду`
+ambiguous_runes_line=`Цей Ñ€Ñдок міÑтить неоднозначні Ñимволи Юнікоду`
+ambiguous_character=`%[1]c [U+%04[1]X] можна Ñплутати з %[2]c [U+%04[2]X]`
+
+escape_control_characters=Екранувати
+unescape_control_characters=Відмінити екрануваннÑ
file_copy_permalink=Копіювати поÑтійне поÑиланнÑ
video_not_supported_in_browser=Ваш браузер не підтримує тег 'video' HTML5.
audio_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 'audio'.
symbolic_link=Символічне поÑиланнÑ
+executable_file=Виконуваний файл
+generated=Створено
commit_graph=Графік комітів
commit_graph.select=Виберіть гілки
commit_graph.hide_pr_refs=Приховати запити на злиттÑ
-commit_graph.monochrome=Монохром
+commit_graph.monochrome=Монохромний
commit_graph.color=Колір
-blame=ЗвинуваченнÑ
+commit.contained_in=Цей коміт міÑтитьÑÑ Ð²:
+commit.contained_in_default_branch=Цей коміт Ñ” чаÑтиною типової гілки
+commit.load_referencing_branches_and_tags=Завантажити гілки та мітки, Ñкі поÑилаютьÑÑ Ð½Ð° цей коміт
+blame=ÐнотаціÑ
download_file=Завантажити файл
normal_view=Звичайний виглÑд
line=Ñ€Ñдок
lines=Ñ€Ñдки
+from_comment=(коментар)
+editor.add_file=Додати файл
editor.new_file=Ðовий файл
editor.upload_file=Завантажити файл
-editor.edit_file=Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ
+editor.edit_file=Редагувати файл
editor.preview_changes=Попередній переглÑд змін
editor.cannot_edit_lfs_files=Файли LFS не можна редагувати в веб-інтерфейÑÑ–.
+editor.cannot_edit_too_large_file=Файл занадто великий Ð´Ð»Ñ Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ.
editor.cannot_edit_non_text_files=Бінарні файли не можливо редагувати у веб-інтерфейÑÑ–.
+editor.file_not_editable_hint=Ðле ви вÑе ще можете перейменувати або переміÑтити його.
editor.edit_this_file=Редагувати файл
editor.this_file_locked=Файл заблоковано
-editor.must_be_on_a_branch=Ви повинні бути у гілці щоб зробити, або запропонувати зміни до цього файлу.
-editor.fork_before_edit=Ðеобхідно зробити форк цього репозиторій, щоб внеÑти або запропонувати зміни в цей файл.
+editor.must_be_on_a_branch=Ви повинні бути у гілці щоб робити або пропонувати зміни до цього файлу.
+editor.fork_before_edit=Ðеобхідно зробити форк цього Ñховища, щоб внеÑти або запропонувати зміни в цей файл.
editor.delete_this_file=Видалити файл
editor.must_have_write_access=Ви повинні мати доÑтуп на Ð·Ð°Ð¿Ð¸Ñ Ñ‰Ð¾Ð± запропонувати зміни до цього файлу.
-editor.name_your_file=Дайте назву файлу…
-editor.filename_help=Щоб додати каталог, наберіть його назву, а потім - коÑу риÑку ('/'). Щоб видалити каталог, перейдіть до початку Ð¿Ð¾Ð»Ñ Ñ– натиÑніть backspace.
+editor.file_delete_success=Файл "%s" видалено.
+editor.name_your_file=Ðазвіть файл…
+editor.filename_help=Щоб додати каталог, наберіть його назву, а потім - прÑмий Ñлеш ('/'). Щоб видалити каталог, перейдіть до початку Ð¿Ð¾Ð»Ñ Ñ– натиÑніть видалити ліворуч.
editor.or=або
editor.cancel_lower=СкаÑувати
editor.commit_signed_changes=ВнеÑти підпиÑані зміни
-editor.commit_changes=Закомітити зміни
+editor.commit_changes=ЗафікÑувати зміни
+editor.add_tmpl=Додати '{filename}'
+editor.add=Додати %s
+editor.update=Оновити %s
+editor.delete=Видалити %s
+editor.patch=ЗаÑтоÑувати патч
+editor.patching=ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½ÑŒ:
+editor.new_patch=Ðовий патч
editor.commit_message_desc=Додати необов'Ñзковий розширений опиÑ…
-editor.signoff_desc=Додатиь Signed-off-by комітом в конці Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¶ÑƒÑ€Ð½Ð°Ð»Ñƒ комітів.
-editor.commit_directly_to_this_branch=Зробіть коміт прÑмо в гілку <strong class="branch-name">%s</strong>.
+editor.signoff_desc=Додати «ПідпиÑано комітером» в кінці Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ.
+editor.commit_directly_to_this_branch=Зробити коміт безпоÑередньо в гілку <strong class="branch-name">%s</strong>.
editor.create_new_branch=Створити <strong>нову гілку</strong> Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту та відкрити запит на злиттÑ.
editor.create_new_branch_np=Створити <strong>нову гілку</strong> Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту.
editor.propose_file_change=Запропонувати зміну файлу
-editor.new_branch_name_desc=Ім'Ñ Ð½Ð¾Ð²Ð¾Ñ— гілки…
+editor.new_branch_name=Ðазвіть нову гілку Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту
+editor.new_branch_name_desc=Ðазва нової гілки…
editor.cancel=Відмінити
-editor.filename_cannot_be_empty=Ім'Ñ Ñ„Ð°Ð¹Ð»Ñƒ не може бути порожнім.
+editor.filename_cannot_be_empty=Ðазва файлу не може бути порожньою.
+editor.filename_is_invalid=Ðазва файлу недійÑна: "%s".
+editor.commit_email=Електронна пошта коміту
+editor.invalid_commit_email=ÐдреÑа електронної пошти Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ недійÑна.
+editor.file_is_a_symlink=`"%s" - це Ñимволічне поÑиланнÑ. Символічні поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ðµ можна редагувати у веб-редакторі`
+editor.filename_is_a_directory=Ðазва файлу '%s' вже викориÑтовуєтьÑÑ Ñк назва каталогу у цьому Ñховищі.
+editor.file_modifying_no_longer_exists=Редагований файл '%s' більше не Ñ–Ñнує в цьому Ñховищі.
editor.file_changed_while_editing=ЗміÑÑ‚ файлу змінивÑÑ Ð· моменту початку редагуваннÑ. <a target="_blank" rel="noopener" href="%s"> ÐатиÑніть тут </a>, щоб переглÑнути що було змінено, або <strong>закомітьте зміни ще раз</strong>, щоб перепиÑати Ñ—Ñ….
editor.commit_empty_file_header=Закомітити порожній файл
-editor.commit_empty_file_text=Файл, в комміті порожній. Продовжити?
-editor.no_changes_to_show=Ðема змін Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ.
-editor.fail_to_update_file_summary=Помилка:
+editor.commit_empty_file_text=Файл, Ñкий ви збираєтеÑÑ Ð·Ð°ÐºÐ¾Ð¼Ñ–Ñ‚Ð¸Ñ‚Ð¸, порожній. Продовжувати?
+editor.no_changes_to_show=Ðемає змін.
+editor.push_rejected_no_message=Зміну відхилено Ñервером без повідомленнÑ. Будь лаÑка, перевірте Git-хуки.
+editor.push_rejected=Зміну відхилено Ñервером. Будь лаÑка, перевірте Git-хуки.
editor.push_rejected_summary=Повне Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ відмову:
editor.add_subdir=Додати каталог…
+editor.unable_to_upload_files=Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ файли до '"%s". Помилка: %v
+editor.upload_file_is_locked=Файл "%s" заблоковано %s.
+editor.upload_files_to_dir=`Завантажити файли до "%s"`
editor.no_commit_to_branch=Ðе вдалоÑÑ Ð²Ð½ÐµÑти коміт безпоÑередньо до гілки, тому що:
-editor.user_no_push_to_branch=КориÑтувач не може здійÑнити пуш до гілки
editor.require_signed_commit=Гілка вимагає підпиÑаного коміту
+editor.revert=Повернути %s до:
+editor.failed_to_commit=Ðе вдалоÑÑ Ð·Ð°Ñ„Ñ–ÐºÑувати зміни.
+editor.failed_to_commit_summary=Помилка:
+
+editor.fork_edit_description=Ви не можете редагувати це Ñховище безпоÑередньо. Зміни буде запиÑано до вашого форку <b>%s</b>, тож ви зможете Ñтворити запит на злиттÑ.
commits.desc=ПереглÑнути Ñ–Ñторію зміни коду.
commits.commits=Коміти
+commits.no_commits=Ðемає Ñпільних комітів. '%s' та '%s' мають різну Ñ–Ñторію.
commits.nothing_to_compare=Ці гілки однакові.
+commits.search_branch=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ°
commits.search_all=УÑÑ– гілки
commits.author=Ðвтор
commits.message=ПовідомленнÑ
commits.date=Дата
-commits.older=Давніше
-commits.newer=Ðовіше
+commits.older=Старіші
+commits.newer=Ðовіші
commits.signed_by=ПідпиÑано
commits.signed_by_untrusted_user=ПідпиÑаний недовіреним кориÑтувачем
-commits.signed_by_untrusted_user_unmatched=ПідпиÑаний недовіреним кориÑтувачем, Ñкий не відповідає комітеру
+commits.signed_by_untrusted_user_unmatched=ПідпиÑано недовіреним кориÑтувачем, Ñкий не відповідає комітеру
commits.gpg_key_id=Ідентифікатор GPG ключа
+commits.ssh_key_fingerprint=Відбиток ключа SSH
+commits.view_path=ПереглÑнути в Ñ–Ñторії
+commits.view_file_diff=ПереглÑнути зміни до цього файлу в цьому коміті
+commit.operations=Дії
+commit.revert=Повернути до попереднього Ñтану
+commit.revert-header=Повернути: %s
+commit.revert-content=Виберіть гілку, до Ñкої хочете повернутиÑÑ:
commitstatus.error=Помилка
+commitstatus.failure=Ðевдача
commitstatus.pending=ОчікуваннÑ
+commitstatus.success=УÑпіх
ext_issues=ДоÑтуп до зовнішніх задач
ext_issues.desc=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° зовнішню ÑиÑтему відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡.
@@ -986,23 +1396,35 @@ projects.description_placeholder=ОпиÑ
projects.create=Створити проєкт
projects.title=Ðазва
projects.new=Ðовий проєкт
-projects.new_subheader=Координуйте, відÑтежуйте та оновлюйте інформацію про виконувану роботу в одному міÑці, аби проєкти залишалиÑÑ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€Ð¸Ð¼Ð¸ та за розкладом.
+projects.new_subheader=Координуйте, відÑтежуйте та оновлюйте Ñвою роботу в одному міÑці, щоб проєкти залишалиÑÑ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€Ð¸Ð¼Ð¸ та виконувалиÑÑ Ð·Ð° графіком.
+projects.create_success=Проєкт "%s" Ñтворено.
projects.deletion=Видалити проєкт
projects.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ñƒ видалÑÑ” його з уÑÑ–Ñ… пов'Ñзаних задач. Продовжити?
projects.deletion_success=Проєкт видалено.
-projects.edit=Редагувати проєкти
+projects.edit=Редагувати проєкт
projects.edit_subheader=Проєкти організовують задачі та відÑтежують прогреÑ.
-projects.modify=Оновити проєкт
+projects.modify=Редагувати проєкт
+projects.edit_success=Проєкт "%s" оновлено.
projects.type.none=ВідÑутній
projects.type.basic_kanban=Спрощений канбан
projects.type.bug_triage=Ð¡Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð¼Ð¸Ð»Ð¾Ðº
projects.template.desc=Шаблон проєкту
-projects.template.desc_helper=Оберіть шаблон проєкту, аби почати
+projects.template.desc_helper=Оберіть шаблон проєкту, щоб розпочати роботу
+projects.column.edit=Редагувати Ñтовпець
projects.column.edit_title=Ðазва
projects.column.new_title=Ðазва
+projects.column.new_submit=Створити Ñтовпець
+projects.column.new=Ðовий Ñтовпець
+projects.column.set_default=Ð’Ñтановити типово
+projects.column.set_default_desc=Ð’Ñтановіть цей Ñтовпець типовим Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ Ñ– запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÐµÐ· категорії
+projects.column.delete=Видалити Ñтовпець
projects.column.color=Колір
projects.open=Відкрити
projects.close=Закрити
+projects.column.assigned_to=Призначено
+projects.card_type.desc=Попередні переглÑди картки
+projects.card_type.images_and_text=Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ– текÑÑ‚
+projects.card_type.text_only=Лише текÑÑ‚
issues.desc=ÐžÑ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð·Ð²Ñ–Ñ‚Ñ–Ð² про помилки, завдань та етапів.
issues.filter_assignees=Фільтр виконавців
@@ -1010,8 +1432,10 @@ issues.filter_milestones=Фільтр етапів
issues.filter_projects=Фільтр проєктів
issues.filter_labels=Фільтр міток
issues.filter_reviewers=Фільтр рецензентів
+issues.filter_no_results=Ðемає результатів
+issues.filter_no_results_placeholder=Спробуйте налаштувати Ñвої фільтри пошуку.
issues.new=Ðова задача
-issues.new.title_empty=Заголовок не може бути пуÑтим
+issues.new.title_empty=Заголовок не може бути порожнім
issues.new.labels=Мітки
issues.new.no_label=Без мітки
issues.new.clear_labels=ОчиÑтити мітки
@@ -1022,15 +1446,21 @@ issues.new.open_projects=Відкриті проєкти
issues.new.closed_projects=Закриті проєкти
issues.new.no_items=Ðемає елементів
issues.new.milestone=Етап
-issues.new.no_milestone=Етап відÑутній
+issues.new.no_milestone=Етапи відÑутні
issues.new.clear_milestone=ОчиÑтити етап
issues.new.assignees=Виконавці
issues.new.clear_assignees=Прибрати виконавців
-issues.new.no_assignees=Ðемає виконавцÑ
-issues.choose.get_started=Початок роботи
+issues.new.no_assignees=Ðемає виконавців
+issues.new.no_reviewers=Ðемає рецензентів
+issues.new.blocked_user=Ðе вдалоÑÑ Ñтворити задачу, тому що ви заблоковані влаÑником Ñховища.
+issues.edit.blocked_user=Ðеможливо редагувати вміÑÑ‚, оÑкільки Ð²Ð°Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾ автором або влаÑником Ñховища.
+issues.choose.get_started=Розпочати
issues.choose.open_external_link=Відкрити
issues.choose.blank=Типово
-issues.choose.blank_about=Створити задачу із шаблону за замовчуваннÑм.
+issues.choose.blank_about=Створити задачу із Ñтандартного шаблону.
+issues.choose.ignore_invalid_templates=ÐедійÑні шаблони проігноровано
+issues.choose.invalid_templates=Знайдено %v недійÑний(Ñ…) шаблон(ів)
+issues.choose.invalid_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– міÑтить помилки:
issues.no_ref=Ðе вказана гілка або тег
issues.create=Створити задачу
issues.new_label=Ðова мітка
@@ -1040,7 +1470,8 @@ issues.create_label=Створити мітку
issues.label_templates.title=Завантажити визначений набір міток
issues.label_templates.info=Ще немає міток. ÐатиÑніть 'Ðова мітка' або викориÑтовуйте попередньо визначений набір міток:
issues.label_templates.helper=Оберіть набір міток
-issues.label_templates.use=ВикориÑтовувати набір міток
+issues.label_templates.use=ВикориÑтати набір міток
+issues.label_templates.fail_to_load_file=Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ файл шаблона мітки '%s': %v
issues.add_label=додано %s з міткою %s
issues.add_labels=додано %s з мітками %s
issues.remove_label=видалено %s з міткою %s
@@ -1066,27 +1497,40 @@ issues.delete_branch_at=`видалена гілка <b>%s</b> %s`
issues.filter_label=Мітка
issues.filter_label_exclude=`ВикориÑтовуйте <code>Alt</code> + <code>клік/Enter</code> Ð´Ð»Ñ Ð²Ð¸ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚Ð¾Ðº`
issues.filter_label_no_select=Ð’ÑÑ– мітки
+issues.filter_label_select_no_label=Без мітки
issues.filter_milestone=Етап
+issues.filter_milestone_all=Ð’ÑÑ– етапи
+issues.filter_milestone_none=Етапи відÑутні
+issues.filter_milestone_open=Відкриті етапи
+issues.filter_milestone_closed=Закриті етапи
issues.filter_project=Проєкт
+issues.filter_project_all=Ð’ÑÑ– проєкти
issues.filter_project_none=Проєкт відÑутній
issues.filter_assignee=Виконавець
+issues.filter_assignee_no_assignee=Ðікому не приÑвоєно
+issues.filter_assignee_any_assignee=Призначено будь-кому
+issues.filter_poster=Ðвтор
+issues.filter_user_placeholder=Пошук кориÑтувачів
+issues.filter_user_no_select=УÑÑ– кориÑтувачі
issues.filter_type=Тип
issues.filter_type.all_issues=Ð’ÑÑ– задачі
+issues.filter_type.all_pull_requests=УÑÑ– запити на злиттÑ
issues.filter_type.assigned_to_you=Призначене вам
issues.filter_type.created_by_you=Створено вами
issues.filter_type.mentioning_you=Ð’Ð°Ñ Ð·Ð³Ð°Ð´Ð°Ð½Ð¾
-issues.filter_type.review_requested=Відгук запитано
+issues.filter_type.review_requested=Запит на рецензію
+issues.filter_type.reviewed_by_you=Перевірено вами
issues.filter_sort=Сортувати
issues.filter_sort.latest=Ðайновіші
issues.filter_sort.oldest=ÐайÑтаріші
-issues.filter_sort.recentupdate=Ðещодавно оновлено
+issues.filter_sort.recentupdate=Ðещодавно оновлені
issues.filter_sort.leastupdate=Ðайдавніше оновлені
issues.filter_sort.mostcomment=Ðайбільш коментовані
issues.filter_sort.leastcomment=Ðайменш коментовані
-issues.filter_sort.nearduedate=Ðайновіша дата
-issues.filter_sort.farduedate=ÐайÑтаріша дата
-issues.filter_sort.moststars=Ðайбільш обраних
-issues.filter_sort.feweststars=Ðайменш обраних
+issues.filter_sort.nearduedate=Ðайближчий термін
+issues.filter_sort.farduedate=Ðайвіддаленіший термін
+issues.filter_sort.moststars=Ðайбільш фаворизовані
+issues.filter_sort.feweststars=Ðайменш фаворизовані
issues.filter_sort.mostforks=Ðайбільше форків
issues.filter_sort.fewestforks=Ðайменше форків
issues.action_open=Відкрити
@@ -1096,13 +1540,16 @@ issues.action_milestone=Етап
issues.action_milestone_no_select=Етап відÑутній
issues.action_assignee=Виконавець
issues.action_assignee_no_select=Ðемає виконавцÑ
+issues.action_check=Ð’Ñтановити/знÑти позначку
+issues.action_check_all=Ð’Ñтановити/знÑти позначку з уÑÑ–Ñ… елементів
issues.opened_by=%[1]s відкрито <a href="%[2]s">%[3]s</a>
-issues.opened_by_fake=відкрито %[1]s кориÑтувачем %[2]s
+issues.opened_by_fake=%[1]s відкрито кориÑтувачем %[2]s
issues.previous=Попередній
issues.next=Далі
issues.open_title=Відкрито
issues.closed_title=Закрито
issues.draft_title=Чернетка
+issues.num_comments_1=%d коментар
issues.num_comments=%d коментарів
issues.commented_at=`прокоментував(ла) <a href="#%s">%s</a>`
issues.delete_comment_confirm=Ви впевнені, що хочете видалити цей коментар?
@@ -1111,33 +1558,56 @@ issues.context.quote_reply=Цитувати відповідь
issues.context.reference_issue=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð² новій задачі
issues.context.edit=Редагувати
issues.context.delete=Видалити
+issues.no_content=Ðемає опиÑу.
+issues.close=Закрити задачу
+issues.comment_pull_merged_at=об'єднав(-ла) коміти %[1]s в %[2]s %[3]s
+issues.comment_manually_pull_merged_at=вручну об'єднав(-ла) коміти %[1]s в %[2]s %[3]s
+issues.close_comment_issue=Закрити з коментарем
issues.reopen_issue=Відкрити знову
+issues.reopen_comment_issue=Повторно відкрити з коментарем
issues.create_comment=Коментар
-issues.closed_at=`закрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
-issues.reopened_at=`повторно відкрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+issues.comment.blocked_user=Ðеможливо Ñтворити або редагувати коментар, тому що ви заблоковані автором або влаÑником Ñховища.
+issues.closed_at=`закрив(ла) цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+issues.reopened_at=`повторно відкрив(ла) цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`згадано цю задачу в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>`
-issues.ref_issue_from=`<a href="%[3]s">поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+issues.ref_issue_from=`<a href="%[3]s"> вказав(ла) на цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from=`<a href="%[3]s">поÑлавÑÑ Ð½Ð° цей запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">згадав запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s, Ñкі закриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
-issues.ref_reopening_from=`<a href="%[3]s">згадав запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s, Ñкі повторно відкриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+issues.ref_reopening_from=`<a href="%[3]s">згадав запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s, Ñкий знову відкриє цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from=`<a href="%[3]s">закрив цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from=`<a href="%[3]s">повторно відкрито цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_from=`із %[1]s`
+issues.author=Ðвтор
+issues.author_helper=Цей кориÑтувач Ñ” автором.
issues.role.owner=ВлаÑник
+issues.role.owner_helper=Цей кориÑтувач — влаÑник Ñховища.
issues.role.member=УчаÑник
+issues.role.member_helper=Цей кориÑтувач Ñ” учаÑником організації, Ñкій належить це Ñховище.
+issues.role.collaborator=Співавтор
+issues.role.collaborator_helper=Цей кориÑтувач був запрошений до Ñпівпраці у Ñховищі.
+issues.role.first_time_contributor=УчаÑник, Ñкий вперше долучивÑÑ
+issues.role.first_time_contributor_helper=Це перший внеÑок цього кориÑтувача в Ñховищі.
+issues.role.contributor=Співавтор
+issues.role.contributor_helper=Цей кориÑтувач раніше вже вноÑив зміни до Ñховища.
issues.re_request_review=Повторно попроÑити рецензію
issues.is_stale=З чаÑу оÑтанньої перевірки в цей PR було внеÑено деÑкі зміни
issues.remove_request_review=Видалити запит рецензуваннÑ
issues.remove_request_review_block=Ðеможливо видалити запит рецензуваннÑ
-issues.dismiss_review=Відхилити рецензiю
-issues.dismiss_review_warning=Ви впевнені, що хочете відхилити цей відгук?
-issues.sign_in_require_desc=<a href="%s">ПідпишітьÑÑ</a> щоб приєднатиÑÑ Ð´Ð¾ обговореннÑ.
+issues.dismiss_review=Відхилити рецензію
+issues.dismiss_review_warning=Ви впевнені, що хочете відхилити рецензію?
+issues.sign_in_require_desc=<a href="%s">Увійдіть</a>, щоб приєднатиÑÑ Ð´Ð¾ розмови.
issues.edit=Редагувати
issues.cancel=Відмінити
issues.save=Зберегти
issues.label_title=Ðазва мітки
issues.label_description=ÐžÐ¿Ð¸Ñ Ð¼Ñ–Ñ‚ÐºÐ¸
-issues.label_color=Колір мітки
+issues.label_color=Колір
+issues.label_color_invalid=ÐедійÑний колір
+issues.label_exclusive=ЕкÑклюзивно
+issues.label_archive=Мітка архіву
+issues.label_archived_filter=Показати архівовані мітки
+issues.label_archive_tooltip=Ðрхівовані мітки типово виключаютьÑÑ Ð· пропозицій під Ñ‡Ð°Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ за мітками.
+issues.label_exclusive_order=ПорÑдок ÑортуваннÑ
issues.label_count=%d міток
issues.label_open_issues=%d відкритих задач
issues.label_edit=Редагувати
@@ -1145,9 +1615,9 @@ issues.label_delete=Видалити
issues.label_modify=Редагувати мітку
issues.label_deletion=Видалити мітку
issues.label_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚ÐºÐ¸ видалÑÑ” Ñ—Ñ— з уÑÑ–Ñ… задач. Продовжити?
-issues.label_deletion_success=Мітку було видалено.
+issues.label_deletion_success=Мітку видалено.
issues.label.filter_sort.alphabetically=За алфавітом
-issues.label.filter_sort.reverse_alphabetically=З ÐºÑ–Ð½Ñ†Ñ Ð°Ð»Ñ„Ð°Ð²Ñ–Ñ‚Ñƒ
+issues.label.filter_sort.reverse_alphabetically=У зворотному алфавітному порÑдку
issues.label.filter_sort.by_size=Ðайменший розмір
issues.label.filter_sort.reverse_by_size=Ðайбільший розмір
issues.num_participants=%d учаÑників
@@ -1155,17 +1625,21 @@ issues.attachment.open_tab=`ÐатиÑніть щоб побачити "%s" у Ð
issues.attachment.download=`ÐатиÑніть щоб завантажити "%s"`
issues.subscribe=ПідпиÑатиÑÑ
issues.unsubscribe=ВідпиÑатиÑÑ
-issues.lock=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ
-issues.unlock=Ð Ð¾Ð·Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ
-issues.lock_duplicate=Задача не може бути заблокованим двічі.
+issues.unpin=Відкріпити
+issues.max_pinned=Ви не можете прикріпити більше задач
+issues.pin_comment=прикріпив(-ла) %s
+issues.unpin_comment=відкріпив(-ла) %s
+issues.lock=Блокувати обговореннÑ
+issues.unlock=Розблокувати обговореннÑ
+issues.lock_duplicate=ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– не може бути заблоковано двічі.
issues.unlock_error=Ðе можливо розблокувати задачу, Ñка не заблокована.
issues.lock_with_reason=заблоковано Ñк <strong>%s</strong> та обмежене Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñпівавторів %s
issues.lock_no_reason=заблоковано та обмежене Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñпівавторів %s
-issues.unlock_comment=розблоковане Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ %s
+issues.unlock_comment=розблокував(ла) Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ %s
issues.lock_confirm=Заблокувати
issues.unlock_confirm=Розблокувати
issues.lock.notice_1=- Інші кориÑтувачі не можуть додавати нові коментарі до цієї задачі.
-issues.lock.notice_2=- Ви й інші Ñпівавтори, Ñкі мають доÑтуп до цього репозиторію, можете залишати коментарі, Ñкі інші можуть бачити.
+issues.lock.notice_2=- Ви та інші Ñпівавтори, Ñкі мають доÑтуп до цього Ñховища, вÑе ще можете залишати коментарі, Ñкі можуть бачити інші.
issues.lock.notice_3=- Ви завжди зможете розблокувати цю задачу в майбутньому.
issues.unlock.notice_1=- Кожен зможе прокоментувати цю задачу ще раз.
issues.unlock.notice_2=- Ви завжди зможете заблокувати цю задачу в майбутньому.
@@ -1174,37 +1648,61 @@ issues.lock.title=Заблокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— задÐ
issues.unlock.title=Розблокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— задачі.
issues.comment_on_locked=Ви не можете коментувати заблоковану задачу.
issues.delete=Видалити
+issues.delete.title=Видалити цю задачу?
+issues.delete.text=Ви дійÑно хочете видалити цю задачу? (Це видалить веÑÑŒ вміÑÑ‚. ÐатоміÑть подумайте про те, щоб закрити Ñ—Ñ— та зберегти в архіві)
issues.tracker=ВідÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу
-
+issues.timetracker_timer_start=ЗапуÑтити таймер
+issues.timetracker_timer_stop=Зупинити таймер
+issues.timetracker_timer_discard=Скинути таймер
+issues.timetracker_timer_manually_add=Додати чаÑ
+
+issues.time_estimate_set=Ð’Ñтановити орієнтовний чаÑ
+issues.time_estimate_display=Оцінка: %s
+issues.change_time_estimate_at=змінено приблизний Ñ‡Ð°Ñ Ð½Ð° <b>%[1]s</b> %[2]s
+issues.remove_time_estimate_at=видалено оцінку чаÑу %s
+issues.time_estimate_invalid=Ðевірний формат розрахунку чаÑу
issues.tracker_auto_close=Таймер буде автоматично зупинено, коли Ñ†Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° буде закрита
issues.tracking_already_started=`Ви вже почали відÑтежувати Ñ‡Ð°Ñ Ð´Ð»Ñ <a href="%s">іншої задачі</a>!`
+issues.stop_tracking=Зупинити таймер
+issues.stop_tracking_history=працював Ð´Ð»Ñ <b>%[1]s</b> %[2]s
+issues.cancel_tracking=Скинути
+issues.cancel_tracking_history=`ÑкаÑував(-ла) відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу %s`
issues.del_time=Видалити цей журнал чаÑу
+issues.add_time_history=додав(ла) витрачений Ñ‡Ð°Ñ <b>%[1]s</b> %[2]s
issues.del_time_history=`видалив витрачений Ñ‡Ð°Ñ %s`
+issues.add_time_manually=Вручну додати чаÑ
issues.add_time_hours=Години
issues.add_time_minutes=Хвилини
issues.add_time_sum_to_small=Ð§Ð°Ñ Ð½Ðµ введено.
issues.time_spent_total=Загальний витрачений чаÑ
issues.time_spent_from_all_authors=`Загальний витрачений чаÑ: %s`
-issues.due_date=Дата завершеннÑ
-issues.invalid_due_date_format=Дата Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ð¼Ð°Ñ” бути в форматі 'ррр-мм-дд'.
-issues.error_modifying_due_date=Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ дату завершеннÑ.
+issues.due_date=Строк виконаннÑ
+issues.invalid_due_date_format=Формат Ñтроку Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ бути 'рррр-мм-дд'.
+issues.error_modifying_due_date=Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ термін виконаннÑ.
issues.error_removing_due_date=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ дату завершеннÑ.
issues.push_commit_1=додав %d коміт %s
issues.push_commits_n=додав %d коміти(-ів) %s
-issues.force_push_codes=`примуÑово залито %[1]s з <a class="ui sha" href="%[3]s"><code>%[2]s</code></a> до <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s`
+issues.force_push_codes=`примуÑово надіÑлано %[1]s з <a class="ui sha" href="%[3]s"><code>%[2]s</code></a> до <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s`
issues.force_push_compare=ПорівнÑти
issues.due_date_form=рррр-мм-дд
issues.due_date_form_add=Додати дату завершеннÑ
issues.due_date_form_edit=Редагувати
issues.due_date_form_remove=Видалити
+issues.due_date_not_writer=Вам потрібен доÑтуп на Ð·Ð°Ð¿Ð¸Ñ Ð´Ð¾ цього Ñховища, щоб оновити дату Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ–.
issues.due_date_not_set=Термін Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вÑтановлений.
issues.due_date_added=додав(ла) дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ %s %s
+issues.due_date_modified=змінив(-ла) термін Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð· %[2]s на %[1]s %[3]s
issues.due_date_remove=видалив(ла) дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ %s %s
issues.due_date_overdue=ПроÑтрочено
-issues.due_date_invalid=Термін дії не дійÑний або знаходитьÑÑ Ð·Ð° межами допуÑтимого діапазону. Будь лаÑка викориÑтовуйте формат 'yyyy-mm-dd'.
+issues.due_date_invalid=Термін дії не дійÑний або знаходитьÑÑ Ð·Ð° межами діапазону. Будь лаÑка, викориÑтовуйте формат 'рррр-мм-дд'.
issues.dependency.title=ЗалежноÑті
+issues.dependency.issue_no_dependencies=ЗалежноÑтей не вÑтановлено.
+issues.dependency.pr_no_dependencies=ЗалежноÑтей не вÑтановлено.
+issues.dependency.no_permission_1=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” дозволу на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ %d залежноÑті
+issues.dependency.no_permission_n=Ви не маєте дозволу на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ %d залежноÑтей
+issues.dependency.no_permission.can_remove=Ви не маєте дозволу на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ†Ñ–Ñ”Ñ— залежноÑті, але можете видалити Ñ—Ñ—.
issues.dependency.add=Додати залежніÑть…
issues.dependency.cancel=Відмінити
issues.dependency.remove=Видалити
@@ -1212,39 +1710,45 @@ issues.dependency.remove_info=Видалити цю залежніÑть
issues.dependency.added_dependency=`додав нову залежніÑть %s`
issues.dependency.removed_dependency=`видалив залежніÑть %s`
issues.dependency.pr_closing_blockedby=Ð—Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾ наÑтупними задачами
-issues.dependency.issue_closing_blockedby=Ð—Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†Ñ–Ñ”Ñ— задачи заблоковано наÑтупними задачами
-issues.dependency.issue_close_blocks=Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° блокує Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¸Ñ… задач
+issues.dependency.issue_closing_blockedby=Ð—Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†Ñ–Ñ”Ñ— задачі заблоковано наÑтупними задачами
+issues.dependency.issue_close_blocks=Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° блокує Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð½Ð°Ñтупних задач
issues.dependency.pr_close_blocks=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±Ð»Ð¾ÐºÑƒÑ” Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¸Ñ… задач
-issues.dependency.issue_close_blocked=Вам потрібно закрити вÑÑ– задачі, що блокують цю задачу, перед Ñ—Ñ— закриттÑм.
+issues.dependency.issue_close_blocked=Перш ніж закрити це завданнÑ, вам потрібно закрити вÑÑ– завданнÑ, що блокують його.
+issues.dependency.issue_batch_close_blocked=Ðеможливо пакетно закрити обрані задачі, оÑкільки задача #%d вÑе ще має відкриті залежноÑті
issues.dependency.pr_close_blocked=Вам потрібно закрити вÑÑ– задачі, що блокують цей запит, перед його злиттÑм.
issues.dependency.blocks_short=Блоки
issues.dependency.blocked_by_short=Залежить від
issues.dependency.remove_header=Видалити залежніÑть
issues.dependency.issue_remove_text=Це призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾Ñті з цієї задачі. Продовжити?
-issues.dependency.pr_remove_text=Це призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾Ñті з цього пулл-реквеÑту. Продовжити?
+issues.dependency.pr_remove_text=Це вилучить залежніÑть з цього запиту на злиттÑ. Продовжити?
issues.dependency.setting=Увімкнути залежноÑті Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ та запитів на злиттÑ
issues.dependency.add_error_same_issue=Ви не можете зробити задачу залежною від Ñебе.
-issues.dependency.add_error_dep_issue_not_exist=ЗалежніÑть Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– не Ñ–Ñнує.
+issues.dependency.add_error_dep_issue_not_exist=Залежної задачі не Ñ–Ñнує.
issues.dependency.add_error_dep_not_exist=ЗалежніÑть не Ñ–Ñнує.
issues.dependency.add_error_dep_exists=ЗалежніÑть уже Ñ–Ñнує.
issues.dependency.add_error_cannot_create_circular=Ви не можете Ñтворити залежніÑть з двома задачами, Ñкі блокують одна одну.
issues.dependency.add_error_dep_not_same_repo=Обидві задачі повинні бути в одному репозиторії.
-issues.review.self.approval=Ви не можете Ñхвалити влаÑний пулл-реквеÑÑ‚.
+issues.review.self.approval=Ви не можете затвердити влаÑний запит на злиттÑ.
issues.review.self.rejection=Ви не можете надіÑлати запит на зміну на влаÑний пулл-реквеÑÑ‚.
-issues.review.approve=зміни затверджено %s
-issues.review.dismissed=відхилено відгук %s %s
+issues.review.approve=затвердив(ла) ці зміни %s
+issues.review.comment=рецензовано %s
+issues.review.dismissed=відхилив(ла) рецензію %s %s
issues.review.dismissed_label=Відхилено
issues.review.left_comment=додав коментар
-issues.review.content.empty=Запрошуючи зміни, ви зобов'Ñзані залишити коментар з поÑÑненнÑми Ñвоїх побажань відноÑно Pull Request'а.
+issues.review.content.empty=Вам потрібно залишити коментар із зазначеннÑм бажаної зміни (змін).
issues.review.reject=зробив запит змін %s
-issues.review.wait=попроÑив рецензію %s
+issues.review.wait=запроÑили на рецензію %s
issues.review.add_review_request=попроÑив рецензію від %s %s
issues.review.remove_review_request=видалив запит на рецензію до %s %s
issues.review.remove_review_request_self=відмовивÑÑ Ñ€ÐµÑ†ÐµÐ½Ð·ÑƒÐ²Ð°Ñ‚Ð¸ %s
issues.review.pending=ОчікуваннÑ
+issues.review.pending.tooltip=Цей коментар наразі не видно іншим кориÑтувачам. Щоб відправити Ñвій коментар, виберіть "%s" -> "%s/%s/%s" у верхній чаÑтині Ñторінки.
issues.review.review=Рецензії
issues.review.reviewers=Рецензенти
issues.review.outdated=ЗаÑтарілі
+issues.review.outdated_description=ВміÑÑ‚ змінивÑÑ Ð· моменту ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коментарÑ
+issues.review.option.show_outdated_comments=Показати заÑтарілі коментарі
+issues.review.option.hide_outdated_comments=Приховати заÑтарілі коментарі
issues.review.show_outdated=Показати заÑтарілі
issues.review.hide_outdated=Приховати заÑтарілі
issues.review.show_resolved=Показати вирішене
@@ -1252,7 +1756,12 @@ issues.review.hide_resolved=Приховати вирішене
issues.review.resolve_conversation=Завершити обговореннÑ
issues.review.un_resolve_conversation=Поновити обговореннÑ
issues.review.resolved_by=позначив Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ð¼
-issues.review.commented=Коментар
+issues.review.commented=Коментувати
+issues.review.official=Затверджено
+issues.review.requested=ОчікуєтьÑÑ Ñ€Ð¾Ð·Ð³Ð»Ñд
+issues.review.rejected=Запит на зміни
+issues.review.stale=Оновлено з моменту затвердженнÑ
+issues.review.unofficial=Ðевраховане затвердженнÑ
issues.assignee.error=Додано не вÑÑ–Ñ… виконавців через непередбачену помилку.
issues.reference_issue.body=Тіло
issues.content_history.deleted=видалено
@@ -1260,21 +1769,39 @@ issues.content_history.edited=відредаговано
issues.content_history.created=Ñтворено
issues.content_history.delete_from_history=Видалити з Ñ–Ñторії
issues.content_history.delete_from_history_confirm=Видалити з Ñ–Ñторії?
-issues.content_history.options=ÐалаштуваннÑ
+issues.content_history.options=Параметри
+issues.reference_link=ПоÑиланнÑ: %s
compare.compare_base=оÑнова
compare.compare_head=порівнÑти
pulls.desc=Увімкнути запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° оглÑд коду.
pulls.new=Ðовий запит на злиттÑ
+pulls.new.blocked_user=Ðеможливо Ñтворити запит на злиттÑ, тому що ви заблоковані влаÑником Ñховища.
+pulls.new.must_collaborator=Ви повинні бути учаÑником, щоб Ñтворити запит на злиттÑ.
+pulls.edit.already_changed=Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ зміни до запиту на злиттÑ. Схоже, вміÑÑ‚ вже змінено іншим кориÑтувачем. Будь лаÑка, оновіть Ñторінку Ñ– Ñпробуйте редагувати ще раз, щоб уникнути перезапиÑу Ñ—Ñ… змін
+pulls.view=ПереглÑнути запит на злиттÑ
pulls.compare_changes=Ðовий запит на злиттÑ
+pulls.allow_edits_from_maintainers_desc=КориÑтувачі з доÑтупом на Ð·Ð°Ð¿Ð¸Ñ Ð´Ð¾ базової гілки також можуть завантажувати Ñвої зміни до цієї гілки
+pulls.allow_edits_from_maintainers_err=ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ
pulls.compare_changes_desc=ПорівнÑти дві гілки Ñ– Ñтворити запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð·Ð¼Ñ–Ð½.
+pulls.has_viewed_file=ПереглÑдів
+pulls.has_changed_since_last_review=Змінено з моменту вашого оÑтаннього відгуку
+pulls.viewed_files_label=ПереглÑнуто %[1]d / %[2]d файлів
+pulls.expand_files=Розгорнути вÑÑ– файли
+pulls.collapse_files=Згорнути вÑÑ– файли
pulls.compare_base=злити в
-pulls.compare_compare=pull з
-pulls.switch_comparison_type=Перемкнути виглÑд порівнÑннÑ
+pulls.switch_comparison_type=Перемкнути тип порівнÑннÑ
pulls.switch_head_and_base=ПомінÑти міÑцÑми оÑновну та базову гілку
pulls.filter_branch=Фільтр по гілці
+pulls.show_all_commits=Показати вÑÑ– коміти
+pulls.show_changes_since_your_last_review=Показати зміни піÑÐ»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ оÑтаннього відгуку
+pulls.showing_only_single_commit=ВідображаютьÑÑ Ð»Ð¸ÑˆÐµ зміни коміту %[1]s
+pulls.showing_specified_commit_range=ВідображаютьÑÑ Ð»Ð¸ÑˆÐµ зміни між %[1]s..%[2]s
+pulls.select_commit_hold_shift_for_range=Виберіть коміт. ÐатиÑніть клавішу Shift + клацніть, щоб виділити діапазон
+pulls.filter_changes_by_commit=Фільтр за комітом
pulls.nothing_to_compare=Ці гілки однакові. Ðемає необхідноÑті Ñтворювати запитів на злиттÑ.
+pulls.nothing_to_compare_have_tag=Виділена гілка або мітка ідентичні.
pulls.nothing_to_compare_and_allow_empty_pr=Одинакові гілки. Цей PR буде порожнім.
pulls.has_pull_request=`Запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ñ†Ð¸Ñ… гілок вже Ñ–Ñнує: <a href="%[1]s">%[2]s#%[3]d</a>`
pulls.create=Створити запит на злиттÑ
@@ -1284,113 +1811,154 @@ pulls.change_target_branch_at=`змінена цільова гілка з <b>%s
pulls.tab_conversation=ОбговореннÑ
pulls.tab_commits=Коміти
pulls.tab_files=Змінені файли
-pulls.reopen_to_merge=Будь лаÑка перевідкрийте цей запит щоб здіÑнити операцію злиттÑ.
+pulls.reopen_to_merge=Будь лаÑка, заново відкрийте цей запит щоб виконати злиттÑ.
pulls.cant_reopen_deleted_branch=Цей запит не можна повторно відкрити, оÑкільки гілку видалено.
pulls.merged=Злито
+pulls.merged_success=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ ÑƒÑпішно об'єднано Ñ– закрито
+pulls.closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¾
pulls.manually_merged=Ручне злиттÑ
-pulls.is_closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ»Ð¾ закрито.
-pulls.title_wip_desc=`<a href="#">Почніть заголовок з <strong>%s</strong></a> щоб запобігти випадковому злиттю запитів.`
-pulls.cannot_merge_work_in_progress=Цей пулл-реквеÑÑ‚ позначений Ñк прийнÑтий в опрацюваннÑ.
-pulls.still_in_progress=Ð’Ñе ще в процеÑÑ–?
+pulls.merged_info_text=Гілку %s тепер можна видалити.
+pulls.is_closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¾.
+pulls.title_wip_desc=`<a href="#">Почніть заголовок з <strong>%s</strong></a> щоб запобігти випадковому об'єднанню.`
+pulls.cannot_merge_work_in_progress=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾ Ñк незавершений.
+pulls.still_in_progress=Ще не закінчено?
pulls.add_prefix=Додати Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ <strong>%s</strong>
pulls.remove_prefix=Видалити Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ <strong>%s</strong>
-pulls.data_broken=ЗміÑÑ‚ цього запиту було порушено внаÑлідок Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— Форком. Цей запит Ñ‚ÑгнетьÑÑ Ñ‡ÐµÑ€ÐµÐ· відÑутніÑть інформації про вилученнÑ.
-pulls.files_conflicted=Цей запит має зміни, що конфліктують з цільовою гілкою.
+pulls.data_broken=Збій цього запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‡ÐµÑ€ÐµÐ· відÑутніÑть інформації про форк.
+pulls.files_conflicted=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¼Ð°Ñ” зміни, що конфліктують з цільовою гілкою.
+pulls.is_checking=Перевірка конфліктів об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ (merge) ...
+pulls.is_ancestor=Цю гілку вже включено до цільової гілки. Ðема чого об'єднувати.
pulls.required_status_check_failed=ДеÑкі необхідні перевірки виконані з помилками.
pulls.required_status_check_missing=Декілька з необхідних перевірок відÑутні.
pulls.required_status_check_administrator=Як адмініÑтратор ви вÑе одно можете об'єднати цей запит на злиттÑ.
-pulls.can_auto_merge_desc=Цей запит можна об'єднати автоматично.
-pulls.cannot_auto_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ може бути злитий автоматично через конфлікти.
-pulls.cannot_auto_merge_helper=Злийте вручну Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚Ñ–Ð².
-pulls.num_conflicting_files_1=%d конфліктуючий файл
-pulls.num_conflicting_files_n=%d конфліктуючі файли
+pulls.can_auto_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¼Ð¾Ð¶Ð½Ð° об'єднати автоматично.
+pulls.cannot_auto_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ може бути об'єднано автоматично через конфлікти.
+pulls.cannot_auto_merge_helper=Об'єднайте вручну Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚Ñ–Ð².
+pulls.num_conflicting_files_1=%d конфліктний файл
+pulls.num_conflicting_files_n=%d конфліктних файлів
pulls.approve_count_1=%d ÑхваленнÑ
pulls.approve_count_n=%d Ñхвалень
pulls.reject_count_1=%d запит на зміну
-pulls.reject_count_n=%d запити на зміну
-pulls.waiting_count_1=очікуєтьÑÑ %d рецензіÑ
+pulls.reject_count_n=%d запитів на зміну
+pulls.waiting_count_1=очікуєтьÑÑ %d рецензій
pulls.waiting_count_n=очікуєтьÑÑ %d рецензії(й)
-pulls.wrong_commit_id=id коміту повинен бути id коміту в цільовій гілці
-
-pulls.no_merge_desc=Цей запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ злити, оÑкільки вÑÑ– параметри об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾.
-pulls.no_merge_helper=Увімкніть параметри Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð² налаштуваннÑÑ… Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð°Ð±Ð¾ злийте запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ€ÑƒÑ‡Ð½Ñƒ.
-pulls.no_merge_wip=Цей пулл-реквеÑÑ‚ не можливо об'єднати, тому-що він вже виконуєтьÑÑ.
-pulls.no_merge_not_ready=Цей запит не готовий до злиттÑ, перевірте ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ€ÐµÑ†ÐµÐ½Ð·Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ñ– ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸.
-pulls.no_merge_access=Ви не авторизовані, щоб виконати цей запит на злиттÑ.
-pulls.merge_pull_request=Створити коміт зі злиттÑм
+pulls.wrong_commit_id=ідентифікатор коміту повинен бути ідентифікатором коміту в цільовій гілці
+
+pulls.no_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ об'єднати, оÑкільки вÑÑ– параметри об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñховищ вимкнено.
+pulls.no_merge_helper=Увімкніть параметри об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð² налаштуваннÑÑ… Ñховища або об'єднайте запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ€ÑƒÑ‡Ð½Ñƒ.
+pulls.no_merge_wip=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ об'єднаний, оÑкільки він позначений Ñк незавершений.
+pulls.no_merge_not_ready=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‰Ðµ не готовий до об'єднаннÑ, перевірте ÑтатуÑи Ñ€ÐµÑ†ÐµÐ½Ð·Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ñ‚Ð° перевірки.
+pulls.no_merge_access=Ви не авторизовані об'єднувати цей запит на злиттÑ.
+pulls.merge_pull_request=Створити коміт об'єднаннÑ
pulls.rebase_merge_pull_request=Перебазувати, а потім виконати Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿ÐµÑ€ÐµÐ¼Ð¾Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñм
-pulls.rebase_merge_commit_pull_request=Перебазувати, а потім Ñтворити коміт злиттÑ
+pulls.rebase_merge_commit_pull_request=Перебазувати, а потім Ñтворити коміт об'єднаннÑ
pulls.squash_merge_pull_request=Створити зварений (squash) коміт
pulls.merge_manually=Об’єднано вручну
-pulls.merge_commit_id=ID коміту злиттÑ
-pulls.require_signed_wont_sign=Гілка вимагає підпиÑаних комітів, але це Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ буде підпиÑано
+pulls.merge_commit_id=Ідентифікатор коміту об’єднаннÑ
+pulls.require_signed_wont_sign=Гілка вимагає підпиÑаних комітів, але це об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑано
-pulls.invalid_merge_option=Цей параметр Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ можна викориÑтовувати Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Pull Request'а.
-pulls.merge_conflict=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ вдалоÑÑ: Був конфлікт при злиттÑ. Підказка: Ñпробуйте іншу Ñтратегію
+pulls.invalid_merge_option=Цей параметр об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ можна викориÑтовувати Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту на злиттÑ.
+pulls.merge_conflict=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вдалоÑÑ: Під Ñ‡Ð°Ñ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ð¸Ð½Ð¸Ðº конфлікт. Підказка: Спробуйте іншу Ñтратегію
pulls.merge_conflict_summary=Помилка
-pulls.rebase_conflict=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ вдалоÑÑ: відбувÑÑ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚ під Ñ‡Ð°Ñ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ: %[1]s. Підказка: Ñпробуйте іншу Ñтратегію
+pulls.rebase_conflict=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вдалоÑÑ: відбувÑÑ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚ під Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÐ±Ð°Ð·ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ: %[1]s. Підказка: Ñпробуйте іншу Ñтратегію
pulls.rebase_conflict_summary=Помилка
-pulls.unrelated_histories=Помилка злиттÑ: head та base Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ мають Ñпільної Ñ–Ñторії. Підказка: Ñпробуйте іншу Ñтратегію
-pulls.merge_out_of_date=Помилка злиттÑ: base було оновлено, поки відбувалоÑÑ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ. Підказка: Ñпробуйте знову.
pulls.push_rejected_summary=Повне Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ відмову
-pulls.open_unmerged_pull_exists=`Ви не можете знову відкрити, оÑкільки вже Ñ–Ñнує запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ (%d) з того ж Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð· тією ж інформацією про Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– в очікуванні.`
-pulls.status_checking=ДеÑкі перевірки знаходÑтьÑÑ Ð½Ð° розглÑді
+pulls.open_unmerged_pull_exists=`Ви не можете повторно відкрити цей запит на злиттÑ, оÑкільки вже Ñ–Ñнує один (#%d) з ідентичними влаÑтивоÑÑ‚Ñми.`
+pulls.status_checking=ДеÑкі перевірки ще не завершені
pulls.status_checks_success=Ð’ÑÑ– перевірки були уÑпішними
pulls.status_checks_warning=Декілька перевірок завершилиÑÑ Ð· попередженнÑми
-pulls.status_checks_failure=Декілька перевірок не були уÑпішними
+pulls.status_checks_failure=ДеÑкі перевірки не Ñпрацювали
pulls.status_checks_error=Декілька перевірок завершилиÑÑ Ð· помилками
pulls.status_checks_requested=Обов'Ñзково
pulls.status_checks_details=Подробиці
-pulls.update_branch=Оновити гілку шлÑхом злиттÑ
+pulls.status_checks_hide_all=Приховати вÑÑ– перевірки
+pulls.status_checks_show_all=Показати вÑÑ– перевірки
+pulls.update_branch=Оновити гілку шлÑхом об'єднаннÑ
pulls.update_branch_rebase=Оновити гілку перебазуваннÑм
-pulls.update_branch_success=ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð³Ñ–Ð»ÐºÐ¸ пройшло уÑпішно
+pulls.update_branch_success=ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð³Ñ–Ð»ÐºÐ¸ уÑпішне
pulls.update_not_allowed=Ви не можете оновити гілку
pulls.outdated_with_base_branch=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° заÑтаріла відноÑно базової гілки
+pulls.close=Закрити запит на злиттÑ
pulls.closed_at=`закрив цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.reopened_at=`повторно відкрив цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+pulls.cmd_instruction_hint=ПереглÑнути інÑтрукції командного Ñ€Ñдка
+pulls.cmd_instruction_merge_title=Об'єднати
+pulls.cmd_instruction_merge_desc=Об'єднати зміни і оновити на Gitea.
+pulls.clear_merge_message=ОчиÑтити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ об'єднаннÑ
+pulls.auto_merge_button_when_succeed=(Якщо перевірки уÑпішні)
+pulls.auto_merge_when_succeed=Ðвтоматичне об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð¿Ñ–ÑÐ»Ñ ÑƒÑпішного Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… перевірок
+pulls.auto_merge_cancel_schedule=СкаÑувати автоматичне об'єднаннÑ
+pulls.auto_merge_not_scheduled=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ плануєтьÑÑ Ð¾Ð±'єднувати автоматично.
+pulls.delete.title=Видалити цей запит на злиттÑ?
+pulls.upstream_diverging_prompt_behind_1=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° на %[1]d коміт позаду %[2]s
+pulls.upstream_diverging_prompt_behind_n=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° на %[1]d комітів позаду %[2]s
+pulls.upstream_diverging_prompt_base_newer=Базова гілка %s має нові зміни
+pulls.upstream_diverging_merge_confirm=Хочете об’єднати "%[1]s" з "%[2]s"?
+pull.deleted_branch=(видалена):%s
+pull.agit_documentation=ПереглÑнути документацію про AGit
milestones.new=Ðовий етап
milestones.closed=Закрито %s
+milestones.update_ago=Оновлено %s
milestones.no_due_date=Ðемає дати завершеннÑ
milestones.open=Відкрити
milestones.close=Закрити
+milestones.completeness=<strong>%d%%</strong> завершено
milestones.create=Створити етап
milestones.title=Заголовок
milestones.desc=ОпиÑ
-milestones.due_date=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ (опціонально)
+milestones.due_date=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ (необов’Ñзково)
milestones.clear=ОчиÑтити
milestones.invalid_due_date_format=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¼Ð°Ñ” бути в форматі 'рррр-мм-дд'.
+milestones.create_success=Етап "%s" Ñтворено.
milestones.edit=Редагувати етап
-milestones.edit_subheader=Створюйте етапи Ð´Ð»Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— ваших задач.
+milestones.edit_subheader=Етапи впорÑдковують задачі та відÑтежують прогреÑ.
milestones.cancel=Відмінити
milestones.modify=Оновити етап
+milestones.edit_success=Етап '%s' оновлено.
milestones.deletion=Видалити етап
-milestones.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐµÑ‚Ð°Ð¿Ñƒ призведе до його Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð· уÑÑ–Ñ… пов'Ñзаних задач. Продовжити?
-milestones.deletion_success=Етап уÑпішно видалено.
+milestones.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐµÑ‚Ð°Ð¿Ñƒ видалÑÑ” його з уÑÑ–Ñ… пов'Ñзаних з ним задач. Продовжити?
+milestones.deletion_success=Етап видалено.
milestones.filter_sort.name=Ðазва
-milestones.filter_sort.least_complete=Менш повне
-milestones.filter_sort.most_complete=Більш повне
-milestones.filter_sort.most_issues=Ðайбільш задач
-milestones.filter_sort.least_issues=Ðайменш задач
-
-
+milestones.filter_sort.earliest_due_data=Ðайраніший Ñтрок
+milestones.filter_sort.latest_due_date=ОÑтанній Ñтрок
+milestones.filter_sort.most_issues=Ðайбільше задач
+milestones.filter_sort.least_issues=Ðайменше задач
+
+signing.will_sign=Цей коміт буде підпиÑано ключем "%s".
+signing.wont_sign.error=Виникла помилка під Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ можливоÑті підпиÑÐ°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ.
+signing.wont_sign.nokey=Ðемає ключа Ð´Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÐ°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту.
+signing.wont_sign.never=Коміти ніколи не підпиÑуютьÑÑ.
+signing.wont_sign.always=Коміти завжди підпиÑуютьÑÑ.
+signing.wont_sign.pubkey=Коміт не буде підпиÑано, оÑкільки у Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” публічного ключа, пов'Ñзаного з вашим обліковим запиÑом.
+signing.wont_sign.twofa=Ð”Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÐ°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð² у Ð²Ð°Ñ Ð¼Ð°Ñ” бути увімкнена двофакторна автентифікаціÑ.
+signing.wont_sign.parentsigned=Цей коміт не буде підпиÑано, оÑкільки не підпиÑано батьківÑький коміт.
+signing.wont_sign.basesigned=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки базовий коміт не підпиÑано.
+signing.wont_sign.headsigned=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки не підпиÑано головний коміт.
+signing.wont_sign.commitssigned=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки вÑÑ– пов'Ñзані з ним коміти не підпиÑані.
+signing.wont_sign.approved=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ затверджено.
+signing.wont_sign.not_signed_in=Ви не увійшли до ÑиÑтеми.
+
+ext_wiki=ДоÑтуп до зовнішньої вікі
ext_wiki.desc=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° зовнішню вікі.
wiki=Вікі
wiki.welcome=ЛаÑкаво проÑимо до Вікі.
-wiki.welcome_desc=Wiki дозволÑÑ” пиÑати та ділитиÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ з Ñпівавторами.
-wiki.desc=Пишіть та обмінюйтеÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ із Ñпівавторами.
+wiki.welcome_desc=Wiki дозволÑÑ” пиÑати та ділитиÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ зі Ñпівавторами.
+wiki.desc=Пишіть та обмінюйтеÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ зі Ñпівавторами.
wiki.create_first_page=Створити першу Ñторінку
wiki.page=Сторінка
wiki.filter_page=Фільтр Ñторінок
wiki.new_page=Сторінка
+wiki.page_title=Заголовок Ñторінки
+wiki.page_content=ЗміÑÑ‚ Ñторінки
wiki.default_commit_message=Ðапишіть примітку про Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— Ñторінки (необов'Ñзково).
wiki.save_page=Зберегти Ñторінку
wiki.last_commit_info=%s редагував цю Ñторінку %s
@@ -1400,12 +1968,19 @@ wiki.file_revision=Ревізії Ñторінки
wiki.wiki_page_revisions=Ревізії вікі Ñторінок
wiki.back_to_wiki=ПовернутиÑÑŒ на Ñторінку Вікі
wiki.delete_page_button=Видалити Ñторінку
-wiki.page_already_exists=Вікі-Ñторінка з таким Ñамим ім'Ñм вже Ñ–Ñнує.
+wiki.delete_page_notice_1=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ñ–ÐºÑ–-Ñторінки "%s" не може бути ÑкаÑовано. Продовжити?
+wiki.page_already_exists=Сторінка Вікі з такою назвою вже Ñ–Ñнує.
+wiki.reserved_page=Ðазва Ñторінки вікі "%s" зарезервована.
wiki.pages=Сторінки
wiki.last_updated=ОÑтанні Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ %s
wiki.page_name_desc=Введіть назву вікі-Ñторінки. ДеÑкі із Ñпеціальних імен: 'Home', '_Sidebar' та '_Footer'.
+wiki.original_git_entry_tooltip=ПереглÑд оригінального файлу Git заміÑть викориÑÑ‚Ð°Ð½Ð½Ñ Ð´Ñ€ÑƒÐ¶Ð½ÑŒÐ¾Ð³Ð¾ поÑиланнÑ.
activity=ÐктивніÑть
+activity.navbar.pulse=ПульÑ
+activity.navbar.code_frequency=ЧаÑтота коду
+activity.navbar.contributors=Співавтори
+activity.navbar.recent_commits=Ðещодавні коміти
activity.period.filter_label=Період:
activity.period.daily=1 день
activity.period.halfweekly=3 дні
@@ -1415,25 +1990,25 @@ activity.period.quarterly=3 міÑÑці
activity.period.semiyearly=6 міÑÑців
activity.period.yearly=1 рік
activity.overview=ОглÑд
-activity.active_prs_count_1=<strong>%d</strong> Ðктивний запити на злиттÑ
+activity.active_prs_count_1=<strong>%d</strong> Ðктивний запит на злиттÑ
activity.active_prs_count_n=<strong>%d</strong> Ðктивні запити на злиттÑ
-activity.merged_prs_count_1=Злитий запит на злиттÑ
-activity.merged_prs_count_n=Злиті запити на злиттÑ
+activity.merged_prs_count_1=Об'єднаний запит на злиттÑ
+activity.merged_prs_count_n=Об'єднані запити на злиттÑ
activity.opened_prs_count_1=Запропонований запит на злиттÑ
-activity.opened_prs_count_n=Запропонованих запитів на злиттÑ
+activity.opened_prs_count_n=Запропоновані запити на злиттÑ
activity.title.user_1=%d кориÑтувачем
activity.title.user_n=%d кориÑтувачами
activity.title.prs_1=%d Запит на злиттÑ
activity.title.prs_n=%d Запитів на злиттÑ
activity.title.prs_merged_by=%s злито %s
activity.title.prs_opened_by=%s запропоновано %s
-activity.merged_prs_label=Злито
+activity.merged_prs_label=Об'єднано
activity.opened_prs_label=Запропоновано
activity.active_issues_count_1=<strong>%d</strong> Ðктивна задача
activity.active_issues_count_n=<strong>%d</strong> Ðктивні задачі
activity.closed_issues_count_1=Закрита задача
activity.closed_issues_count_n=Закриті задачі
-activity.title.issues_1=%d Задач
+activity.title.issues_1=%d Задача
activity.title.issues_n=%d Задач
activity.title.issues_closed_from=%s закрито %s
activity.title.issues_created_by=%s Ñтворена(Ñ–) %s
@@ -1443,97 +2018,131 @@ activity.new_issues_count_n=Ðові Задачі
activity.new_issue_label=Відкриті
activity.title.unresolved_conv_1=%d Ðезавершене обговореннÑ
activity.title.unresolved_conv_n=%d Ðезавершених обговорень
-activity.unresolved_conv_desc=СпиÑок вÑÑ–Ñ… Ñтарих задач Ñ– Pull Request'ів з недавньої активніÑтю, але ще не закритих або прийнÑтих.
+activity.unresolved_conv_desc=Ці нещодавно змінені задачі Ñ– запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‰Ðµ не вирішені.
activity.unresolved_conv_label=Відкрити
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.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_pushed_1=відправив(ла)
+activity.git_stats_pushed_n=відправили
activity.git_stats_commit_1=%d коміт
-activity.git_stats_commit_n=%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_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_addition_n=%d додатків
activity.git_stats_and_deletions=та
activity.git_stats_deletion_1=%d видалений
activity.git_stats_deletion_n=%d видалені
+contributors.contribution_type.filter_label=Тип внеÑку:
contributors.contribution_type.commits=Коміти
+contributors.contribution_type.deletions=ВидаленнÑ
settings=ÐалаштуваннÑ
-settings.desc=У налаштуваннÑÑ… ви можете змінювати різні параметри цього репозиторіÑ
-settings.options=Репозиторій
+settings.desc=У налаштуваннÑÑ… ви можете керувати параметрами Ñховища
+settings.options=Сховище
+settings.public_access=Публічний доÑтуп
+settings.public_access.docs.everyone_read=Ð’ÑÑ– читають: вÑÑ– зареєÑтровані кориÑтувачі можуть читати розділ. Це також дозволÑÑ” кориÑтувачам Ñтворювати нові задачі/запити на злиттÑ.
+settings.public_access.docs.everyone_write=Ð’ÑÑ– пишуть: вÑÑ– зареєÑтровані кориÑтувачі можуть вноÑити зміни до розділу. Тільки розділ Wiki підтримує цей дозвіл.
settings.collaboration=Співавтори
settings.collaboration.admin=ÐдмініÑтратор
settings.collaboration.write=ЗапиÑ
-settings.collaboration.read=Читати
+settings.collaboration.read=ЧитаннÑ
settings.collaboration.owner=ВлаÑник
settings.collaboration.undefined=Ðе визначено
settings.hooks=Веб-хуки
settings.githooks=Git хуки
settings.basic_settings=Базові налаштуваннÑ
settings.mirror_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð·ÐµÑ€ÐºÐ°Ð»Ð°
-settings.mirror_settings.mirrored_repository=Віддзеркалений репозиторій
+settings.mirror_settings.docs=Ðалаштуйте Ñховище на автоматичну Ñинхронізацію комітів, міток Ñ– гілок з іншим Ñховищем.
+settings.mirror_settings.docs.disabled_pull_mirror.instructions=Ðалаштуйте ваш проєкт на автоматичне перенеÑÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð², міток Ñ– гілок до іншого Ñховища. ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð·Ð¼Ñ–Ð½ з дзеркал було вимкнено адмініÑтратором вашого Ñайту.
+settings.mirror_settings.docs.disabled_push_mirror.instructions=Ðалаштуйте Ñвій проєкт на автоматичне Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð², міток Ñ– гілок з іншого Ñховища.
+settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Ðаразі це можна зробити лише в меню «Ðова міграціÑ». Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації звернітьÑÑ Ð´Ð¾:
+settings.mirror_settings.docs.no_new_mirrors=Ваше Ñховище віддзеркалює зміни до іншого Ñховища або з нього. Будь лаÑка, майте на увазі, що наразі ви не можете Ñтворювати нові дзеркала.
+settings.mirror_settings.docs.can_still_use=Хоча ви не можете змінювати наÑвні дзеркала чи Ñтворювати нові, ви вÑе одно можете викориÑтовувати чинне дзеркало.
+settings.mirror_settings.docs.doc_link_title=Як віддзеркалити Ñховища?
+settings.mirror_settings.docs.doc_link_pull_section=розділ документації "Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð· віддаленого Ñховища".
+settings.mirror_settings.docs.pulling_remote_title=Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð· віддаленого Ñховища
+settings.mirror_settings.mirrored_repository=Віддзеркалене Ñховища
settings.mirror_settings.direction=ÐапрÑмок
settings.mirror_settings.direction.pull=Pull
settings.mirror_settings.direction.push=Push
settings.mirror_settings.last_update=ОÑтаннє оновленнÑ
-settings.mirror_settings.push_mirror.none=Ðе налаштовано дзеркало push
-settings.mirror_settings.push_mirror.remote_url=URL віддаленого Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð°Ñ€Ñ–Ñ git
-settings.mirror_settings.push_mirror.add=Додати Push дзеркало
+settings.mirror_settings.push_mirror.remote_url=URL віддаленого Ñховища Git
+settings.mirror_settings.push_mirror.edit_sync_time=Редагувати інтервал Ñинхронізації дзеркал
settings.sync_mirror=Синхронізувати зараз
settings.site=Веб-Ñайт
settings.update_settings=Оновити налаштуваннÑ
-settings.branches.update_default_branch=Оновити гілку за замовчуваннÑм
-settings.advanced_settings=Додаткові налаштуваннÑ
-settings.wiki_desc=Увімкнути репозиторії Вікі
-settings.use_internal_wiki=ВикориÑтовувати вбудовані Вікі
-settings.use_external_wiki=ВикориÑтовувати зовнішні Вікі
+settings.update_mirror_settings=Оновити параметри дзеркала
+settings.branches.switch_default_branch=Змінити типову гілку
+settings.branches.update_default_branch=Оновити типову гілку
+settings.branches.add_new_rule=Додати нове правило
+settings.advanced_settings=Розширені налаштуваннÑ
+settings.wiki_desc=Увімкнути Вікі Ñховища
+settings.use_internal_wiki=ВикориÑтовувати вбудовану Вікі
+settings.default_wiki_branch_name=Ðазва типової гілки Вікі
+settings.failed_to_change_default_wiki_branch=Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ Ñтандартну гілку вікі.
+settings.use_external_wiki=ВикориÑтовувати зовнішню Вікі
settings.external_wiki_url=URL зовнішньої вікі
-settings.external_wiki_url_error=Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ URL-адреÑа wiki не Ñ” допуÑтимою URL-адреÑою.
-settings.external_wiki_url_desc=Відвідувачі будуть перенаправлені на URL-адреÑу, коли вони клацають по вкладці.
+settings.external_wiki_url_error=Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ URL-адреÑа Вікі Ñ” недійÑною.
+settings.external_wiki_url_desc=Відвідувачі перенаправлÑютьÑÑ Ð½Ð° URL-адреÑу зовнішньої вікі при натиÑканні вкладки.
settings.issues_desc=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡ в репозиторію
settings.use_internal_issue_tracker=ВикориÑтовувати вбудовану ÑиÑтему відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡
settings.use_external_issue_tracker=ВикориÑтовувати зовнішню ÑиÑтему обліку задач
settings.external_tracker_url=URL зовнішньої ÑиÑтеми відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡
-settings.external_tracker_url_error=URL зовнішнього баг-трекера не Ñ” допуÑтимою URL-адреÑою.
-settings.external_tracker_url_desc=Відвідувачі перенаправлÑютьÑÑ Ð½Ð° зовнішню URL-адреÑу, коли натиÑкають вкладку 'Задачі'.
+settings.external_tracker_url_error=URL-адреÑа зовнішнього трекера задач Ñ” недійÑною.
+settings.external_tracker_url_desc=Відвідувачі перенаправлÑютьÑÑ Ð½Ð° URL-адреÑу зовнішнього трекера задач при натиÑканні на вкладку.
settings.tracker_url_format=Формат URL зовнішнього трекера задач
-settings.tracker_url_format_error=Ðеправильний формат URL-адреÑи зовнішнього баг-трекера.
-settings.tracker_issue_style=Формат номеру Ð´Ð»Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½ÑŒÐ¾Ñ— ÑиÑтеми обліку задач
+settings.tracker_url_format_error=Формат URL-адреÑи зовнішнього трекера проблем недійÑний.
settings.tracker_issue_style.numeric=Цифровий
-settings.tracker_issue_style.alphanumeric=Буквено-цифровий
+settings.tracker_issue_style.alphanumeric=Ðлфавітно-цифровий
+settings.tracker_issue_style.regexp=РегулÑрний вираз
+settings.tracker_issue_style.regexp_pattern=Шаблон регулÑрного виразу
+settings.tracker_issue_style.regexp_pattern_desc=Першу захоплену групу буде викориÑтано заміÑть <code>{index}</code>.
settings.tracker_url_format_desc=ВикориÑтовуйте шаблони <code>{user}</code>, <code>{repo}</code> та <code>{index}</code> Ð´Ð»Ñ Ñ–Ð¼ÐµÐ½Ñ– кориÑтувача, репозиторію та номеру задічі.
settings.enable_timetracker=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу
-settings.allow_only_contributors_to_track_time=Враховувати тільки учаÑників розробки в підрахунку чаÑу
+settings.allow_only_contributors_to_track_time=Лише відÑтежувати Ñ‡Ð°Ñ Ñпівавторів
settings.pulls_desc=Увімкнути запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð² репозиторій
settings.pulls.ignore_whitespace=Ігнорувати пробіл у конфліктах
-settings.pulls.enable_autodetect_manual_merge=Увімкнути Ð°Ð²Ñ‚Ð¾Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ€ÑƒÑ‡Ð½Ð¾Ð³Ð¾ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ (Примітка: у деÑких оÑобливий випадках можуть виникнуть помилки)
+settings.pulls.enable_autodetect_manual_merge=Увімкнути автоматичне Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ€ÑƒÑ‡Ð½Ð¾Ð³Ð¾ об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ (Примітка: у деÑких оÑобливих випадках можуть виникати помилкові оцінки)
settings.pulls.default_delete_branch_after_merge=ВидалÑти гілку запиту злиттÑ, коли його прийнÑто
+settings.releases_desc=Увімкнути релізи Ñховища
+settings.packages_desc=Увімкнути реєÑтр пакетів Ñховища
+settings.projects_desc=Увімкнути проєкти
+settings.projects_mode_desc=Режим проєктів (Ñкі типи проєктів показувати)
+settings.projects_mode_repo=Тільки проєкти Ñховища
+settings.projects_mode_owner=Тільки проєкти кориÑтувачів або організацій
+settings.projects_mode_all=Ð’ÑÑ– проєкти
+settings.actions_desc=Увімкнути дії Ñховища
settings.admin_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора
-settings.admin_enable_health_check=Включити перевірки працездатноÑті репозиторію (git fsck)
+settings.admin_enable_health_check=Увімкнути перевірку Ñтану Ñховища (git fsck)
+settings.admin_code_indexer=ІндекÑатор коду
+settings.admin_stats_indexer=ІндекÑатор ÑтатиÑтики коду
+settings.admin_indexer_commit_sha=ОÑтанній індекÑований SHA
+settings.admin_indexer_unindexed=Ðе індекÑовано
+settings.reindex_button=Додати до черги на реіндекÑацію
+settings.reindex_requested=Запит на переіндекÑацію
settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити задачу за допомогою коміта, зробленого не в головній гілці
settings.danger_zone=Ðебезпечна зона
-settings.new_owner_has_same_repo=Ðовий влаÑник вже має репозиторій з такою назвою. Будь лаÑка, виберіть інше ім'Ñ.
-settings.convert=Перетворити на звичайний репозиторій
-settings.convert_desc=Ви можете Ñконвертувати це дзеркало у звичайний репозиторій. Це не може бути ÑкаÑовано.
+settings.new_owner_has_same_repo=Ðовий влаÑник вже має Ñховище з такою назвою. Будь лаÑка, виберіть іншу назву.
+settings.convert=Перетворити на звичайне Ñховище
+settings.convert_desc=Ви можете перетворити це дзеркало на звичайне Ñховище. Це неможливо ÑкаÑувати.
settings.convert_notices_1=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚ÑŒ дзеркало у звичайний репозиторій Ñ– не може бути ÑкаÑована.
settings.convert_confirm=Перетворити репозиторій
-settings.convert_succeed=Репозиторій уÑпішно перетворений в звичайний.
+settings.convert_succeed=Дзеркало було перетворено на звичайне Ñховище.
settings.convert_fork=Перетворити на звичайний репозиторій
settings.convert_fork_desc=Ви можете перетворити цей форк на звичайний репозиторій. Цю дію неможливо ÑкаÑувати.
settings.convert_fork_notices_1=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚ÑŒ форк на звичайний репозиторій та не може бути ÑкаÑованою.
@@ -1543,89 +2152,92 @@ settings.transfer=Передати новому влаÑнику
settings.transfer.rejected=ПеренеÑÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ відхилено.
settings.transfer.success=ПеренеÑÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ виконано.
settings.transfer_abort=СкаÑувати перенеÑеннÑ
-settings.transfer_abort_invalid=Ви не можете ÑкаÑувати неіÑнуюче перенеÑÐµÐ½Ð½Ñ Ñховища.
-settings.transfer_desc=Передати репозиторій кориÑтувачеві або організації, де ви маєте права адмініÑтратора.
-settings.transfer_form_title=Введіть ім'Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ñк підтвердженнÑ:
-settings.transfer_in_progress=Ð’ даний Ñ‡Ð°Ñ Ð²Ñ–Ð´Ð±ÑƒÐ²Ð°Ñ”Ñ‚ÑŒÑÑ Ð¿ÐµÑ€ÐµÐ½ÐµÑеннÑ. Будь лаÑка, ÑкаÑуйте його, Ñкщо ви бажаєте перенеÑти цей репозиторій іншому кориÑтувачу.
-settings.transfer_notices_1=- Ви втратите доÑтуп до репозиторіÑ, Ñкщо ви переведете його окремому кориÑтувачеві.
-settings.transfer_notices_2=- Ви збережете доÑтуп, Ñкщо новим влаÑником Ñтане організаціÑ, влаÑником Ñкої ви Ñ”.
-settings.transfer_notices_3=- Якщо репозиторій Ñ” приватним Ñ– передаєтьÑÑ Ð¾ÐºÑ€ÐµÐ¼Ð¾Ð¼Ñƒ кориÑтувачеві, Ñ†Ñ Ð´Ñ–Ñ Ð³Ð°Ñ€Ð°Ð½Ñ‚ÑƒÑ”, що кориÑтувач має хоча б дозвіл на Ñ‡Ð¸Ñ‚Ð°Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð°Ñ€Ñ–ÑŽ (Ñ– при необхідноÑті змінює права дозволів).
+settings.transfer_abort_invalid=Ви не можете ÑкаÑувати перенеÑÐµÐ½Ð½Ñ Ð½ÐµÑ–Ñнуючого Ñховища.
+settings.transfer_abort_success=ПеренеÑÐµÐ½Ð½Ñ Ñховища до %s уÑпішно ÑкаÑовано.
+settings.transfer_desc=Передати це Ñховище кориÑтувачеві або організації, Ð´Ð»Ñ Ñкої ви маєте права адмініÑтратора.
+settings.transfer_form_title=Введіть назву Ñховища Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ:
+settings.transfer_in_progress=Ðаразі триває передача. Будь лаÑка, ÑкаÑуйте його, Ñкщо ви хочете передати це Ñховище іншому кориÑтувачеві.
+settings.transfer_notices_1=- Ви втратите доÑтуп до Ñховища, Ñкщо передаÑте його окремому кориÑтувачеві.
+settings.transfer_notices_2=- Ви збережете доÑтуп до Ñховища, Ñкщо передаÑте його організації, Ñкою ви (Ñпів)володієте.
+settings.transfer_notices_3=- Якщо Ñховище Ñ” приватним Ñ– передаєтьÑÑ Ð¾ÐºÑ€ÐµÐ¼Ð¾Ð¼Ñƒ кориÑтувачеві, Ñ†Ñ Ð´Ñ–Ñ Ð³Ð°Ñ€Ð°Ð½Ñ‚ÑƒÑ”, що кориÑтувач має принаймні права на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ (Ñ– змінює ці права, Ñкщо необхідно).
settings.transfer_owner=Ðовий влаÑник
settings.transfer_perform=ЗдіÑнити перенеÑеннÑ
-settings.transfer_started=`Цей репозиторій чекає Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÐµÐ½ÐµÑÐµÐ½Ð½Ñ Ð²Ñ–Ð´ "%s"`
-settings.transfer_succeed=Репозиторій був перенеÑений.
+settings.transfer_started=Це Ñховище було позначено Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ñ– та очікує на Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ «%s»
+settings.transfer_succeed=Сховище перенеÑено.
settings.signing_settings=Параметри перевірки підпиÑу
-settings.trust_model=Модель довіри Ð´Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñу
-settings.trust_model.default=Модель довіри за замовчуваннÑм
-settings.trust_model.default.desc=ВикориÑтовувати модель довіри репозиторію за замовчуваннÑм Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñайту.
+settings.trust_model=Модель довіри до підпиÑу
+settings.trust_model.default=Типова модель довіри
+settings.trust_model.default.desc=ВикориÑтовувати типову модель довіри до Ñховища Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñайту.
settings.trust_model.collaborator=Співавтор
settings.trust_model.collaborator.long=Співавтор: підпиÑи довіри від Ñпівавторів
-settings.trust_model.collaborator.desc=ДопуÑтимі підпиÑи Ñпівавторів цього репозиторію буде позначано Ñк "довірені" - (Ñкщо вони відповідають комітеру чи ні). Ð’ іншому випадку дійÑні підпиÑи будуть позначені Ñк «ненадійні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñпівпадає з комітером Ñ– «невідповідні», Ñкщо ні.
-settings.trust_model.committer=Коммітер
-settings.trust_model.committer.long=Коммітер: ДовірÑти підпиÑам Ñкі відповідають комітерам (Так Ñк Ñ– на GitHub, Ñ– змуÑить підпиÑати коміти Gitea в ÑкоÑті коммітера)
-settings.trust_model.collaboratorcommitter=Співавтор+Коммітер
-settings.trust_model.collaboratorcommitter.long=Співавтор+Коммітер: ДовірÑти підпиÑам від Ñпівавторів, Ñкі відповідають комітеру
-settings.trust_model.collaboratorcommitter.desc=ДопуÑтимі підпиÑи Ñпівавторів цього репозиторію будуть позначатиÑÑ Ñк "довірені", Ñкщо вони відповідають комітеру. Ð’ іншому випадку дійÑні підпиÑи будуть позначені Ñк «ненадійні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñпівпадає з комітером Ñ– Ñк «невідповіді» в іншому випадку. Це змуÑить Gitea бути відміченим Ñк комітер піÑÐ»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÐ°Ð½Ð½Ñ Ñ„Ð°ÐºÑ‚Ð¸Ñ‡Ð½Ð¸Ð¼ комітером, позначеним Co-Authored-By: Ñ– Co-Committed-By: прикріпленим до комміту. Типовий ключ Gitea повинен відповідати кориÑтувачу в базі даних.
-settings.wiki_delete=Видалити вікі-дані
-settings.wiki_delete_desc=Будьте уважні! Як тільки ви видалите Вікі - шлÑху назад не буде.
-settings.wiki_delete_notices_1=- Це назавжди знищить Ñ– відключить wiki Ð´Ð»Ñ %s.
-settings.confirm_wiki_delete=Видалити Вікі-дані
-settings.wiki_deletion_success=Дані wiki були видалені.
+settings.trust_model.collaborator.desc=ДійÑні підпиÑи Ñпівавторів цього Ñховища будуть позначені Ñк «довірені» - (незалежно від того, чи збігаютьÑÑ Ð²Ð¾Ð½Ð¸ з підпиÑом комітера чи ні). Ð’ іншому випадку дійÑні підпиÑи будуть позначені Ñк «недійÑні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð±Ñ–Ð³Ð°Ñ”Ñ‚ÑŒÑÑ Ð· комітером Ñ– «невідповідні», Ñкщо ні.
+settings.trust_model.committer=Комітер
+settings.trust_model.committer.long=Комітер: ДовірÑти підпиÑам, Ñкі відповідають комітерам (це відповідає GitHub Ñ– змуÑить підпиÑані Gitea коміти мати Gitea в ÑкоÑті комітера)
+settings.trust_model.collaboratorcommitter=Співавтор+Комітер
+settings.trust_model.collaboratorcommitter.long=Співавтор+Комітер: ДовірÑти підпиÑам від Ñпівавторів, Ñкі відповідають комітеру
+settings.trust_model.collaboratorcommitter.desc=ДійÑні підпиÑи Ñпівавторів цього Ñховища будуть позначені Ñк «довірені», Ñкщо вони збігаютьÑÑ Ð· комітером. Ð’ іншому випадку, дійÑні підпиÑи будуть позначені Ñк «недійÑні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð±Ñ–Ð³Ð°Ñ”Ñ‚ÑŒÑÑ Ð· комітером, Ñ– «невідповідні» у протилежному випадку. Це призведе до того, що Gitea буде позначено комітером у підпиÑаних комітах, а Ñправжній комітер буде позначений Ñк Co-Author-By: та Co-Committed-By: у трейлері коміта. Типовий ключ Gitea має відповідати кориÑтувачеві у базі даних.
+settings.wiki_delete=Видалити дані Вікі
+settings.wiki_delete_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… Вікі Ñховища Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване.
+settings.wiki_delete_notices_1=- Це назавжди видалить Ñ– вимкне вікі Ñховища Ð´Ð»Ñ %s.
+settings.confirm_wiki_delete=Видалити дані Вікі
+settings.wiki_deletion_success=Дані Вікі видалено.
settings.delete=Видалити цей репозиторій
-settings.delete_desc=Будьте уважні! Як тільки ви видалите репозиторій - шлÑху назад не буде.
-settings.delete_notices_1=- Цю операцію <strong>ÐЕ МОЖÐÐ</strong> відмінити.
-settings.delete_notices_2=- Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñтаточно видалить <strong>%s</strong> репозиторій, включаючи код, задачі, коментарі, вікі та Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñпівавторів.
+settings.delete_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñховища Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване.
+settings.delete_notices_1=- Цю операцію <strong>ÐЕМОЖЛИВО</strong> ÑкаÑувати.
+settings.delete_notices_2=- Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð·Ð°Ð²Ð¶Ð´Ð¸ видалить Ñховище <strong>%s</strong>, включно з кодом, проблемами, коментарÑми, даними вікі та налаштуваннÑми Ñпівавторів.
settings.delete_notices_fork_1=- Ð’ÑÑ– форки Ñтануть незалежними репозиторіÑми піÑÐ»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ.
settings.deletion_success=Репозиторій уÑпішно видалено.
-settings.update_settings_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ було оновлено.
+settings.update_settings_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñховища оновлено.
+settings.update_settings_no_unit=Сховище повинно дозволÑти хоча б ÑкуÑÑŒ взаємодію.
settings.confirm_delete=Видалити репозиторій
settings.add_collaborator=Додати Ñпівавтора
settings.add_collaborator_success=Додано Ñпівавтора.
-settings.add_collaborator_inactive_user=Ðе можливо додати неактивного кориÑтувача ÑкоÑті Ñпівавтора.
+settings.add_collaborator_inactive_user=Ðеможливо додати неактивного кориÑтувача Ñк Ñпівавтора.
+settings.add_collaborator_owner=Ðеможливо додати влаÑника Ñк Ñпівавтора.
settings.add_collaborator_duplicate=Співавтора уже додано до цього репозиторію.
settings.delete_collaborator=Видалити
settings.collaborator_deletion=Видалити Ñпівавтора
-settings.collaborator_deletion_desc=Цей кориÑтувач більше не матиме доÑтупу Ð´Ð»Ñ Ñпільної роботи в цьому репозиторії піÑÐ»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ. Ви хочете продовжити?
-settings.remove_collaborator_success=Співавтор видалений.
+settings.collaborator_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñпівавтора призведе до Ð²Ñ–Ð´ÐºÐ»Ð¸ÐºÐ°Ð½Ð½Ñ Ð¹Ð¾Ð³Ð¾ доÑтупу до цього Ñховища. Продовжити?
+settings.remove_collaborator_success=Співавтора видалено.
settings.org_not_allowed_to_be_collaborator=Організації не можуть бути додані Ñк Ñпівавтори.
settings.change_team_access_not_allowed=Зміна доÑтупу команди до репозитарію обмежена влаÑником організації
-settings.team_not_in_organization=Команда та репозитарій мають привÑзки до різних організацій
+settings.team_not_in_organization=Команда не належить до тієї ж організації, що й Ñховище
settings.teams=Команди
-settings.add_team=Додати Команду
-settings.add_team_duplicate=Команда вже має привÑзку до репозитарію
-settings.add_team_success=Команда отримала доÑтуп до репозиторію.
-settings.change_team_permission_tip=Дозволи команди вÑтановлюютьÑÑ Ð½Ð° Ñторінці налаштувань команди та не можуть бути заданими Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ з репозиторіїв окремо
+settings.add_team=Додати команду
+settings.add_team_duplicate=Команда вже має Ñховище
+settings.add_team_success=Команда тепер має доÑтуп до Ñховища.
+settings.change_team_permission_tip=Дозвіл команди вÑтановлюєтьÑÑ Ð½Ð° Ñторінці налаштувань команди Ñ– не може бути змінений Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ Ñховища
settings.delete_team_tip=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° має доÑтуп до вÑÑ–Ñ… репозиторіїв та не може бути видалена
-settings.remove_team_success=ДоÑтуп команди до репозиторію видалений.
+settings.remove_team_success=ДоÑтуп команди до Ñховища видалено.
settings.add_webhook=Додати веб-хук
-settings.add_webhook.invalid_channel_name=Ðазва каналу Webhook не може бути порожньою Ñ– не може міÑтити лише Ñимвол #.
-settings.hooks_desc=Веб-хуки автоматично робить HTTP POST-запити на Ñервер, коли відбуваютьÑÑ Ð¿ÐµÐ²Ð½Ñ– події Gitea. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в <a target="_blank" rel="noopener" href="%s"> інÑтрукції по викориÑтанню web-хуків </a>.
+settings.add_webhook.invalid_channel_name=Ðазва каналу веб-хука не може бути порожньою Ñ– міÑтити лише Ñимвол #.
+settings.hooks_desc=Веб-хуки автоматично роблÑть HTTP POST запити до Ñервера, коли відбуваютьÑÑ Ð¿ÐµÐ²Ð½Ñ– події Gitea. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в <a target="_blank" rel="noopener noreferrer" href="%s">інÑтрукції по викориÑтанню веб-хуків</a>.
settings.webhook_deletion=Видалити веб-хук
-settings.webhook_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ веб-хука призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ñієї пов'Ñзаної з ним інформації, включаючи Ñ–Ñторію. Бажаєте продовжити?
-settings.webhook_deletion_success=Webhook видалено.
+settings.webhook_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÐµÐ±-хука видалÑÑ” його Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð° Ñ–Ñторію доÑтавки. Продовжити?
+settings.webhook_deletion_success=Веб-хук видалено.
settings.webhook.test_delivery=Перевірити доÑтавку
-settings.webhook.test_delivery_desc=Перевірте цей веб-хук з підробленою подією.
+settings.webhook.test_delivery_desc=Перевірте цей веб-хук з фальшивою подією.
settings.webhook.request=Запит
settings.webhook.response=Відповідь
settings.webhook.headers=Заголовки
settings.webhook.payload=ЗміÑÑ‚
settings.webhook.body=Тіло
-settings.githook_edit_desc=Якщо хук неактивний, буде предÑтавлено зразок зміÑту. Порожнє Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñƒ цьому полі призведе до Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ñ…ÑƒÐºÑƒ.
-settings.githook_name=Ім'Ñ Ñ…ÑƒÐºÑƒ
+settings.webhook.replay.description=Повторити цей веб-хук.
+settings.githook_edit_desc=Якщо хук неактивний, буде показано зразок вміÑту. Якщо залишити вміÑÑ‚ порожнім, хук буде вимкнено.
+settings.githook_name=Ðазва хуку
settings.githook_content=ЗміÑÑ‚ хука
settings.update_githook=Оновити хук
-settings.add_webhook_desc=Gitea буде відправлÑти <code>POST</code> запити на вказану URL адреÑу, з інформацією про події, що відбуваютьÑÑ. Подробиці на Ñторінці <a target="_blank" rel="noopener" href="%s"> інÑтрукції по викориÑтанню web-хуків </a>.
+settings.add_webhook_desc=Gitea надішле запити <code>POST</code> із зазначеним типом зміÑту на цільову URL-адреÑу. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в <a target="_blank" rel="noopener noreferrer" href="%s">інÑтрукції по викориÑтанню веб-хуків</a>.
settings.payload_url=Цільова URL-адреÑа
settings.http_method=Метод HTTP
settings.content_type=Тип зміÑту
settings.secret=Секрет
settings.slack_username=Ім'Ñ ÐºÑ€Ð¸Ñтувача
-settings.slack_icon_url=URL іконки
+settings.slack_icon_url=URL піктограми
settings.slack_color=Колір
settings.discord_username=Ім'Ñ ÐºÑ€Ð¸Ñтувача
-settings.discord_icon_url=URL іконки
+settings.discord_icon_url=URL піктограми
settings.event_desc=Тригер:
-settings.event_push_only=Push події
settings.event_send_everything=Ð’ÑÑ– події
settings.event_choose=ВлаÑні події…
settings.event_header_repository=Події репозиторію
@@ -1634,41 +2246,44 @@ settings.event_create_desc=Гілку або тег Ñтворено.
settings.event_delete=Видалити
settings.event_delete_desc=Гілку або мітку було видалено.
settings.event_fork=Форк
-settings.event_fork_desc=Репозиторій було форкнуто.
settings.event_wiki=Вікі
+settings.event_statuses=СтатуÑи
+settings.event_statuses_desc=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ оновлено з API.
settings.event_release=Реліз
-settings.event_release_desc=Реліз опублікований, оновлений або видалений з репозиторіÑ.
+settings.event_release_desc=Реліз опубліковано, оновлено або видалено зі Ñховища.
settings.event_push=Push
-settings.event_push_desc=Git push до репозиторію.
settings.event_repository=Репозиторій
settings.event_repository_desc=Репозиторій Ñтворений або видалено.
settings.event_header_issue=Події задачі
settings.event_issues=Задачі
-settings.event_issues_desc=Задача відкрита, закрита, повторно відкрита або відредагована.
-settings.event_issue_assign=Задача прив'Ñзана
+settings.event_issues_desc=Задачу відкрито, закрито, повторно відкрито, відредаговано або видалено.
+settings.event_issue_assign=Задачу призначено
settings.event_issue_assign_desc=Задачу призначено або ÑкаÑовано.
-settings.event_issue_label=Задача з міткою
settings.event_issue_label_desc=Мітки задачі оновлено або видалено.
-settings.event_issue_milestone=Задача з етапом
-settings.event_issue_milestone_desc=Задача призначена на етап або видалена з етапу.
settings.event_issue_comment=Коментар задачі
settings.event_issue_comment_desc=Коментар задачі Ñтворено, видалено чи відредаговано.
settings.event_header_pull_request=Події запиту злиттÑ
-settings.event_pull_request=Запити до злиттÑ
-settings.event_pull_request_desc=Запит до Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¾, закрито, перевідкрито або відредаговано.
+settings.event_pull_request=Запити на злиттÑ
+settings.event_pull_request_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¾, закрито, повторно відкрито, відредаговано або видалено.
settings.event_pull_request_assign=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾
-settings.event_pull_request_assign_desc=Запит про Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾ або ÑкаÑовано.
+settings.event_pull_request_assign_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾ або ÑкаÑовано.
settings.event_pull_request_label=Запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð° мітка
settings.event_pull_request_label_desc=Мітка запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð° або очищена.
-settings.event_pull_request_milestone=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ð¹ на етап
-settings.event_pull_request_milestone_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ð¹ на етап або видалений з етапу.
-settings.event_pull_request_comment=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¾ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð¾Ð²Ð°Ð½Ð¸Ð¹
+settings.event_pull_request_milestone=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð¾Ð´Ð°Ð½Ð¾ до етапу
+settings.event_pull_request_milestone_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð¾Ð´Ð°Ð½Ð¾ до етапу або видалено з етапу.
+settings.event_pull_request_comment=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¾ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð¾Ð²Ð°Ð½Ð¾
settings.event_pull_request_comment_desc=Коментар запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñтворено, відредаговано чи видалено.
settings.event_pull_request_review=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ€ÐµÑ†ÐµÐ½Ð·Ð¾Ð²Ð°Ð½Ð¾
-settings.event_pull_request_review_desc=Коментар запиту до Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð¸Ð¹, відхилений або рецензований.
+settings.event_pull_request_review_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð¾, відхилено або прокоментовано.
settings.event_pull_request_sync=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ ÑинхронізуєтьÑÑ
settings.event_pull_request_sync_desc=Запит до Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñинхронізовано.
+settings.event_header_workflow=Події робочого процеÑу
+settings.event_workflow_run=Запущений робочий процеÑ
+settings.event_workflow_run_desc=Запущений робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Gitea в черзі, в очікуванні, в процеÑÑ– Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ завершений.
+settings.event_workflow_job=Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ñ€Ð¾Ð±Ð¾Ñ‡Ð¾Ð³Ð¾ процеÑу
+settings.event_package=Пакет
settings.branch_filter=Фільтр гілок
+settings.authorization_header=Заголовок авторизації
settings.active=Ðктивний
settings.active_helper=Інформацію про викликані події буде надіÑлано за цією веб-хук URL-адреÑою.
settings.add_hook_success=Веб-хук було додано.
@@ -1680,71 +2295,109 @@ settings.hook_type=Тип хука
settings.slack_token=Токен
settings.slack_domain=Домен
settings.slack_channel=Канал
-settings.deploy_keys=Ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ
-settings.add_deploy_key=Додати ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ
-settings.deploy_key_desc=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупні тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ. Це не те ж Ñаме що Ñ– SSH-ключі аккаунта.
+settings.web_hook_name_gitea=Gitea
+settings.web_hook_name_gogs=Gogs
+settings.web_hook_name_slack=Slack
+settings.web_hook_name_discord=Discord
+settings.web_hook_name_dingtalk=DingTalk
+settings.web_hook_name_telegram=Telegram
+settings.web_hook_name_matrix=Matrix
+settings.web_hook_name_msteams=Microsoft Teams
+settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
+settings.web_hook_name_feishu=Feishu
+settings.web_hook_name_larksuite=Lark Suite
+settings.web_hook_name_wechatwork=WeCom (Wechat Work)
+settings.web_hook_name_packagist=Packagist
+settings.packagist_username=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Packagist
+settings.packagist_api_token=Токен API
+settings.deploy_keys=Ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ
+settings.add_deploy_key=Додати ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ
+settings.deploy_key_desc=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð¼Ð°ÑŽÑ‚ÑŒ доÑтуп до Ñховища лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ.
settings.is_writable=Увімкнути доÑтуп Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу
-settings.is_writable_info=Чи може цей ключ бути викориÑтаний Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ <strong>push</strong> в репозиторій? Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð·Ð°Ð²Ð¶Ð´Ð¸ мають доÑтуп на pull.
-settings.no_deploy_keys=Ви не додавали ключі розгортаннÑ.
+settings.no_deploy_keys=Ключів Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñ‰Ðµ немає.
settings.title=Заголовок
settings.deploy_key_content=ЗміÑÑ‚
-settings.key_been_used=ЗміÑÑ‚ ключа Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ викориÑтовуєтьÑÑ.
-settings.key_name_used=Ключ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð· таким заголовком вже Ñ–Ñнує.
-settings.deploy_key_deletion=Видалити ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ
-settings.deploy_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° розгортки унеможливить доÑтуп до Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð· його допомогою. Ви впевнені?
+settings.key_been_used=Ключ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð· ідентичним вміÑтом вже викориÑтовуєтьÑÑ.
+settings.key_name_used=Ключ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð· такою ж назвою вже Ñ–Ñнує.
+settings.deploy_key_deletion=Видалити ключ розгортаннÑ
+settings.deploy_key_deletion_desc=Ð’Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð·Ð²ÐµÐ´Ðµ до Ð²Ñ–Ð´ÐºÐ»Ð¸ÐºÐ°Ð½Ð½Ñ Ð¹Ð¾Ð³Ð¾ доÑтупу до цього Ñховища. Продовжити?
settings.deploy_key_deletion_success=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ видалено.
settings.branches=Гілки
settings.protected_branch=ЗахиÑÑ‚ гілки
-settings.protected_branch_can_push=Дозволити push?
-settings.protected_branch_can_push_yes=Ви можете виконувати push
-settings.protect_this_branch=ЗахиÑтити цю гілку
-settings.protect_this_branch_desc=Запобігає видаленню гілки та обмежує Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð² ній push та злиттÑ.
+settings.protected_branch.save_rule=Зберегти правило
+settings.protected_branch.delete_rule=Видалити правило
+settings.branch_protection=ЗахиÑÑ‚ гілки '<b>%s</b>'
+settings.protect_this_branch=Увімкнути захиÑÑ‚ гілок
settings.protect_disable_push=Заборонити Push
settings.protect_disable_push_desc=Ð”Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки буде заборонено Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ push.
settings.protect_enable_push=Дозволити Push
settings.protect_enable_push_desc=Будь-хто із правом запиÑу зможе виконувати push Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки (за виключеннÑм force push).
+settings.protect_enable_merge=Увімкнути об’єднаннÑ
settings.protect_check_status_contexts=Увімкнути перевірку Ñтану
+settings.protect_status_check_patterns=Шаблони перевірки Ñтану:
+settings.protect_status_check_patterns_desc=Введіть шаблони, щоб вказати, Ñкі перевірки Ñтану повинні пройти гілки, перш ніж Ñ—Ñ… буде об'єднано у гілку, що відповідає цьому правилу. Кожен Ñ€Ñдок визначає шаблон. Шаблони не можуть бути порожніми.
settings.protect_check_status_contexts_list=Перевірки ÑтатуÑу знайдено Ð´Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð°Ñ€Ñ–ÑŽ за минулий тиждень
+settings.protect_status_check_matched=Збіг
+settings.protect_invalid_status_check_pattern=ÐедійÑний шаблон перевірки Ñтану: "%s".
+settings.protect_no_valid_status_check_patterns=Ðемає дійÑних шаблонів перевірки Ñтану.
settings.protect_required_approvals=Ðеобхідно ÑхваленнÑ:
settings.dismiss_stale_approvals=Відхилити заÑтарілі погодженнÑ
settings.dismiss_stale_approvals_desc=Коли нові коміти що змінюють вміÑÑ‚ пулл-запиту відправлÑютьÑÑ Ð² гілку, Ñтарі Ð¿Ð¾Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ´ÑƒÑ‚ÑŒ відхилені.
+settings.ignore_stale_approvals=Ігнорувати заÑтарілі затвердженнÑ
settings.require_signed_commits=Потрібно підпиÑані коміти
settings.require_signed_commits_desc=ВідхилÑти push до цієї гілки, Ñкщо вони не підпиÑані або Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ перевірити.
+settings.protect_branch_name_pattern=Шаблон назви захищених гілок
+settings.protect_branch_name_pattern_desc=Шаблони назв захищених гілок. ПереглÑньте <a href="https://github.com/gobwas/glob">документацію</a> щодо ÑинтакÑиÑу шаблону. Приклади: main, release/**
+settings.protect_patterns=Шаблони
+settings.protect_protected_file_patterns=Шаблони захищених файлів (розділені крапками з комою ';'):
+settings.protect_unprotected_file_patterns=Шаблони незахищених файлів (розділені крапкою з комою ';'):
settings.add_protected_branch=Увімкнути захиÑÑ‚
settings.delete_protected_branch=Вимкнути захиÑÑ‚
+settings.update_protect_branch_success=Оновлено захиÑÑ‚ гілок Ð´Ð»Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»Ð° "%s".
+settings.remove_protected_branch_success=Видалено захиÑÑ‚ гілок Ð´Ð»Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»Ð° "%s".
+settings.remove_protected_branch_failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ правило захиÑту гілки "%s.
settings.protected_branch_deletion_desc=Будь-Ñкий кориÑтувач з дозволами на Ð·Ð°Ð¿Ð¸Ñ Ð·Ð¼Ð¾Ð¶Ðµ виконувати push в цю гілку. Ви впевнені?
-settings.block_rejected_reviews=Блокувати Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸ відкидаючих рецензіÑÑ…
-settings.block_rejected_reviews_desc=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ´Ðµ недоÑтупним, Ñкщо Ñ” запит змін від офіційних рецензентів, навіть за наÑвноÑті доÑтатньої кількоÑті Ñхвалень.
-settings.block_on_official_review_requests=Блокувати Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸ запиті на офіціальний розглÑд
+settings.block_rejected_reviews=Блокувати об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñкщо рецензії відхилено
+settings.block_rejected_reviews_desc=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ неможливим, Ñкщо офіційні рецензенти вимагають внеÑÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½, навіть Ñкщо Ñ” доÑÑ‚Ð°Ñ‚Ð½Ñ ÐºÑ–Ð»ÑŒÐºÑ–Ñть Ñхвалень.
+settings.block_on_official_review_requests=Блокувати об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð·Ð° офіційними запитами на рецензуваннÑ
settings.block_on_official_review_requests_desc=ÐžÐ±â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ðµ, коли воно має офіційні запити на розглÑд, навіть Ñкщо доÑтатньо Ñхвалень.
-settings.block_outdated_branch=Блокувати злиттÑ, Ñкщо запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ñтарів
-settings.block_outdated_branch_desc=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ´Ðµ неможливим, коли головна гілка позаду оÑновної.
-settings.default_branch_desc=Головна гілка Ñ” 'базовою' Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ репозиторіÑ, на Ñку за замовчуваннÑм ÑпрÑмовані вÑÑ– запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– Ñка Ñ” обличчÑм вашого репозиторіÑ. Перше, що побачить відвідувач - це зміÑÑ‚ головної гілки. Виберіть Ñ—Ñ— з уже Ñ–Ñнуючих:
+settings.block_outdated_branch=Блокувати об'єднаннÑ, Ñкщо запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ñтарів
+settings.block_outdated_branch_desc=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ неможливим, Ñкщо головна гілка позаду оÑновної.
+settings.block_admin_merge_override=ÐдмініÑтратори повинні дотримуватиÑÑ Ð¿Ñ€Ð°Ð²Ð¸Ð» захиÑту гілки
+settings.block_admin_merge_override_desc=ÐдмініÑтратори повинні дотримуватиÑÑ Ð¿Ñ€Ð°Ð²Ð¸Ð» захиÑту гілки Ñ– не можуть Ñ—Ñ… обійти.
+settings.default_branch_desc=Обрати типову гілку Ñховища Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– комітів:
+settings.merge_style_desc=Стилі об'єднаннÑ
+settings.default_merge_style_desc=Типовий Ñтиль об'єднаннÑ
settings.choose_branch=Оберіть гілку…
settings.no_protected_branch=Ðемає захищених гілок.
settings.edit_protected_branch=Редагувати
+settings.protected_branch_required_rule_name=Ðеобхідна назва правила
+settings.protected_branch_duplicate_rule_name=Ðазва правила вже Ñ–Ñнує
settings.protected_branch_required_approvals_min=ЧиÑло необхідних Ñхвалень не може бути від'ємним.
settings.tags=Мітки
settings.tags.protection=ЗахиÑÑ‚ мітки
-settings.tags.protection.pattern=Шаблон тега
+settings.tags.protection.pattern=Шаблон мітки
settings.tags.protection.allowed=Дозволено
settings.tags.protection.allowed.users=Дозволені кориÑтувачі
settings.tags.protection.allowed.teams=Дозволені команди
settings.tags.protection.allowed.noone=Ðіхто
-settings.tags.protection.create=ЗахиÑтна мітка
+settings.tags.protection.create=ЗахиÑтити мітку
settings.tags.protection.none=Там не немає захищених міток.
+settings.tags.protection.pattern.description=Ви можете викориÑтовувати єдине ім’Ñ, глобальний шаблон або регулÑрний вираз, щоб відповідати кільком міткам. Докладніше читайте в <a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/protected-tags">поÑібнику із захищених міток</a>.
settings.bot_token=Токен Ð´Ð»Ñ Ð±Ð¾Ñ‚Ð°
-settings.chat_id=Чат ID
+settings.chat_id=ID чату
settings.matrix.homeserver_url=URL домашньої Ñторінки
-settings.matrix.room_id=Ðомер кімнати
+settings.matrix.room_id=ID кімнати
settings.matrix.message_type=Тип повідомленнÑ
-settings.archive.button=Ðрхівний репозиторій
+settings.visibility.public.button=Зробити публічним
settings.archive.header=Відправити репозиторій в архів
-settings.archive.success=Репозиторію уÑпішно приÑвоєно ÑÑ‚Ð°Ñ‚ÑƒÑ Ð°Ñ€Ñ…Ñ–Ð²Ð½Ð¾Ð³Ð¾.
-settings.archive.error=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при Ñпробі архівувати репозиторій. Докладнішу інформацію див. у журналі.
-settings.archive.error_ismirror=Ðеможливо архівувати дзеркальний репозиротрій.
+settings.archive.success=Сховище уÑпішно заархівовано.
+settings.archive.error=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при Ñпробі архівувати репозиторій. Докладнішу інформацію дивітьÑÑ Ñƒ журналі.
+settings.archive.error_ismirror=Ðеможливо архівувати дзеркальне Ñховище.
settings.archive.branchsettings_unavailable=Параметри гілки не доÑтупні, Ñкщо репозиторій архівний.
settings.archive.tagsettings_unavailable=Параметри міток недоÑтупні, Ñкщо репозиторій архівний.
+settings.unarchive.header=Розархівувати це Ñховище
+settings.unarchive.success=Сховище уÑпішно розархівовано.
settings.update_avatar_success=Ðватар репозиторію оновлений.
settings.lfs=LFS
settings.lfs_filelist=Файли LFS, Ñкі зберігаютьÑÑ Ð² цьому репозиторії
@@ -1756,9 +2409,9 @@ settings.lfs_delete=Видалити файл LFS з OID %s
settings.lfs_delete_warning=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ LFS може Ñпричинити помилки "Об'єкт не Ñ–Ñнує" під Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸. Ви впевнені?
settings.lfs_findpointerfiles=Знайти файли-поÑиланнÑ
settings.lfs_locks=БлокуваннÑ
-settings.lfs_invalid_locking_path=ÐеприпуÑтимий шлÑÑ…: %s
+settings.lfs_invalid_locking_path=ÐедійÑний шлÑÑ…: %s
settings.lfs_invalid_lock_directory=Ðе можливо заблокувати каталог: %s
-settings.lfs_lock_already_exists=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ викориÑтовуєтьÑÑ: %s
+settings.lfs_lock_already_exists=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ Ñ–Ñнує: %s
settings.lfs_lock=Блокувати
settings.lfs_lock_path=ШлÑÑ… до файлу Ð´Ð»Ñ Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ...
settings.lfs_locks_no_locks=ВідÑутнє блокуваннÑ
@@ -1798,7 +2451,7 @@ diff.stats_desc_file=%d змін: %d доповнень та %d видалень
diff.bin=BIN
diff.bin_not_shown=Бінарний файл не відображаєтьÑÑ.
diff.view_file=ПереглÑнути файл
-diff.file_before=Перед
+diff.file_before=До
diff.file_after=ПіÑлÑ
diff.file_image_width=Ширина
diff.file_image_height=ВиÑота
@@ -1810,6 +2463,7 @@ diff.show_more=Показати більше
diff.load=Завантажити різницю
diff.generated=згенерований
diff.vendored=Ñторонній
+diff.comment.add_line_comment=Додати проÑтий коментар
diff.comment.placeholder=Залишити коментар
diff.comment.add_single_comment=Додати проÑтий коментар
diff.comment.add_review_comment=Додати коментар
@@ -1817,81 +2471,130 @@ diff.comment.start_review=Розпочати рецензію
diff.comment.reply=Відповідь
diff.review=РецензіÑ
diff.review.header=ÐадіÑлати рецензію
-diff.review.placeholder=Рецензійований коментарій
+diff.review.placeholder=Ð ÐµÑ†ÐµÐ½Ð·Ñ–Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ
diff.review.comment=Коментар
diff.review.approve=Затвердити
diff.review.reject=Запит змін
diff.committed_by=зафікÑовано
diff.protected=Захищений
-diff.image.side_by_side=Пліч-о-пліч
-diff.image.swipe=Свайп
-diff.image.overlay=Оверлей
+diff.image.side_by_side=Поруч
+diff.image.swipe=ПровеÑти пальцем
+diff.image.overlay=ÐаклаÑти
+diff.has_escaped=Цей Ñ€Ñдок міÑтить приховані Ñимволи Юнікоду
+diff.show_file_tree=Показати дерево файлів
+diff.hide_file_tree=Сховати дерево файлів
+diff.submodule_added=Підмодуль %[1]s додано в %[2]s
+diff.submodule_deleted=Підмодуль %[1]s видалено з %[2]s
+diff.submodule_updated=Підмодуль %[1]s оновлено: %[2]s
releases.desc=ВідÑлідковувати верÑÑ–Ñ— проєкту Ñ– завантаженнÑ.
release.releases=Релізи
release.detail=Деталі релізу
-release.tags=Теги
+release.tags=Мітки
release.new_release=Ðовий реліз
release.draft=Чернетка
release.prerelease=Пре-реліз
release.stable=Стабільний
+release.latest=ОÑтанні
release.compare=ПорівнÑти
release.edit=редагувати
release.ahead.commits=<strong>%d</strong> коміт(ів)
release.ahead.target=до %s з моменту цього випуÑку
release.source_code=Код
-release.new_subheader=ÐŸÑƒÐ±Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñ€ÐµÐ»Ñ–Ð·Ñ–Ð² допоможе вам організувати верÑÑ–ÑŽ проєкту.
-release.edit_subheader=ÐŸÑƒÐ±Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñ€ÐµÐ»Ñ–Ð·Ñ–Ð² допоможе вам організувати верÑÑ–ÑŽ проєкту.
-release.tag_name=Ðазва тегу
+release.new_subheader=Релізи впорÑдковують верÑÑ–Ñ— проєкту.
+release.edit_subheader=Релізи впорÑдковують верÑÑ–Ñ— проєкту.
+release.tag_name=Ðазва мітки
release.target=Ціль
-release.tag_helper=Виберіть Ñ–Ñнуючий тег або Ñтворіть новий.
+release.tag_helper=Вибрати Ñ–Ñнуючу мітку або Ñтворити нову.
+release.title=Ðазва релізу
+release.title_empty=Заголовок не може бути порожнім.
+release.message=Опишіть цей реліз
release.prerelease_desc=Позначити Ñк пре-реліз
-release.prerelease_helper=Позначте цей випуÑк непридатним Ð´Ð»Ñ ÐŸÐ ÐžÐ” викориÑтаннÑ.
+release.prerelease_helper=Позначите випуÑк Ñк непридатний Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´ÑƒÐºÑ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ викориÑтаннÑ.
release.cancel=Відмінити
release.publish=Опублікувати реліз
release.save_draft=Зберегти чернетку
release.edit_release=Оновити реліз
release.delete_release=Видалити реліз
-release.delete_tag=Видалити тег
+release.delete_tag=Видалити мітку
release.deletion=Видалити реліз
-release.deletion_success=Реліз, було видалено.
+release.deletion_success=Реліз видалено.
release.deletion_tag_desc=Буде видалено цей тег із репозиторію. ВміÑÑ‚ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ñ‚Ð° Ñ–ÑÑ‚Ð¾Ñ€Ñ–Ñ Ð·Ð°Ð»Ð¸ÑˆÐ°Ñ‚ÑŒÑÑ Ð½ÐµÐ·Ð¼Ñ–Ð½Ð½Ð¸Ð¼Ð¸. Продовжити?
release.deletion_tag_success=Мітка видалена.
-release.tag_name_already_exist=Реліз з цим ім'Ñм мітки вже Ñ–Ñнує.
-release.tag_name_invalid=ÐеприпуÑтиме ім'Ñ Ñ‚ÐµÐ³Ð°.
-release.tag_name_protected=Ім'Ñ Ñ‚ÐµÐ³Ð° захищене.
-release.tag_already_exist=Цей тег вже викориÑтовуєтьÑÑ.
-release.downloads=Завантажити
+release.tag_name_already_exist=Реліз з такою ж міткою вже Ñ–Ñнує.
+release.tag_name_invalid=Ðазва мітки недійÑна.
+release.tag_name_protected=Ðазва мітки захищена.
+release.tag_already_exist=Ðазва мітки вже Ñ–Ñнує.
+release.downloads=ЗавантаженнÑ
release.download_count=ЗавантаженнÑ: %s
-release.add_tag_msg=ВикориÑтовуйте заголовок Ñ– зміÑÑ‚ релізу Ñк Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñк тег повідомленнÑ.
+release.add_tag_msg=ВикориÑтовуйте заголовок Ñ– зміÑÑ‚ релізу Ñк Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚ÐºÐ¸.
release.add_tag=Створити тільки мітку
+release.releases_for=Релізи Ð´Ð»Ñ %s
+release.tags_for=Мітки Ð´Ð»Ñ %s
-branch.name=Ім'Ñ Ð³Ñ–Ð»ÐºÐ¸
+branch.name=Ðазва гілки
+branch.already_exists=Гілка з назвою "%s" вже Ñ–Ñнує.
branch.delete_head=Видалити
+branch.delete=`Видалити гілку "%s"`
branch.delete_html=Видалити гілку
+branch.delete_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð³Ñ–Ð»ÐºÐ¸ Ñ” незворотним. Хоча видалена гілка може продовжувати Ñ–Ñнувати ще деÑкий Ñ‡Ð°Ñ Ð´Ð¾ того, Ñк Ñ—Ñ— буде видалено оÑтаточно, у більшоÑті випадків це ÐЕМОЖЛИВО ÑкаÑувати. Продовжити?
+branch.deletion_success=Гілку "%s" видалено.
+branch.deletion_failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ гілку "%s".
+branch.delete_branch_has_new_commits=Гілку "%s" не можна видалити, оÑкільки піÑÐ»Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ додано нові коміти.
branch.create_branch=Створити гілку %s
+branch.create_from=`з "%s"`
+branch.create_success=Створено гілку "%s".
+branch.branch_name_conflict=Ðазва гілки "%s" конфліктує з уже Ñ–Ñнуючою гілкою "%s".
+branch.tag_collision=Гілка "%s" не може бути Ñтворена, оÑкільки у Ñховищі вже Ñ–Ñнує мітка з такою назвою.
branch.deleted_by=Видалено %s
+branch.restore_success=Гілку "%s" відновлено.
+branch.restore_failed=Ðе вдалоÑÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ гілку "%s".
+branch.protected_deletion_failed=Гілка "%s" захищена. Її неможливо видалити.
+branch.default_deletion_failed=Гілка "%s" Ñтандартна. Її неможливо видалити.
+branch.restore=`Відновити гілку "%s"`
+branch.download=`Завантажити гілку "%s"`
+branch.rename=`Перейменувати гілку "%s"`
branch.included_desc=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° Ñ” чаÑтиною типової гілки
branch.included=Включено
branch.create_new_branch=Створити гілку з гілки:
branch.confirm_create_branch=Створити гілку
+branch.warning_rename_default_branch=Ви перейменовуєте Ñтандартну гілку.
+branch.rename_branch_to=Перейменувати "%s" на:
branch.confirm_rename_branch=Перейменувати гілку
branch.create_branch_operation=Створити гілку
branch.new_branch=Створити нову гілку
+branch.new_branch_from=`Створити нову гілку з "%s"`
branch.renamed=Гілку %s перейменовано на %s.
+branch.rename_default_or_protected_branch_error=Лише адмініÑтратори можуть перейменовувати типові або захищені гілки.
+branch.rename_protected_branch_failed=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° захищена правилами захиÑту на оÑнові глобальних правил.
tag.create_tag=Створити тег %s
+tag.create_tag_operation=Створити мітку
+tag.confirm_create_tag=Створити мітку
+tag.create_tag_from=`Створити нову мітку з "%s"`
+tag.create_success=Мітку "%s" Ñтворено.
-topic.manage_topics=Керувати тематичними мітками
+topic.manage_topics=Керувати темами
topic.done=Готово
+topic.count_prompt=Ви не можете вибрати більше ніж 25 тем
+topic.format_prompt=Теми мають починатиÑÑ Ð· літери або цифри, можуть міÑтити дефіÑи ('-') Ñ– крапки ('.'), мати довжину до 35 Ñимволів. Літери повинні бути малими.
+find_file.go_to_file=Перейти до файлу
+find_file.no_matching=Ðе знайдено відповідного файлу
error.csv.too_large=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він завеликий.
error.csv.unexpected=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він міÑтить неочікуваний Ñимвол в Ñ€Ñдку %d Ñ– Ñтовпці %d.
error.csv.invalid_field_count=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він має неправильну кількіÑть полів у Ñ€Ñдку %d.
[graphs]
+component_loading=Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ %s...
+component_loading_failed=Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ %s
+component_loading_info=Це може зайнÑти трохи чаÑу…
+component_failed_to_load=СталаÑÑŒ непередбачена помилка.
+code_frequency.what=чаÑтота коду
+contributors.what=внеÑки
+recent_commits.what=нові коміти
[org]
org_name_holder=Ðазва організації
@@ -1901,6 +2604,7 @@ create_org=Створити організацію
repo_updated=Оновлено
members=УчаÑники
teams=Команди
+code=Код
lower_members=учаÑники
lower_repositories=репозиторії
create_new_team=Ðова команда
@@ -1910,43 +2614,62 @@ team_name=Ðазва команди
team_desc=ОпиÑ
team_name_helper=Ðазва команди має бути проÑтою та зрозумілою.
team_desc_helper=Опишіть мету або роль команди.
-team_access_desc=ДоÑтуп до репозиторіÑ
+team_access_desc=ДоÑтуп до Ñховища
team_permission_desc=Права доÑтупу
team_unit_desc=Дозволити доÑтуп до розділів репозиторію
team_unit_disabled=(Вимкнено)
+form.name_been_taken=Ðазва організації "%s" вже зайнÑта.
+form.name_reserved=Ðазву організації "%s" зарезервовано.
+form.name_pattern_not_allowed=Шаблон "%s" не допуÑкаєтьÑÑ Ð² назві організації.
form.create_org_not_allowed=Вам не дозволено Ñтворювати організації.
settings=ÐалаштуваннÑ
settings.options=ОрганізаціÑ
settings.full_name=Повне ім'Ñ
+settings.email=Контактна адреÑа електронної пошти
settings.website=Веб-Ñайт
settings.location=РозташуваннÑ
settings.permission=Дозволи
settings.repoadminchangeteam=ÐдмініÑтратор репозитарію може додавати та видалÑти доÑтуп Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´
settings.visibility=ВидиміÑть
-settings.visibility.public=Публічний
-settings.visibility.limited_shortname=Обмежений
-settings.visibility.private=Приватний (Видимий лише членам організації)
+settings.visibility.public=Публічна
+settings.visibility.limited=Обмежений (Видимий лише Ð´Ð»Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ¾Ð²Ð°Ð½Ð¸Ñ… кориÑтувачів)
+settings.visibility.limited_shortname=Обмежена
+settings.visibility.private=Приватна (Видима лише членам організації)
settings.visibility.private_shortname=Приватний
settings.update_settings=Оновити налаштуваннÑ
settings.update_setting_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— оновлені.
-settings.change_orgname_redirect_prompt=Старе ім'Ñ Ð±ÑƒÐ´Ðµ перенаправлено до тих пір, поки воно не буде заброньовано.
+
+settings.rename=Перейменувати організацію
+settings.rename_desc=Зміна назви організації також змінить URL адреÑу вашої організації Ñ– звільнить Ñтару назву.
+settings.rename_success=Організацію %[1]s уÑпішно перейменована на %[2].
+settings.rename_no_change=Ðазва організації не змінилаÑÑ.
+settings.rename_new_org_name=Ðазва нової організації
+settings.rename_failed=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ¹Ð¼ÐµÐ½ÑƒÐ²Ð°Ñ‚Ð¸ організацію через внутрішню помилку
+settings.rename_notices_1=Цю операцію <strong>ÐЕМОЖЛИВО</strong> ÑкаÑувати.
+settings.rename_notices_2=Стара назва буде перенаправлÑтиÑÑ Ð½Ð° нову, поки хтоÑÑŒ не викориÑтає Ñ—Ñ—.
+
settings.update_avatar_success=Ðватар організації оновлений.
settings.delete=Видалити організацію
settings.delete_account=Видалити цю організацію
-settings.delete_prompt=ÐžÑ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ оÑтаточно видалена. Це <strong>ÐЕ МОЖЛИВО</strong> відмінити!
-settings.confirm_delete_account=Підтвердіть видаленнÑ
-settings.delete_org_title=Видалити організацію
-settings.delete_org_desc=Ð¦Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ безповоротно видалена. Продовжити?
-settings.hooks_desc=Додайте webhooks, Ñкий буде викликатиÑÑ Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong> Ñкими володіє Ñ†Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ.
-
-settings.labels_desc=Додати мітки, Ñкі можуть бути викориÑтані Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong> в цій організації.
+settings.delete_prompt=Організацію буде оÑтаточно видалено. Це <strong>ÐЕМОЖЛИВО</strong> ÑкаÑувати!
+settings.name_confirm=Введіть назву організації Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ:
+settings.delete_notices_1=Цю операцію <strong>ÐЕМОЖЛИВО</strong> ÑкаÑувати.
+settings.delete_notices_2=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð·Ð°Ð²Ð¶Ð´Ð¸ видалить <strong>Ñховища</strong> <strong>%s</strong>, включно з кодом, задачами, коментарÑми, даними вікі та налаштуваннÑми Ñпівавторів.
+settings.delete_notices_3=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð·Ð°Ð²Ð¶Ð´Ð¸ видалить вÑÑ– <strong>пакети</strong> <strong>%s</strong>.
+settings.delete_notices_4=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð·Ð°Ð²Ð¶Ð´Ð¸ видалить вÑÑ– <strong>проєкти</strong> <strong>%s</strong>.
+settings.confirm_delete_account=Підтвердити видаленнÑ
+settings.delete_failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ організацію через внутрішню помилку
+settings.delete_successful=Організацію <b>%s</b> уÑпішно видалено.
+settings.hooks_desc=Додайте веб-хуки, Ñкі Ñпрацьовуватимуть Ð´Ð»Ñ <strong>вÑÑ–Ñ… Ñховищ</strong> у цій організації.
+
+settings.labels_desc=Додайте мітки, Ñкі можна викориÑтовувати у задачах Ð´Ð»Ñ <strong>уÑÑ–Ñ… Ñховищ</strong> у цій організації.
members.membership_visibility=ВидиміÑть учаÑника:
members.public=Показувати
-members.public_helper=зробити прихованим
+members.public_helper=приховати
members.private=Прихований
members.private_helper=зробити видимим
members.member_role=Роль учаÑника:
@@ -1964,22 +2687,28 @@ teams.leave=Покинути
teams.leave.detail=Покинути %s?
teams.can_create_org_repo=Створити репозиторії
teams.can_create_org_repo_helper=УчаÑники можуть Ñтворювати нові репозиторії в організації. Ðвтор отримає доÑтуп адмініÑтратора до нового репозиторію.
-teams.read_access=Прочитані
+teams.none_access=Ðемає доÑтупу
+teams.none_access_helper=УчаÑники не можуть переглÑдати або виконувати будь-Ñкі інші дії з цією одиницею. Це не впливає на загальнодоÑтупні Ñховища.
+teams.general_access=Загальний доÑтуп
+teams.general_access_helper=Дозволи учаÑників будуть визначатиÑÑ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð½Ð¾ до наведеної нижче таблиці дозволів.
+teams.read_access=ЧитаннÑ
teams.read_access_helper=УчаÑники можуть переглÑдати та клонувати репозиторії команд.
+teams.write_access=ЗапиÑ
teams.write_access_helper=УчаÑники можуть читати Ñ– виконувати push в репозиторії команд.
teams.admin_access=ДоÑтуп адмініÑтратора
teams.admin_access_helper=УчаÑники можуть виконувати pull, push в репозиторії команд Ñ– додавати Ñпівавторів в команду.
teams.no_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° не має опиÑу
teams.settings=ÐалаштуваннÑ
-teams.owners_permission_desc=ВлаÑник має повний доÑтуп до <strong>уÑÑ–Ñ… репозиторіїв</strong> та має <strong>права адмініÑтратора</strong> організації.
+teams.owners_permission_desc=ВлаÑники мають повний доÑтуп до <strong>уÑÑ–Ñ… репозиторіїв</strong> та <strong>права адмініÑтратора</strong> організації.
teams.members=УчаÑники команди
teams.update_settings=Оновити налаштуваннÑ
teams.delete_team=Видалити команду
teams.add_team_member=Додати учаÑника команди
+teams.invite_team_member=ЗапроÑити до %s
teams.delete_team_title=Видалити команду
teams.delete_team_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ ÑкаÑовує доÑтуп до Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð´Ð»Ñ Ñ—Ñ— учаÑників. Продовжити?
teams.delete_team_success=Команду було видалено.
-teams.read_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° має доÑтуп Ð´Ð»Ñ <strong>читаннÑ</strong>: учаÑники можуть переглÑдати та клонувати репозиторії.
+teams.read_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° має доÑтуп на <strong>читаннÑ</strong>: учаÑники можуть переглÑдати та клонувати репозиторії.
teams.write_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає доÑтуп на <strong>запиÑ</strong>: учаÑники можуть отримувати й виконувати push команди до репозитрію.
teams.admin_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає <strong>адмініÑтраторÑький</strong> доÑтуп: учаÑники можуть читати, виконувати push команди та додавати Ñпівробітників до репозиторію.
teams.create_repo_permission_desc=Крім того, Ñ†Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>Створити репозиторій</strong>: учаÑники можуть Ñтворювати нові репозиторії в організації.
@@ -1988,9 +2717,11 @@ teams.remove_all_repos_title=Видалити вÑÑ– репозиторії ко
teams.remove_all_repos_desc=Це видалить уÑÑ– репозиторії команди.
teams.add_all_repos_title=Додати вÑÑ– репозиторії
teams.add_all_repos_desc=Це додаÑть вÑÑ– репозиторії організації до команди.
+teams.add_nonexistent_repo=Сховище, Ñке ви намагаєтеÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸, не Ñ–Ñнує, будь лаÑка, Ñтворіть його Ñпочатку.
teams.add_duplicate_users=КориÑтувач уже Ñ” членом команди.
teams.repos.none=Ð”Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ немає доÑтупних репозиторіїв.
teams.members.none=Ðемає членів в цій команді.
+teams.members.blocked_user=Ðе вдаєтьÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ кориÑтувача, оÑкільки він заблокований організацією.
teams.specific_repositories=Конкретні репозиторії
teams.specific_repositories_helper=УчаÑники матимуть доÑтуп лише до репозиторіїв, Ñкі були Ñвно додані до команди. Вибір цього пункту <strong>не призводить</strong> до автоматичного Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ—Ð², доданих з <i>Ð’ÑÑ– репозиторії</i>.
teams.all_repositories=Ð’ÑÑ– репозиторії
@@ -1998,17 +2729,35 @@ teams.all_repositories_helper=Команда має доÑтуп до вÑÑ–Ñ… Ñ
teams.all_repositories_read_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ПереглÑд</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати та клонувати Ñ—Ñ….
teams.all_repositories_write_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ЗапиÑ</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати та виконувати push в репозиторіÑÑ….
teams.all_repositories_admin_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ÐдмініÑтруваннÑ</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати, виконувати push та додавати Ñпівробітників.
-
-
+teams.invite.title=Ð’Ð°Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñили приєднатиÑÑ Ð´Ð¾ команди <strong>%s</strong> в організації <strong>%s</strong>.
+teams.invite.by=Запрошений %s
+teams.invite.description=Будь лаÑка, натиÑніть на кнопку нижче, щоб приєднатиÑÑ Ð´Ð¾ команди.
+
+view_as_role=ПереглÑнути Ñк: %s
+view_as_public_hint=Ви переглÑдаєте README Ñк публічний кориÑтувач.
+view_as_member_hint=Ви переглÑдаєте README Ñк член цієї організації.
+
+worktime=Ð§Ð°Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸
+worktime.date_range_start=Дата початку
+worktime.date_range_end=Дата завершеннÑ
+worktime.query=Запит
+worktime.time=ЧаÑ
+worktime.by_milestones=За етапами
+worktime.by_members=За учаÑниками
[admin]
+maintenance=Технічне обÑлуговуваннÑ
dashboard=Панель управліннÑ
+self_check=Самоперевірка
+identity_access=Ð†Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñ‚Ð° доÑтуп
users=Облікові запиÑи кориÑтувачів
organizations=Організації
+assets=РеÑурÑи коду
repositories=Репозиторії
hooks=Веб-хуки
+integrations=Інтеграції
authentication=Джерела автентифікації
-emails=Електронні адреÑи КориÑтувача
+emails=Електронна пошта кориÑтувача
config=КонфігураціÑ
config_summary=ПідÑумок
config_settings=ÐалаштуваннÑ
@@ -2017,8 +2766,11 @@ monitor=Моніторинг
first_page=Перша
last_page=ОÑтаннÑ
total=Разом: %d
+settings=ÐдмініÑтративні налаштуваннÑ
+dashboard.new_version_hint=Gitea %s тепер доÑтупна, ви викориÑтовуєте %s. Перевірте <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">блог</a> Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації.
dashboard.statistic=ПідÑумок
+dashboard.maintenance_operations=Операції з технічного обÑлуговуваннÑ
dashboard.system_status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑиÑтеми
dashboard.operation_name=Ðазва операції
dashboard.operation_switch=Перемкнути
@@ -2027,20 +2779,23 @@ dashboard.clean_unbind_oauth=ОчиÑтити ÑпиÑок незавершенÐ
dashboard.clean_unbind_oauth_success=Ð’ÑÑ– незавершені зв'Ñзки OAuth були видалені.
dashboard.task.started=Запущено завданнÑ: %[1]s
dashboard.task.process=ЗавданнÑ: %[1]s
+dashboard.task.cancelled=ЗавданнÑ: %[1]s ÑкаÑовано: %[3]s
dashboard.task.error=Помилка у завданні: %[1]s:%[3]s
dashboard.task.finished=ЗавершилоÑÑ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ, Ñке запуÑтив %[2]s: %[1]s
dashboard.task.unknown=Ðевідоме завданнÑ: %[1]s
dashboard.cron.started=Запущено Cron: %[1]s
dashboard.cron.process=Cron: %[1]s
+dashboard.cron.cancelled=Планувальник: %[1]s ÑкаÑовано: %[3]s
dashboard.cron.error=Помилка в Cron: %s: %[3]s
dashboard.cron.finished=Cron: %[1]s завершено
dashboard.delete_inactive_accounts=Видалити вÑÑ– неактивовані облікові запиÑи
-dashboard.delete_inactive_accounts.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ– неактивованих облікових запиÑів.
+dashboard.delete_inactive_accounts.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… неактивованих облікових запиÑів.
dashboard.delete_repo_archives=Видалити вÑÑ– архіви репозиторіїв (ZIP, TAR.GZ, Ñ– Ñ‚. д..)
dashboard.delete_repo_archives.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… архівів репозиторіїв.
-dashboard.delete_missing_repos=Видалити вÑÑ– запиÑи про репозиторії з відÑутніми файлами Git
+dashboard.delete_missing_repos=Видаліть уÑÑ– Ñховища, в Ñких відÑутні файли Git
dashboard.delete_missing_repos.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… репозиторіїв, в Ñких відÑутні файли Git.
-dashboard.delete_generated_repository_avatars=Видалити репозиторій з згенерованими аватарами
+dashboard.delete_generated_repository_avatars=Видалити згенеровані аватарки Ñховища
+dashboard.sync_repo_branches=Синхронізувати пропущені гілки з даних git до баз даних
dashboard.update_mirrors=Оновити дзеркала
dashboard.repo_health_check=Перевірка Ñтану вÑÑ–Ñ… репозиторіїв
dashboard.check_repo_stats=Перевірити ÑтатиÑтику вÑÑ–Ñ… репозиторіїв
@@ -2050,11 +2805,13 @@ dashboard.update_migration_poster_id=Оновити мігровані ID авт
dashboard.git_gc_repos=Виконати очиÑтку ÑÐ¼Ñ–Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ… репозиторіїв
dashboard.resync_all_sshkeys=Оновити файл '.ssh/authorized_keys' з SSH ключами Gitea.
dashboard.resync_all_sshprincipals=Оновіть файл '.ssh/authorized_princÑ‚ipals' з SSH даними кориÑтувача Gitea.
-dashboard.resync_all_hooks=ПереÑинхронізувати перед-прийнÑтні, оновлюючі та поÑÑ‚-прийнÑтні хуки в уÑÑ–Ñ… репозиторіÑÑ….
-dashboard.reinit_missing_repos=Переініціалізувати уÑÑ– репозитрії git-файли Ñких втрачено
+dashboard.resync_all_hooks=Заново Ñинхронізувати хуки попереднього отриманнÑ, Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚Ð° поÑÑ‚-Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð²ÑÑ–Ñ… Ñховищ.
+dashboard.reinit_missing_repos=Заново ініціалізувати вÑÑ– відÑутні Ñховища Git'а, Ð´Ð»Ñ Ñких Ñ–Ñнують запиÑи
dashboard.sync_external_users=Синхронізувати дані зовнішніх кориÑтувачів
-dashboard.cleanup_hook_task_table=ОчиÑтити hook_task таблицю
-dashboard.server_uptime=Uptime Ñерверу
+dashboard.cleanup_hook_task_table=ОчиÑтити таблицю hook_task
+dashboard.cleanup_packages=ОчиÑтити заÑтарілі пакети
+dashboard.cleanup_actions=ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ñ€ÐµÑурÑів проÑтрочених дій
+dashboard.server_uptime=Ð§Ð°Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ Ñервера
dashboard.current_goroutine=Поточна кількіÑть Goroutines
dashboard.current_memory_usage=Поточне викориÑÑ‚Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті
dashboard.total_memory_allocated=Виділено пам'Ñті загалом
@@ -2083,6 +2840,18 @@ dashboard.total_gc_time=Загальна пауза збирача ÑміттÑ
dashboard.total_gc_pause=Загальна пауза збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC)
dashboard.last_gc_pause=ОÑÑ‚Ð°Ð½Ð½Ñ Ð¿Ð°ÑƒÐ·Ð° збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC)
dashboard.gc_times=КількіÑть запуÑків збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC)
+dashboard.delete_old_actions=Видалити вÑÑ– Ñтарі дії з бази даних
+dashboard.delete_old_actions.started=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… Ñтарих дій з бази даних розпочато.
+dashboard.update_checker=Перевірка оновлень
+dashboard.delete_old_system_notices=Видалити вÑÑ– Ñтарі ÑиÑтемні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· бази даних
+dashboard.gc_lfs=Збір ÑÐ¼Ñ–Ñ‚Ñ‚Ñ Ð¼ÐµÑ‚Ð°-об'єктів LFS
+dashboard.stop_endless_tasks=Зупинити неÑкінченні завданнÑ
+dashboard.cancel_abandoned_jobs=СкаÑувати покинуті завданнÑ
+dashboard.start_schedule_tasks=ЗапуÑк запланованих завдань
+dashboard.sync_branch.started=Розпочато Ñинхронізацію гілок
+dashboard.sync_tag.started=Розпочато Ñинхронізацію міток
+dashboard.rebuild_issue_indexer=Перебудувати індекÑатор задач
+dashboard.sync_repo_licenses=Синхронізувати ліцензії Ñховища
users.user_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¸Ð¼Ð¸ запиÑами кориÑтувачів
users.new_account=Створити обліковий запиÑ
@@ -2091,12 +2860,16 @@ users.full_name=Повне ім'Ñ
users.activated=Ðктивовано
users.admin=ÐдмініÑтратор
users.restricted=Обмежено
+users.reserved=Зарезервовано
+users.bot=Бот
+users.remote=Віддалений
users.2fa=2FA
users.repos=Репозиторії
users.created=Створено
users.last_login=ОÑтанній вхід
users.never_login=Ðіколи не входив
users.send_register_notify=ÐадіÑлати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ реєÑтрацію кориÑтувача
+users.new_success=Обліковий Ð·Ð°Ð¿Ð¸Ñ "%s" Ñтворено.
users.edit=Редагувати
users.auth_source=Джерело автентифікації
users.local=Локальні
@@ -2116,8 +2889,10 @@ users.allow_import_local=Може імпортувати локальні реп
users.allow_create_organization=Може Ñтворювати організацій
users.update_profile=Оновити обліковий запиÑ
users.delete_account=Видалити цей обліковий запиÑ
-users.still_own_repo=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñе ще володіє одним або кількома репозиторіÑми, Ñпочатку вам потрібно видалити або передати Ñ—Ñ….
-users.still_has_org=Цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñе ще Ñ” учаÑником однієї або декількох організацій. Ð”Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð²Ð¶ÐµÐ½Ð½Ñ, покиньте або видаліть організації.
+users.cannot_delete_self=Ви не можете видалити Ñебе
+users.still_own_repo=Цей кориÑтувач вÑе ще володіє одним або кількома Ñховищами. Спочатку видаліть або передайте ці Ñховища.
+users.still_has_org=Цей кориÑтувач Ñ” членом організації. Спочатку видаліть кориÑтувача з уÑÑ–Ñ… організацій.
+users.purge=Видалити кориÑтувача
users.deletion_success=Обліковий Ð·Ð°Ð¿Ð¸Ñ ÐºÐ¾Ñ€Ð¸Ñтувача було видалено.
users.reset_2fa=Скинути 2FA
users.list_status_filter.menu_text=Фільтр
@@ -2128,13 +2903,14 @@ users.list_status_filter.is_admin=ÐдмініÑтратор
users.list_status_filter.not_admin=Ðе адмініÑтратор
users.list_status_filter.is_restricted=З обмеженнÑми
users.list_status_filter.not_restricted=Без обмежень
-users.list_status_filter.is_prohibit_login=Вхід заборонено
-users.list_status_filter.not_prohibit_login=Вхід дозволено
+users.list_status_filter.is_prohibit_login=Заборонити вхід
+users.list_status_filter.not_prohibit_login=Дозволити вхід
users.list_status_filter.is_2fa_enabled=2FA увімкнена
users.list_status_filter.not_2fa_enabled=2FA вимкнена
+users.details=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ кориÑтувача
emails.email_manage_panel=Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¾ÑŽ кориÑтувача
-emails.primary=Головний
+emails.primary=ОÑновна
emails.activated=Ðктивовано
emails.filter_sort.email=Електронна пошта
emails.filter_sort.email_reverse=Електронна пошта (зворотна)
@@ -2142,8 +2918,13 @@ emails.filter_sort.name=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача
emails.filter_sort.name_reverse=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача (зворотне)
emails.updated=Електронну пошту оновлено
emails.not_updated=Ðе вдалоÑÑŒ оновити адреÑу електронної пошти: %v
-emails.duplicate_active=Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° адреÑа вже активна Ð´Ð»Ñ Ñ–Ð½ÑˆÐ¾Ð³Ð¾ кориÑтувача.
+emails.duplicate_active=Ð¦Ñ Ð°Ð´Ñ€ÐµÑа електронної пошти вже активна Ð´Ð»Ñ Ñ–Ð½ÑˆÐ¾Ð³Ð¾ кориÑтувача.
emails.change_email_header=Редагувати влаÑтивоÑті електронної пошти
+emails.change_email_text=Ви впевнені, що хочете оновити адреÑу електронної пошти?
+emails.delete=Видалити адреÑу електронної пошти
+emails.delete_desc=Ви впевнені, що хочете видалити адреÑу електронної пошти?
+emails.deletion_success=ÐдреÑу електронної пошти видалено.
+emails.delete_primary_email_error=Ви не можете видалити оÑновну адреÑу електронної пошти.
orgs.org_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñми
orgs.name=Ðазва
@@ -2159,12 +2940,21 @@ repos.name=Ðазва
repos.private=Приватний
repos.issues=Задачі
repos.size=Розмір
+repos.lfs_size=Розмір LFS
+packages.package_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°ÐºÐµÑ‚Ð°Ð¼Ð¸
+packages.total_size=Загальний розмір: %s
+packages.unreferenced_size=Розмір без поÑилань: %s
+packages.cleanup=ОчиÑтити проÑтрочені дані
+packages.cleanup.success=ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ñтрочених даних завершено
packages.owner=ВлаÑник
+packages.creator=Ðвтор
packages.name=Ðазва
+packages.version=ВерÑÑ–Ñ
packages.type=Тип
-packages.repository=Репозиторій
+packages.repository=Сховище
packages.size=Розмір
+packages.published=Опубліковано
defaulthooks=Веб-хуки за замовчуваннÑм
defaulthooks.add_webhook=Додати веб-хук за замовчуваннÑм
@@ -2176,7 +2966,7 @@ systemhooks.update_webhook=Оновити ÑиÑтемний вебхук
auths.auth_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¶ÐµÑ€ÐµÐ»Ð¾Ð¼ автентифікації
auths.new=Додати джерело автентифікації
-auths.name=Ім'Ñ
+auths.name=Ðазва
auths.type=Тип
auths.enabled=Увімкнено
auths.syncenabled=Увімкнути Ñинхронізацію кориÑтувача
@@ -2193,12 +2983,13 @@ auths.user_base=База пошуку кориÑтувачів
auths.user_dn=DN кориÑтувача
auths.attribute_username=Ðтрибут імені кориÑтувача
auths.attribute_username_placeholder=Залиште порожнім, щоб викориÑтовувати ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації.
-auths.attribute_name=Ðтрибут імені
-auths.attribute_surname=Ðтрибут Surname
-auths.attribute_mail=Ðтрибут Email
-auths.attribute_ssh_public_key=Ðтрибут Відкритий SSH ключ
+auths.attribute_name=ВлаÑтивоÑті імені
+auths.attribute_surname=ВлаÑтивоÑті прізвища
+auths.attribute_mail=ВлаÑтивоÑті електронної пошти
+auths.attribute_ssh_public_key=ВлаÑтивоÑті публічного ключа SSH
+auths.attribute_avatar=ВлаÑтивоÑті аватару
auths.attributes_in_bind=ВитÑгувати атрибути в контекÑті Bind DN
-auths.allow_deactivate_all=Дозволити порожньому результату пошуку відключити вÑÑ–Ñ… кориÑтувачів
+auths.allow_deactivate_all=Дозволити порожній результат пошуку, щоб деактивувати вÑÑ–Ñ… кориÑтувачів
auths.use_paged_search=ВикориÑтовувати поÑторінковий пошук
auths.search_page_size=Розмір Ñторінки
auths.filter=КориÑтувацький фільтр
@@ -2208,6 +2999,7 @@ auths.restricted_filter_helper=Залиште пуÑтим, щоб не вÑта
auths.group_search_base=Пошукова база груп DN
auths.group_attribute_list_users=Ðтрибут групи зі ÑпиÑком кориÑтувачів
auths.user_attribute_in_group=Ðтрибути кориÑтувача в групі
+auths.enable_ldap_groups=Увімкнути групи LDAP
auths.ms_ad_sa=Ðтрибути пошуку MS AD
auths.smtp_auth=Тип автентифікації SMTP
auths.smtphost=SMTP хоÑÑ‚
@@ -2223,7 +3015,7 @@ auths.disable_helo=Вимкнути HELO
auths.pam_service_name=Ім'Ñ Ñлужби PAM
auths.pam_email_domain=Поштовий домен PAM (необов'Ñзково)
auths.oauth2_provider=ПоÑтачальник OAuth2
-auths.oauth2_icon_url=URL іконки
+auths.oauth2_icon_url=URL піктограми
auths.oauth2_clientID=ID клієнта (ключ)
auths.oauth2_clientSecret=Ключ клієнта
auths.openIdConnectAutoDiscoveryURL=OpenID Connect URL Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ð·Ð°Ñ†Ñ–Ñ— входу
@@ -2235,6 +3027,7 @@ auths.oauth2_emailURL=URL електронної пошти
auths.skip_local_two_fa=ПропуÑтити локальну 2FA
auths.skip_local_two_fa_helper=Якщо Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ вказано, локальнам кориÑтувачам, що викориÑтовують двофакторну автентифікацію, вÑе одно проходитимуть Ñ—Ñ— Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ в ÑиÑтему
auths.oauth2_tenant=Tenant
+auths.oauth2_map_group_to_team_removal=Видалити кориÑтувачів із Ñинхронізованих команд, Ñкщо кориÑтувач не належить до відповідної групи.
auths.enable_auto_register=Увімкнути автоматичну реєÑтрацію
auths.sspi_auto_create_users=Ðвтоматично Ñтворювати кориÑтувачів
auths.sspi_auto_create_users_helper=Дозволити автоматичне ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ… облікових запиÑів Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів, Ñкі вперше увійшли з викориÑÑ‚Ð°Ð½Ð½Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— SSPI
@@ -2248,21 +3041,33 @@ auths.sspi_default_language=Типова мова кориÑтувача
auths.sspi_default_language_helper=Типова мова Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів, Ñкі ÑтворюютьÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾ при SSPI-автентифікації. Залиште не вказаним, Ñкщо надаєте перевагу автоматичному визначенню мови.
auths.tips=Поради
auths.tips.oauth2.general=OAuth2 автентифікаціÑ
+auths.tips.oauth2.general.tip=Під Ñ‡Ð°Ñ Ñ€ÐµÑ”Ñтрації нової автентифікації OAuth2 URL-адреÑа зворотного виклику/Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð²Ð¸Ð½Ð½Ð° бути такою:
auths.tip.oauth2_provider=ПоÑтачальник OAuth2
+auths.tip.bitbucket=`ЗареєÑтруйте нового Ñпоживача OAuth на %s Ñ– додайте дозвіл "Обліковий запиÑ" - "ЧитаннÑ"`
auths.tip.nextcloud=`ЗареєÑтруйте нового Ñпоживача OAuth у вашому екземплÑрі за допомогою наÑтупного меню "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ -> Безпека -> клієнт OAuth 2.0"`
+auths.tip.dropbox=Створити новий додаток на %s
+auths.tip.facebook=`ЗареєÑтруйте новий додаток на %s Ñ– додайте модуль "Facebook Login"`
+auths.tip.github=ЗареєÑтруйте новий додаток OAuth на %s
+auths.tip.gitlab_new=ЗареєÑтруйте новий додаток на %s
+auths.tip.google_plus=Отримайте облікові дані клієнта OAuth2 з конÑолі Google API за адреÑою %s
+auths.tip.twitter=Перейдіть до %s, Ñтворіть додаток Ñ– переконайтеÑÑ, що параметр «Дозволити викориÑтовувати цей додаток Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ в Twitter» увімкнено
+auths.tip.discord=ЗареєÑтрувати новий додаток на %s
+auths.tip.gitea=ЗареєÑтруйте новий додаток OAuth2. ПоÑібник можна знайти за поÑиланнÑм %s
auths.tip.mastodon=Введіть URL Ñпеціального екземплÑра Ð´Ð»Ñ ÐµÐºÐ·ÐµÐ¼Ð¿Ð»Ñра mastodon, Ñкий ви хочете автентифікувати за допомогою (або викориÑтовувати за замовчуваннÑм)
auths.edit=Редагувати джерело автентифікації
-auths.activated=Ð¦Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð°ÐºÑ‚Ð¸Ð²Ð¾Ð²Ð°Ð½Ð°
-auths.update_success=Параметри аутентифікації оновлені.
+auths.activated=Це джерело автентифікації активовано
+auths.new_success=Ðвтентифікацію "%s" додано.
+auths.update_success=Джерело автентифікації оновлено.
auths.update=Оновити джерело автентифікації
auths.delete=Видалити джерело автентифікації
auths.delete_auth_title=Видалити джерело автентифікації
-auths.delete_auth_desc=Це джерело аутентифікації буде видалене, ви впевнені, що ви хочете продовжити?
-auths.still_in_used=Ð¦Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ° ÑправжноÑті доÑÑ– викориÑтовуєтьÑÑ Ð´ÐµÑкими кориÑтувачами. Видаліть або змініть Ð´Ð»Ñ Ñ†Ð¸Ñ… кориÑтувачів тип входу в ÑиÑтему.
-auths.deletion_success=Канал аутентифікації уÑпішно знищений.
-auths.login_source_of_type_exist=Джерело автентифікації такого типу вже наÑвне.
+auths.delete_auth_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð´Ð¶ÐµÑ€ÐµÐ»Ð° автентифікації заборонÑÑ” кориÑтувачам викориÑтовувати його Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ. Продовжити?
+auths.still_in_used=Джерело автентифікації вÑе ще викориÑтовуєтьÑÑ. Спочатку перетворіть або видаліть кориÑтувачів, Ñкі викориÑтовують це джерело автентифікації.
+auths.deletion_success=Джерело автентифікації видалено.
+auths.login_source_exist=Джерело автентифікації "%s" вже Ñ–Ñнує.
+auths.login_source_of_type_exist=Джерело автентифікації такого типу вже Ñ–Ñнує.
-config.server_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ñервера
+config.server_config=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñервера
config.app_name=Ðазва Ñайту
config.app_ver=ВерÑÑ–Ñ Gitea
config.app_url=Базова URL-адреÑа Gitea
@@ -2270,13 +3075,14 @@ config.custom_conf=ШлÑÑ… до файлу конфігурації
config.custom_file_root_path=ШлÑÑ… до файлу кориÑтувача
config.domain=Домен Ñервера
config.offline_mode=Локальний режим
-config.disable_router_log=Вимкнути Ð»Ð¾Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ð¾ÑƒÑ‚ÐµÑ€Ñƒ
+config.disable_router_log=Вимкнути журнал маршрутизатора
config.run_user=ЗапуÑк від імені КориÑтувача
config.run_mode=Режим виконаннÑ
config.git_version=ВерÑÑ–Ñ Git
+config.app_data_path=ШлÑÑ… до даних додатка
config.repo_root_path=Кореневий шлÑÑ… репозиторіÑ
-config.lfs_root_path=Кореневої шлÑÑ… LFS
-config.log_file_root_path=ШлÑÑ… до лог файлу
+config.lfs_root_path=Кореневий шлÑÑ… LFS
+config.log_file_root_path=ШлÑÑ… до журналу
config.script_type=Тип Ñкрипта
config.reverse_auth_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ— на reverse proxy
@@ -2286,7 +3092,7 @@ config.ssh_start_builtin_server=ВикориÑтовувати вбудованÐ
config.ssh_domain=Домен SSH Ñервера
config.ssh_port=Порт
config.ssh_listen_port=Порт що проÑлуховуєтьÑÑ
-config.ssh_root_path=ШлÑÑ… до кореню
+config.ssh_root_path=ШлÑÑ… до коренÑ
config.ssh_minimum_key_size_check=Мінімальний розмір ключа перевірки
config.ssh_minimum_key_sizes=Мінімальні розміри ключів
@@ -2298,8 +3104,8 @@ config.lfs_http_auth_expiry=ЗаÑтаріла LFS HTTP аунтифікаціÑ
config.db_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð±Ð°Ð·Ð¸ даних
config.db_type=Тип
config.db_host=ХоÑÑ‚
-config.db_name=Ім'Ñ
-config.db_user=Ім'Ñ ÐºÑ€Ð¸Ñтувача
+config.db_name=Ðазва
+config.db_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача
config.db_schema=Схема
config.db_ssl_mode=SSL
config.db_path=ШлÑÑ…
@@ -2331,16 +3137,24 @@ config.queue_length=Довжина черги
config.deliver_timeout=Затримка доÑтавки
config.skip_tls_verify=ПропуÑтити перевірку TLS
+config.mailer_config=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸
config.mailer_enabled=Увімкнено
-config.mailer_name=Ім'Ñ
-config.mailer_smtp_port=SMTP порт
+config.mailer_enable_helo=Увімкнути HELO
+config.mailer_name=Ðазва
+config.mailer_protocol=Протокол
+config.mailer_smtp_addr=ÐдреÑа SMTP
+config.mailer_smtp_port=Порт SMTP
config.mailer_user=КориÑтувач
config.mailer_use_sendmail=ВикориÑтовувати Sendmail
config.mailer_sendmail_path=ШлÑÑ… до Sendmail
config.mailer_sendmail_args=Додаткові аргументи до Sendmail
config.mailer_sendmail_timeout=Тайм-аут Sendmail
+config.mailer_use_dummy=Даммі
config.test_email_placeholder=ÐдреÑа електронної пошти (наприклад, test@example.com)
config.send_test_mail=Відправити теÑтового лиÑта
+config.send_test_mail_submit=ÐадіÑлати
+config.test_mail_failed=Ðе вдалоÑÑ Ð½Ð°Ð´Ñ–Ñлати теÑтовий лиÑÑ‚ "%s": %v
+config.test_mail_sent=ТеÑтовий лиÑÑ‚ надіÑлано "%s".
config.oauth_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ OAuth
config.oauth_enabled=Увімкнено
@@ -2350,6 +3164,10 @@ config.cache_adapter=Ðдаптер кешу
config.cache_interval=Інтервал кешуваннÑ
config.cache_conn=ÐŸÑ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ кешу
config.cache_item_ttl=Ð§Ð°Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… кешу
+config.cache_test=Перевірити кеш
+config.cache_test_failed=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ кеш: %v.
+config.cache_test_slow=Перевірка кешу уÑпішна, але відповідь повільна: %s.
+config.cache_test_succeeded=ТеÑÑ‚ кешу уÑпішно завершено, отримано відповідь за %s.
config.session_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ ÑеÑÑ–Ñ—
config.session_provider=Провайдер ÑеÑÑ–Ñ—
@@ -2378,23 +3196,32 @@ config.git_pull_timeout=Тайм-аут операції Pull
config.git_gc_timeout=Тайм-аут операції збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC)
config.log_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð¶ÑƒÑ€Ð½Ð°Ð»Ñƒ
+config.logger_name_fmt=Журнал: %s
config.disabled_logger=Вимкнено
config.access_log_mode=Режим доÑтупу до журналу
+config.access_log_template=Шаблон журналу доÑтупу
config.xorm_log_sql=Журнал SQL
+config.set_setting_failed=Ðе вдалоÑÑ Ð²Ñтановити параметр %s
+monitor.stats=СтатиÑтика
monitor.cron=Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ cron
-monitor.name=Ім'Ñ
+monitor.name=Ðазва
monitor.schedule=Розклад
monitor.next=ÐаÑтупного разу
monitor.previous=Попереднього разу
monitor.execute_times=КількіÑть виконань
monitor.process=Запущені процеÑи
+monitor.performance_logs=Журнал швидкодії
+monitor.processes_count=%d процеÑи(-ів)
+monitor.download_diagnosis_report=Завантажити звіт діагноÑтики
monitor.desc=ОпиÑ
monitor.start=Ð§Ð°Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ
monitor.execute_time=Ð§Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ
+monitor.last_execution_result=Результат
monitor.process.cancel=Зупинити процеÑ
+monitor.process.cancel_desc=СкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑу може призвеÑти до втрати даних
monitor.process.children=Дочірні процеÑи
monitor.queues=Черги
@@ -2404,15 +3231,19 @@ monitor.queue.type=Тип
monitor.queue.exemplar=Приклад типу
monitor.queue.numberworkers=КількіÑть робочих потоків
monitor.queue.maxnumberworkers=МакÑимальна кількіÑть робочих потоків
+monitor.queue.numberinqueue=Ðомер у черзі
monitor.queue.settings.title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÑƒÐ»Ñƒ
monitor.queue.settings.maxnumberworkers=МакÑимальна кількіÑть робочих потоків
monitor.queue.settings.maxnumberworkers.placeholder=Поточний %[1]d
monitor.queue.settings.maxnumberworkers.error=МакÑимальна кількіÑть робочих потоків має бути чиÑлом
monitor.queue.settings.submit=Оновити налаштуваннÑ
monitor.queue.settings.changed=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð¾
+monitor.queue.settings.remove_all_items=Видалити вÑе
+monitor.queue.settings.remove_all_items_done=УÑÑ– елементи черги видалено.
notices.system_notice_list=Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÑиÑтеми
notices.view_detail_header=ПереглÑнути деталі повідомленнÑ
+notices.operations=Дії
notices.select_all=Вибрати вÑе
notices.deselect_all=СкаÑувати виділеннÑ
notices.inverse_selection=Інвертувати виділене
@@ -2425,11 +3256,19 @@ notices.desc=ОпиÑ
notices.op=Оп.
notices.delete_success=Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÑиÑтеми були видалені.
+self_check.no_problem_found=Ðаразі проблем не виÑвлено.
+self_check.startup_warnings=ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´ Ñ‡Ð°Ñ Ð·Ð°Ð¿ÑƒÑку:
+self_check.database_collation_mismatch=ОчікуєтьÑÑ, що база даних викориÑтає зіÑтавленнÑ: %s
+self_check.database_collation_case_insensitive=У базі даних викориÑтовуєтьÑÑ Ð·Ñ–ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ñ %s, що Ñ” нечутливим. Хоч Gitea може працювати з ним, у деÑких рідкіÑних випадках він може працювати не так, Ñк очікуєтьÑÑ.
+self_check.database_inconsistent_collation_columns=База даних викориÑтовує зіÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ñ %s, але ці Ñтовпці викориÑтовують невідповідні зіÑтавленнÑ. Це може Ñпричинити деÑкі неÑподівані проблеми.
+self_check.database_fix_mysql=Ð”Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів MySQL/MariaDB можна викориÑтати команду "gitea doctor convert" Ð´Ð»Ñ Ñ€Ð¾Ð·Ð²'ÑÐ·Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ з ÑортуваннÑм, або ви також можете розв'Ñзати проблему SQL командою "ALTER ... COLLATE ..." вручну.
+self_check.database_fix_mssql=`Ð”Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів MSSQL, наразі ви можете виправити цю проблему тільки через SQL запит "ALTER ... COLATE ..."`
+self_check.location_origin_mismatch=Поточна URL-адреÑа (%[1]) не відповідає URL-адреÑÑ– Gitea (%[2]). Якщо ви викориÑтовуєте зворотний прокÑÑ–, переконайтеÑÑ, що заголовки "Host" та "X-Forwarded-Proto" вÑтановлені правильно.
[action]
create_repo=Ñтворив(ла) репозиторій <a href="%s">%s</a>
rename_repo=репозиторій перейменовано з <code>%[1]s</code> на <a href="%[2]s">%[3]s</a>
-commit_repo=надіÑлав зміни (push) до <a href="%[2]s">%[3]s</a> о <a href="%[1]s">%[4]s</a>
+commit_repo=надіÑлав зміни (push) до <a href="%[2]s">%[3]s</a> в <a href="%[1]s">%[4]s</a>
create_issue=`відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`закрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue=`повторно відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
@@ -2439,6 +3278,7 @@ reopen_pull_request=`повторно відкрив запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a
comment_issue=`прокоментував задачу <a href="%[1]s">%[3]s#%[2]s</a>`
comment_pull=`прокоментував запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a href="%[1]s">%[3]s#%[2]s</a>`
merge_pull_request=`прийнÑв запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a href="%[1]s">%[3]s#%[2]s</a>`
+auto_merge_pull_request=`автоматично об'єднано запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a href="%[1]s">%[3]s#%[2]s</a>`
transfer_repo=перенеÑено репозиторій <code>%s</code> у <a href="%s">%s</a>
push_tag=Ñтворив мітку <a href="%[2]s">%[3]s</a> в <a href="%[1]s">%[4]s</a>
delete_tag=видалено мітку %[2]s з <a href="%[1]s">%[3]s</a>
@@ -2471,7 +3311,7 @@ seconds=%d Ñекунди
minutes=%d хвилини
hours=%d години
days=%d дні
-weeks=%d тижднів
+weeks=%d тижні(в)
months=%d міÑÑці
years=%d роки
raw_seconds=Ñекунди
@@ -2479,6 +3319,7 @@ raw_minutes=хвилини
[dropzone]
default_message=ПеретÑгніть файли або натиÑніть тут, щоб завантажити.
+invalid_input_type=Ðеможливо завантажити файли цього типу.
file_too_big=Розмір файлу ({{filesize}} MB), що більше ніж макÑимальний розмір: ({{maxFilesize}} MB).
remove_file=Видалити файл
@@ -2492,54 +3333,292 @@ pin=Прикріпити ÑповіщеннÑ
mark_as_read=Позначити Ñк прочитане
mark_as_unread=Позначити Ñк непрочитане
mark_all_as_read=Позначити вÑÑ– Ñк прочитані
+subscriptions=Ðбонементи
+watching=Стежить
+no_subscriptions=Ðемає абонементів
[gpg]
default_key=ПідпиÑано типовим ключем
error.extract_sign=Ðе вдалоÑÑ Ð²Ð¸Ñ‚Ñгти підпиÑ
error.generate_hash=Ðе вдалоÑÑ Ð·Ð³ÐµÐ½ÐµÑ€ÑƒÐ²Ð°Ñ‚Ð¸ хеш коміту
-error.no_committer_account=Ðккаунт кориÑтувача з таким Email не знайдено
-error.no_gpg_keys_found=Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ GPG ключ що відповідає даному підпиÑу
+error.no_committer_account=Ðемає облікового запиÑу, прив'Ñзаного до адреÑи електронної пошти комітера
+error.no_gpg_keys_found=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ підпиÑу в базі даних не знайдено жодного відомого ключа
error.not_signed_commit=ÐепідпиÑаний коміт
-error.failed_retrieval_gpg_keys=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ відповідний GPG ключ кориÑтувача
+error.failed_retrieval_gpg_keys=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ жодного ключа, прив'Ñзаного до облікового запиÑу комітера
error.probable_bad_signature=УВÐГÐ! Хоча ключ з таким ID Ñ– Ñ” в базі, коміт не може бути ним перевірено! Цей коміт ПІДОЗРІЛИЙ.
error.probable_bad_default_signature=УВÐГÐ! Хоча типовий ключ має цей ID, коміт не може бути ним перевірено! Цей коміт ПІДОЗРІЛИЙ.
[units]
-error.no_unit_allowed_repo=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього репозиториÑ.
-error.unit_not_allowed=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього репозиториÑ.
+unit=ÐžÐ´Ð¸Ð½Ð¸Ñ†Ñ Ð²Ð¸Ð¼Ñ–Ñ€ÑŽÐ²Ð°Ð½Ð½Ñ
+error.no_unit_allowed_repo=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього Ñховища.
+error.unit_not_allowed=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього Ñховища.
[packages]
+title=Пакети
+desc=Керувати пакетами Ñховища.
+empty=Ðаразі пакети відÑутні.
+no_metadata=Ðемає метаданих.
+empty.documentation=Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації про реєÑтр пакетів, дивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a>.
+empty.repo=Ви завантажили пакунок, але він тут не відображаєтьÑÑ? Перейдіть до <a href="%[1]s">Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°ÐºÐµÑ‚Ñƒ</a> та під'єднайте його до цього Ñховища.
+registry.documentation=Докладнішу інформацію про реєÑтр %s наведено у <a target="_blank" rel="noopener noreferrer" href="%s">документації</a>.
filter.type=Тип
+filter.type.all=Ð’ÑÑ–
+filter.no_result=Ваш фільтр не дав результатів.
+filter.container.tagged=З міткою
+filter.container.untagged=Без мітки
+published_by=%[1]s опубліковано <a href="%[2]s">%[3]s</a>
+published_by_in=%[1]s опубліковано <a href="%[2]s">%[3]s</a> у <a href="%[4]s"><strong>%[5]s</strong></a>
+installation=Ð’ÑтановленнÑ
+about=Про цей пакет
+requirements=Вимоги
+dependencies=ЗалежноÑті
+keywords=Ключові Ñлова
+details=Подробиці
+details.author=Ðвтор
+details.project_site=Сторінка проєкту
+details.repository_site=Сторінка Ñховища
+details.documentation_site=Сторінка документації
+details.license=ЛіцензіÑ
+assets=РеÑурÑи
+versions=ВерÑÑ–Ñ—
+versions.view_all=ПереглÑнути вÑе
+dependency.id=ID
+dependency.version=ВерÑÑ–Ñ
+search_in_external_registry=Шукати в %s
+alpine.registry=Ðалаштуйте цей реєÑтр, додавши URL у ваш файл <code>/etc/apk/repositories</code>:
+alpine.registry.key=Завантажте публічний ключ RSA реєÑтру в теку <code>/etc/apk/keys/</code> Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ підпиÑу індекÑу:
+alpine.registry.info=Виберіть $branch та $repository зі ÑпиÑку нижче.
+alpine.install=Щоб вÑтановити пакет, виконайте наÑтупну команду:
+alpine.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище
alpine.repository.branches=Гілки
alpine.repository.repositories=Репозиторії
+alpine.repository.architectures=Ðрхітектури
+arch.registry=Додати Ñервер з відповідним Ñховищем та архітектурою до <code>/etc/pacman.conf</code>:
+arch.install=Синхронізувати пакет з pacman:
+arch.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище
arch.repository.repositories=Репозиторії
-conan.details.repository=Репозиторій
+arch.repository.architectures=Ðрхітектури
+cargo.registry=Ðалаштуйте цей реєÑтр у файлі конфігурації Cargo (наприклад, <code>~/.cargo/config.toml</code>):
+cargo.install=Щоб вÑтановити пакет за допомогою Cargo, виконайте наÑтупну команду:
+chef.registry=Ðалаштуйте цей реєÑтр у вашому файлі <code>~/.chef/config.rb</code>:
+chef.install=Щоб вÑтановити пакет, виконайте наÑтупну команду:
+composer.registry=Ðалаштуйте цей реєÑтр у вашому файлі <code>~/.composer/config.json</code>:
+composer.install=Щоб вÑтановити пакет за допомогою Composer, виконайте наÑтупну команду:
+composer.dependencies=ЗалежноÑті
+composer.dependencies.development=ЗалежноÑті розробки
+conan.details.repository=Сховище
+conan.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка:
+conan.install=Щоб вÑтановити пакет за допомогою Conan, виконайте наÑтупну команду:
+conda.registry=Ðалаштуйте цей реєÑтр Ñк Ñховище Conda у влаÑному файлі <code>.condarc</code>:
+conda.install=Щоб вÑтановити пакет за допомогою Conda, виконайте наÑтупну команду:
+container.details.type=Тип зображеннÑ
+container.details.platform=Платформа
+container.pull=Завантажити образ з командного Ñ€Ñдка:
+container.images=Образи
+container.multi_arch=ОС / Ðрхітектура
+container.layers=Шари образів
+container.labels=Мітки
+container.labels.key=Ключ
+container.labels.value=ЗначеннÑ
+cran.registry=Ðалаштуйте цей реєÑтр у вашому файлі <code>Rprofile.site</code>:
+cran.install=Щоб вÑтановити пакет, виконайте наÑтупну команду:
+debian.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка:
+debian.registry.info=Оберіть $distribution та $component зі ÑпиÑку нижче.
+debian.install=Щоб вÑтановити пакет, виконайте наÑтупну команду:
+debian.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище
+debian.repository.distributions=ДиÑтрибутиви
+debian.repository.components=Компоненти
+debian.repository.architectures=Ðрхітектури
+generic.download=Завантажити пакет з командного Ñ€Ñдка:
+go.install=Ð’Ñтановити пакет із командного Ñ€Ñдка:
+helm.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка:
+helm.install=Щоб вÑтановити пакет, виконайте наÑтупну команду:
+maven.registry=Ðалаштуйте цей реєÑтр у файлі <code>pom.xml</code> вашого проєкту:
+maven.install=Щоб викориÑтати пакет, додайте наÑтупне до блоку <code>dependencies</code> у файлі <code>pom.xml</code>:
+maven.install2=Виконати з командного Ñ€Ñдка:
+maven.download=Щоб завантажити залежніÑть, виконайте в командному Ñ€Ñдку:
+nuget.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка:
+nuget.install=Щоб вÑтановити пакет за допомогою NuGet, запуÑтіть наÑтупну команду:
+nuget.dependency.framework=Цільовий фреймворк
+npm.registry=Ðалаштувати цей реєÑтр у файлі вашого проєкту <code>.npmrc</code>:
+npm.install=Щоб вÑтановити пакет за допомогою npm, виконайте наÑтупну команду:
+npm.install2=або додайте до файлу package.json:
+npm.dependencies=ЗалежноÑті
+npm.dependencies.development=ЗалежноÑті розробки
+npm.dependencies.optional=ÐеобовʼÑзкові залежноÑті
+npm.details.tag=Мітка
+pypi.requires=Потрібен Python
+rpm.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка:
+rpm.distros.redhat=на диÑтрибутивах на оÑнові RedHat
+rpm.distros.suse=на диÑтрибутивах на оÑнові SUSE
+rpm.install=Щоб вÑтановити пакет, виконайте наÑтупну команду:
+rpm.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище
+rpm.repository.architectures=Ðрхітектури
+rpm.repository.multiple_groups=Цей пакет доÑтупний у багатьох групах.
+rubygems.install=Щоб вÑтановити пакет за допомогою gem, виконайте наÑтупну команду:
+rubygems.install2=або додайте до Gemfile:
+rubygems.dependencies.development=ЗалежноÑті розробки
+rubygems.required.ruby=Вимагає верÑÑ–ÑŽ Ruby
+rubygems.required.rubygems=Вимагає верÑÑ–ÑŽ RubyGem
+swift.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка:
+swift.install=Додайте пакет у ваш файл <code>Package.swift</code>:
+swift.install2=Ñ– виконайте наÑтупну команду:
+vagrant.install=Щоб додати Ð±Ð¾ÐºÑ Vagrant, виконайте наÑтупну команду:
+settings.link=Прив'Ñзати пакет до Ñховища
+settings.link.description=Якщо ви зв'Ñжете пакет зі Ñховищем, його буде вказано у ÑпиÑку пакетів Ñховища.
+settings.link.select=Обрати Ñховище
+settings.link.button=Оновити поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° Ñховище
+settings.link.error=Ðе вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° Ñховище.
+settings.delete=Видалити пакет
+settings.delete.description=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ð°ÐºÐµÑ‚Ð° Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване.
+settings.delete.notice=Ви збираєтеÑÑŒ видалити %s (%s). Цю операцію неможливо ÑкаÑувати, ви впевнені?
+settings.delete.success=Пакет видалено.
+settings.delete.error=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ пакет.
+owner.settings.cargo.title=Ð†Ð½Ð´ÐµÐºÑ Ñ€ÐµÑ”Ñтру Cargo
+owner.settings.cargo.initialize=Ініціалізувати індекÑ
+owner.settings.cargo.initialize.description=Ð”Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ñ€ÐµÑ”Ñтру Cargo потрібне Ñпеціальне Ñховище індекÑів Git. ВикориÑÑ‚Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ параметра дозволить (повторно) Ñтворити та автоматично налаштувати Ñховище.
+owner.settings.cargo.initialize.error=Ðе вдалоÑÑ Ñ–Ð½Ñ–Ñ†Ñ–Ð°Ð»Ñ–Ð·ÑƒÐ²Ð°Ñ‚Ð¸ Ñ–Ð½Ð´ÐµÐºÑ Cargo: %v
+owner.settings.cargo.initialize.success=Ð†Ð½Ð´ÐµÐºÑ Cargo уÑпішно Ñтворено.
+owner.settings.cargo.rebuild=Перебудувати індекÑ
+owner.settings.cargo.rebuild.description=Повторна збірка може бути кориÑною, Ñкщо Ñ–Ð½Ð´ÐµÐºÑ Ð½Ðµ Ñинхронізовано зі збереженими пакетами Cargo.
+owner.settings.cargo.rebuild.error=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ±ÑƒÐ´ÑƒÐ²Ð°Ñ‚Ð¸ Ñ–Ð½Ð´ÐµÐºÑ Cargo: %v
+owner.settings.cargo.rebuild.success=Ð†Ð½Ð´ÐµÐºÑ Cargo уÑпішно перебудовано.
+owner.settings.cleanuprules.title=Керувати правилами очищеннÑ
+owner.settings.cleanuprules.add=Додати правило очищеннÑ
+owner.settings.cleanuprules.edit=Редагувати правило очищеннÑ
+owner.settings.cleanuprules.none=Правила Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ñутні. Будь лаÑка, звернітьÑÑ Ð´Ð¾ документації.
+owner.settings.cleanuprules.preview=Попередній переглÑд правила очищеннÑ
+owner.settings.cleanuprules.preview.overview=Заплановано Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ %d пакетів.
+owner.settings.cleanuprules.preview.none=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð½Ðµ відповідає жодному пакету.
owner.settings.cleanuprules.enabled=Увімкнено
+owner.settings.cleanuprules.pattern_full_match=ЗаÑтоÑувати шаблон до повної назви пакета
+owner.settings.cleanuprules.keep.title=ВерÑÑ–Ñ—, Ñкі відповідають цим правилам, зберігаютьÑÑ, навіть Ñкщо вони відповідають наведеному нижче правилу видаленнÑ.
+owner.settings.cleanuprules.keep.count=Залишити найновіші
+owner.settings.cleanuprules.keep.count.1=1 верÑÑ–Ñ Ð½Ð° пакет
+owner.settings.cleanuprules.keep.count.n=%d верÑÑ–Ñ—(-й) на пакет
+owner.settings.cleanuprules.keep.pattern=Зберігати верÑÑ–Ñ—, що збігаютьÑÑ
+owner.settings.cleanuprules.remove.title=ВерÑÑ–Ñ—, Ñкі відповідають цим правилам, видалÑютьÑÑ, Ñкщо правило вище не вимагає Ñ—Ñ… збереженнÑ.
+owner.settings.cleanuprules.remove.days=Видалити верÑÑ–Ñ—, Ñтаріші за
+owner.settings.cleanuprules.remove.pattern=Видалити верÑÑ–Ñ—, що збігаютьÑÑ
+owner.settings.cleanuprules.success.update=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð¾.
+owner.settings.cleanuprules.success.delete=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾.
+owner.settings.chef.title=РеєÑтр Chef
+owner.settings.chef.keypair=Згенерувати ключову пару
+owner.settings.chef.keypair.description=Ключова пара необхідна Ð´Ð»Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— у реєÑтрі Chef. Якщо ви Ñтворювали ключову пару раніше, ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¾Ñ— пари призведе до ÑкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ñтарої.
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=ОпиÑ
+creation.name_placeholder=без ÑƒÑ€Ð°Ñ…ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ³Ñ–Ñтру, тільки алфавітно-цифрові Ñимволи або підкреÑленнÑ, не можуть починатиÑÑ Ð· GITEA_ або GITHUB_.
+creation.value_placeholder=Введіть довільний вміÑÑ‚. Пробіли на початку та в кінці будуть пропущені.
+creation.description_placeholder=Введіть короткий Ð¾Ð¿Ð¸Ñ (необов'Ñзково).
+
+
[actions]
+actions=Дії
+unit.desc=Керувати діÑми
+status.unknown=Ðевідомий
+status.waiting=ОчікуваннÑ
+status.running=ВиконуєтьÑÑ
+status.success=УÑпіх
+status.failure=Ðевдача
+status.cancelled=СкаÑовано
+status.skipped=Пропущено
+status.blocked=Заблоковано
+runners.status=СтатуÑ
+runners.id=ID
runners.name=Ðазва
runners.owner_type=Тип
runners.description=ОпиÑ
+runners.labels=Мітки
+runners.last_online=ОÑтанній раз онлайн
+runners.task_list.no_tasks=Ðаразі завдань немає.
runners.task_list.run=ЗапуÑтити
+runners.task_list.status=СтатуÑ
runners.task_list.repository=Репозиторій
runners.task_list.commit=Коміт
+runners.task_list.done_at=Завершено о
+runners.update_runner=Оновити зміни
+runners.status.unspecified=Ðевідомий
+runners.status.idle=ОчікуваннÑ
runners.status.active=Ðктивний
+runners.version=ВерÑÑ–Ñ
+runners.reset_registration_token=Скинути реєÑтраційний токен
+runs.all_workflows=Ð’ÑÑ– робочі процеÑи
runs.commit=Коміт
-
-
-
-
+runs.scheduled=Заплановано
+runs.pushed_by=завантажено
+runs.invalid_workflow_helper=Файл конфігурації робочих процеÑів недійÑний. Будь лаÑка, перевірте файл конфігурації: %s
+runs.no_job_without_needs=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ міÑтити принаймні одну задачу без залежноÑтей.
+runs.no_job=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ міÑтити принаймні одну задачу
+runs.actor=Ðктор
+runs.status=СтатуÑ
+runs.actors_no_select=УÑÑ– актори
+runs.status_no_select=Ð’ÑÑ– ÑтатуÑи
+runs.no_results=Збігів немає.
+runs.no_workflows=Робочих процеÑів наразі немає.
+runs.no_workflows.quick_start=Ðе знаєте, Ñк почати з Gitea Дії? ДивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">поÑібник швидкого Ñтарту</a>.
+runs.no_workflows.documentation=Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації про Gitea Дії, переглÑньте <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a>.
+runs.no_runs=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ñ‰Ðµ не виконувавÑÑ.
+runs.empty_commit_message=(порожнє Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ)
+runs.expire_log_message=Журнали були очищені, тому що вони були занадто Ñтарі.
+runs.delete=Видалити запущений робочий процеÑ
+runs.delete.description=Ви впевнені, що хочете оÑтаточно видалити цей робочий процеÑ? Цю дію неможливо ÑкаÑувати.
+runs.not_done=Ð’Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ робочого процеÑу не завершено.
+runs.view_workflow_file=ПереглÑд файлу робочого процеÑу
+
+workflow.disable=Вимкнути робочий процеÑ
+workflow.disable_success=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ '%s' уÑпішно вимкнено.
+workflow.enable=Увімкнути робочий процеÑ
+workflow.enable_success=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ '%s' уÑпішно ввімкнено.
+workflow.disabled=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¸Ð¹.
+workflow.run=ЗапуÑтити робочий процеÑ
+workflow.not_found=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ '%s' не знайдено.
+workflow.run_success=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ '%s' завершивÑÑ ÑƒÑпішно.
+workflow.from_ref=ВикориÑтати робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·
+workflow.has_workflow_dispatch=Цей робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¼Ð°Ñ” тригер події workflow_dispatch.
+workflow.has_no_workflow_dispatch=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ â€œ%s†не має тригера події workflow_dispatch.
+
+
+variables=Змінні
+variables.management=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¸Ð¼Ð¸
+variables.creation=Додати змінну
+variables.none=Ðаразі немає змінних.
+variables.deletion=Видалити змінну
+variables.deletion.description=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¾Ñ— Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване. Продовжити?
+variables.description=Змінні будуть передані певним діÑм Ñ– не можуть бути прочитані інакше.
+variables.id_not_exist=Змінної з ідентифікатором %d не Ñ–Ñнує.
+variables.edit=Редагувати змінну
+variables.deletion.failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ змінну.
+variables.deletion.success=Змінну видалено.
+variables.creation.failed=Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ змінну.
+variables.creation.success=Змінну "%s" додано.
+variables.update.failed=Ðе вдалоÑÑ Ð²Ñ–Ð´Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ñ‚Ð¸ змінну.
+variables.update.success=Змінну відредаговано.
+
+logs.always_auto_scroll=Завжди автоматично прокручувати журнали
+logs.always_expand_running=Завжди розгортати поточні журнали
[projects]
+deleted.display_name=Видалений проєкт
+type-1.display_name=Індивідуальний проєкт
+type-2.display_name=Проєкт Ñховища
+type-3.display_name=Проєкт організації
+enter_fullscreen=Повноекранний режим
+exit_fullscreen=Вийти з повноекранного режиму
[git.filemode]
+changed_filemode=%[1]s → %[2]s
; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", …
+directory=Тека
+normal_file=Звичайний файл
+executable_file=Виконуваний файл
symbolic_link=Символічне поÑиланнÑ
+submodule=Підмодуль
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 0e7db6350c..96a6d518e2 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -4,14 +4,14 @@ explore=探索
help=帮助
logo=徽标
sign_in=登录
-sign_in_with_provider=使用 %s 登录
+sign_in_with_provider=使用「%sã€ç™»å½•
sign_in_or=或
sign_out=退出
sign_up=注册
link_account=链接账户
register=注册
-version=当å‰ç‰ˆæœ¬
-powered_by=Powered by %s
+version=版本
+powered_by=由 %s 强力驱动
page=页é¢
template=模æ¿
language=语言选项
@@ -28,7 +28,7 @@ return_to_gitea=返回 Gitea
more_items=更多选项
username=用户å
-email=电å­é‚®ä»¶åœ°å€
+email=邮箱地å€
password=密ç 
access_token=访问令牌(Access Token)
re_type=确认密ç 
@@ -42,7 +42,7 @@ webauthn_sign_in=按下安全密钥上的按钮。如果安全密钥没有按钮
webauthn_press_button=请按下安全密钥上的按钮…
webauthn_use_twofa=使用æ¥è‡ªæ‰‹æœºä¸­çš„两步验è¯ç 
webauthn_error=无法读å–安全密钥。
-webauthn_unsupported_browser=ä½ çš„æµè§ˆå™¨ç›®å‰ä¸æ”¯æŒ WebAuthn。
+webauthn_unsupported_browser=您的æµè§ˆå™¨ç›®å‰ä¸æ”¯æŒ WebAuthn。
webauthn_error_unknown=å‘生未知错误。请é‡è¯•。
webauthn_error_insecure=WebAuthn 仅支æŒå®‰å…¨è¿žæŽ¥ã€‚如果è¦åœ¨ HTTP å议上进行测试,请使用 "localhost" 或 "127.0.0.1" ä½œä¸ºè®¿é—®æ¥æº
webauthn_error_unable_to_process=æœåŠ¡å™¨æ— æ³•å¤„ç†æ‚¨çš„请求。
@@ -58,11 +58,11 @@ issue_milestone=里程碑
new_repo=创建仓库
new_migrate=è¿ç§»å¤–部仓库
new_mirror=创建新的镜åƒ
-new_fork=新的仓库Fork
+new_fork=派生新仓库
new_org=创建组织
new_project=创建项目
new_project_column=创建列
-manage_org=ç®¡ç†æˆ‘的组织
+manage_org=管ç†ç»„织
admin_panel=管ç†åŽå°
account_settings=叿ˆ·è®¾ç½®
settings=设置
@@ -78,7 +78,7 @@ forks=派生
activities=最近活动
pull_requests=åˆå¹¶è¯·æ±‚
-issues=å·¥å•管ç†
+issues=å·¥å•
milestones=里程碑
ok=确定
@@ -91,7 +91,7 @@ add=添加
add_all=添加所有
remove=移除
remove_all=移除所有
-remove_label_str=`删除标签 "%s"`
+remove_label_str=删除标签「%sã€
edit=编辑
view=查看
test=测试
@@ -117,6 +117,7 @@ files=文件
error=错误
error404=您正å°è¯•è®¿é—®çš„é¡µé¢ <strong>ä¸å­˜åœ¨</strong> 或 <strong>您尚未被授æƒ</strong> 查看该页é¢ã€‚
+error503=æœåŠ¡å™¨æ— æ³•å®Œæˆæ‚¨çš„请求,请ç¨åŽé‡è¯•。
go_back=返回
invalid_data=无效数æ®ï¼š %v
@@ -128,8 +129,9 @@ rss_feed=RSS 订阅æº
pin=固定
unpin=å–æ¶ˆç½®é¡¶
-artifacts=制å“
-confirm_delete_artifact=您确定è¦åˆ é™¤åˆ¶å“'%s'å—?
+artifacts=产物
+expired=已过期
+confirm_delete_artifact=您确定è¦åˆ é™¤äº§ç‰©ã€Œ%sã€å—?
archived=已归档
@@ -163,7 +165,7 @@ filter.public=公开
filter.private=ç§æœ‰
no_results_found=未找到结果
-internal_error_skipped=å‘生内部错误,但已被跳过: %s
+internal_error_skipped=å‘生内部错误,但已跳过: %s
[search]
search=æœç´¢...
@@ -182,14 +184,14 @@ org_kind=æœç´¢ç»„织...
team_kind=æœç´¢å›¢é˜Ÿ...
code_kind=æœç´¢ä»£ç ...
code_search_unavailable=ä»£ç æœç´¢å½“å‰ä¸å¯ç”¨ã€‚请与网站管ç†å‘˜è”系。
-code_search_by_git_grep=当å‰ä»£ç æœç´¢ç»“果由“git grepâ€æä¾›ã€‚å¦‚æžœç«™ç‚¹ç®¡ç†å‘˜å¯ç”¨ä»“库索引器,å¯èƒ½ä¼šæœ‰æ›´å¥½çš„结果。
+code_search_by_git_grep=当å‰ä»£ç æœç´¢ç»“果由「git grepã€æä¾›ã€‚å¦‚æžœç«™ç‚¹ç®¡ç†å‘˜å¯ç”¨ä»“库索引器,å¯èƒ½ä¼šæœ‰æ›´å¥½çš„结果。
package_kind=æœç´¢è½¯ä»¶åŒ…...
project_kind=æœç´¢é¡¹ç›®...
branch_kind=æœç´¢åˆ†æ”¯...
tag_kind=æœç´¢æ ‡ç­¾...
-tag_tooltip=æœç´¢åŒ¹é…的标签。使用“%â€æ¥åŒ¹é…任何åºåˆ—的数字
+tag_tooltip=æœç´¢åŒ¹é…的标签。使用「%ã€æ¥åŒ¹é…任何åºåˆ—的数字。
commit_kind=æœç´¢æäº¤è®°å½•...
-runner_kind=æœç´¢runners...
+runner_kind=æœç´¢è¿è¡Œå™¨...
no_results=未找到匹é…结果
issue_kind=æœç´¢å·¥å•...
pull_kind=æœç´¢åˆå¹¶è¯·æ±‚...
@@ -260,20 +262,20 @@ host=æ•°æ®åº“主机
user=用户å
password=æ•°æ®åº“用户密ç 
db_name=æ•°æ®åº“åç§°
-db_schema=Schema
+db_schema=æž¶æž„
db_schema_helper=留空则数æ®åº“中默认值为("public")。
ssl_mode=SSL
path=æ•°æ®åº“文件路径
sqlite_helper=SQLite3 æ•°æ®åº“的文件路径。<br>如果以æœåŠ¡çš„æ–¹å¼è¿è¡Œ Gitea,请输入ç»å¯¹è·¯å¾„。
reinstall_error=您正在å°è¯•å®‰è£…åˆ°ä¸€ä¸ªå·²ç»æœ‰ Gitea æ•°æ®çš„æ•°æ®åº“中
-reinstall_confirm_message=使用现有的 Gitea æ•°æ®åº“釿–°å®‰è£…å¯èƒ½ä¼šå¯¼è‡´å¤šä¸ªé—®é¢˜ã€‚在大多数情况下,你应该使用你现有的 “app.ini†æ¥è¿è¡Œ Gitea。如果你知é“自己在åšä»€ä¹ˆï¼Œè¯·ç¡®è®¤ä»¥ä¸‹å†…容:
+reinstall_confirm_message=使用现有的 Gitea æ•°æ®åº“釿–°å®‰è£…å¯èƒ½ä¼šå¯¼è‡´å¤šä¸ªé—®é¢˜ã€‚在大多数情况下,您应该使用您现有的「app.iniã€æ¥è¿è¡Œ Gitea。如果您知é“自己在åšä»€ä¹ˆï¼Œè¯·ç¡®è®¤ä»¥ä¸‹å†…容:
reinstall_confirm_check_1=使用 app.ini 中 SECRET KEY 加密的数æ®å¯èƒ½ä¼šä¸¢å¤±ï¼šç”¨æˆ·å¯èƒ½æ— æ³•使用 2FA/OTP 登录,仓库镜åƒå¯èƒ½æ— æ³•æ­£å¸¸å·¥ä½œã€‚å‹¾é€‰æ­¤æ¡†ï¼Œè¡¨ç¤ºæ‚¨ç¡®è®¤å½“å‰ app.ini æ–‡ä»¶åŒ…å«æ­£ç¡®çš„ SECRET KEY。
reinstall_confirm_check_2=代ç ä»“库和设置å¯èƒ½éœ€è¦é‡æ–°åŒæ­¥ã€‚å‹¾é€‰æ­¤æ¡†ï¼Œè¡¨ç¤ºæ‚¨ç¡®è®¤å°†æ‰‹åŠ¨é‡æ–°åŒæ­¥ä»“库和 SSH authorized_keys 的钩å­ã€‚您确认您将确ä¿ä»£ç ä»“库和镜åƒè®¾ç½®æ˜¯æ­£ç¡®çš„。
-reinstall_confirm_check_3=你确认你ç»å¯¹è‚¯å®šè¿™ä¸ª Gitea 在正确的 app.ini ä½ç½®ä¸Šè¿è¡Œï¼Œè€Œä¸”ä½ ç¡®å®šä½ å¿…é¡»é‡æ–°å®‰è£…。你确认你知晓上述风险。
+reinstall_confirm_check_3=您确认您ç»å¯¹è‚¯å®šè¿™ä¸ª Gitea 在正确的 app.ini ä½ç½®ä¸Šè¿è¡Œï¼Œè€Œä¸”æ‚¨ç¡®å®šæ‚¨å¿…é¡»é‡æ–°å®‰è£…。您确认您知晓上述风险。
err_empty_db_path=SQLite æ•°æ®åº“文件路径ä¸èƒ½ä¸ºç©ºã€‚
no_admin_and_disable_registration=您ä¸èƒ½å¤Ÿåœ¨æœªåˆ›å»ºç®¡ç†å‘˜ç”¨æˆ·çš„æƒ…å†µä¸‹ç¦æ­¢æ³¨å†Œã€‚
err_empty_admin_password=管ç†å‘˜å¯†ç ä¸èƒ½ä¸ºç©ºã€‚
-err_empty_admin_email=管ç†å‘˜ç”µå­é‚®ä»¶ä¸èƒ½ä¸ºç©ºã€‚
+err_empty_admin_email=管ç†å‘˜é‚®ç®±ä¸èƒ½ä¸ºç©ºã€‚
err_admin_name_is_reserved=管ç†å‘˜ç”¨æˆ·åæ— æ•ˆï¼Œç”¨æˆ·åæ˜¯ä¿ç•™çš„
err_admin_name_pattern_not_allowed=管ç†å‘˜ç”¨æˆ·åæ— æ•ˆï¼Œç”¨æˆ·åæ˜¯ä¿ç•™å­—
err_admin_name_is_invalid=管ç†å‘˜ç”¨æˆ·å无效
@@ -294,7 +296,7 @@ ssh_port_helper=SSH æœåŠ¡å™¨çš„ç«¯å£å·ï¼Œä¸ºç©ºåˆ™ç¦ç”¨å®ƒã€‚
http_port=HTTP æœåŠ¡ç«¯å£
http_port_helper=Giteas web æœåС噍将侦å¬çš„端å£å·ã€‚
app_url=基础URL
-app_url_helper=用于 HTTP (S) 克隆和电å­é‚®ä»¶é€šçŸ¥çš„基本地å€ã€‚
+app_url_helper=用于 HTTP (S) 克隆和邮件通知的基本地å€ã€‚
log_root_path=日志路径
log_root_path_helper=日志文件将写入此目录。
@@ -302,12 +304,12 @@ optional_title=å¯é€‰è®¾ç½®
email_title=电å­é‚®ç®±è®¾ç½®
smtp_addr=SMTP 主机地å€
smtp_port=SMTP 端å£
-smtp_from=电å­é‚®ä»¶å‘件人
-smtp_from_invalid=`"å‘é€ç”µå­é‚®ä»¶ä¸º"åœ°å€æ— æ•ˆ`
-smtp_from_helper=请输入一个用于 Gitea 的电å­é‚®ä»¶åœ°å€ï¼Œæˆ–者使用完整格å¼ï¼š"åç§°" <email@example.com>
+smtp_from=邮件å‘件人
+smtp_from_invalid=「邮件å‘件人ã€åœ°å€æ— æ•ˆ
+smtp_from_helper=请输入一个用于 Gitea 的邮箱地å€ï¼Œæˆ–者使用完整格å¼ï¼šã€Œåç§°ã€<email@example.com>。
mailer_user=SMTP 用户å
mailer_password=SMTP 密ç 
-register_confirm=需è¦å‘电å­é‚®ä»¶ç¡®è®¤æ³¨å†Œ
+register_confirm=需è¦é‚®ä»¶ç¡®è®¤æ³¨å†Œ
mail_notify=å¯ç”¨é‚®ä»¶é€šçŸ¥æé†’
server_service_title=æœåŠ¡å™¨å’Œç¬¬ä¸‰æ–¹æœåŠ¡è®¾ç½®
offline_mode=å¯ç”¨æœ¬åœ°æ¨¡å¼
@@ -332,12 +334,12 @@ admin_title=管ç†å‘˜å¸å·è®¾ç½®
admin_name=管ç†å‘˜ç”¨æˆ·å
admin_password=管ç†å‘˜å¯†ç 
confirm_password=确认密ç 
-admin_email=电å­é‚®ä»¶åœ°å€
+admin_email=邮箱地å€
install_btn_confirm=ç«‹å³å®‰è£…
test_git_failed=无法识别 'git' 命令:%v
sqlite3_not_available=您所使用的å‘è¡Œç‰ˆä¸æ”¯æŒ SQLite3,请从 %s ä¸‹è½½å®˜æ–¹æž„å»ºç‰ˆï¼Œè€Œä¸æ˜¯ gobuild 版本。
invalid_db_setting=æ•°æ®åº“设置无效: %v
-invalid_db_table=æ•°æ®åº“表 '%s' 无效: %v
+invalid_db_table=æ•°æ®åº“表「%sã€æ— æ•ˆï¼š%v
invalid_repo_path=仓库根目录设置无效:%v
invalid_app_data_path=应用数æ®è·¯å¾„无效: %v
run_user_not_match=è¿è¡Œç”¨æˆ·å䏿˜¯å½“å‰çš„用户å:%s -> %s
@@ -346,14 +348,14 @@ secret_key_failed=生æˆå¯†é’¥å¤±è´¥ï¼š %v
save_config_failed=应用é…ç½®ä¿å­˜å¤±è´¥ï¼š%v
invalid_admin_setting=管ç†å‘˜å¸æˆ·è®¾ç½®æ— æ•ˆ: %v
invalid_log_root_path=日志路径无效: %v
-default_keep_email_private=默认情况下éšè—电å­é‚®ä»¶åœ°å€
-default_keep_email_private_popup=默认情况下, éšè—æ–°ç”¨æˆ·å¸æˆ·çš„电å­é‚®ä»¶åœ°å€ã€‚
+default_keep_email_private=默认情况下éšè—邮箱地å€
+default_keep_email_private_popup=默认情况下,éšè—æ–°ç”¨æˆ·å¸æˆ·çš„邮箱地å€ã€‚
default_allow_create_organization=默认情况下å…许创建组织
default_allow_create_organization_popup=默认情况下, å…è®¸æ–°ç”¨æˆ·å¸æˆ·åˆ›å»ºç»„织。
default_enable_timetracking=默认情况下å¯ç”¨æ—¶é—´è·Ÿè¸ª
default_enable_timetracking_popup=默认情况下å¯ç”¨æ–°ä»“库的时间跟踪。
-no_reply_address=éšè—电å­é‚®ä»¶
-no_reply_address_helper=具有éšè—电å­é‚®ä»¶åœ°å€çš„用户的域å。例如, 用户å "joe" 将以 "joe@noreply.example.org" 的身份登录到 Git 中. 如果éšè—的电å­é‚®ä»¶åŸŸè®¾ç½®ä¸º "noreply.example.org"。
+no_reply_address=éšè—邮件域
+no_reply_address_helper=具有éšè—邮箱地å€çš„用户的域å。例如,如果éšè—邮箱域å设置为「noreply.example.orgã€ï¼Œé‚£ä¹ˆç”¨æˆ·å「joeã€åœ¨ Git 中将显示为「joe@noreply.example.orgã€ã€‚
password_algorithm=密ç å“ˆå¸Œç®—法
invalid_password_algorithm=无效的密ç å“ˆå¸Œç®—法
password_algorithm_helper=è®¾ç½®å¯†ç æ•£åˆ—算法。算法有ä¸åŒçš„è¦æ±‚和强度。 argon2 算法相当安全,但使用大é‡å†…存,因此å¯èƒ½ä¸é€‚åˆå°åž‹ç³»ç»Ÿã€‚
@@ -376,7 +378,7 @@ my_mirrors=我的镜åƒ
view_home=访问 %s
filter=其他过滤器
filter_by_team_repositories=按团队仓库筛选
-feed_of=`"%s"çš„æº`
+feed_of=「%sã€çš„æº
show_archived=已归档
show_both_archived_unarchived=显示已归档和未归档的
@@ -412,43 +414,45 @@ create_new_account=注册å¸å·
already_have_account=已有账å·ï¼Ÿ
sign_in_now=ç«‹å³ç™»å½•
disable_register_prompt=对ä¸èµ·ï¼Œæ³¨å†ŒåŠŸèƒ½å·²è¢«å…³é—­ã€‚è¯·è”系网站管ç†å‘˜ã€‚
-disable_register_mail=å·²ç¦ç”¨æ³¨å†Œçš„电å­é‚®ä»¶ç¡®è®¤ã€‚
+disable_register_mail=å·²ç¦ç”¨æ³¨å†Œé‚®ä»¶ç¡®è®¤ã€‚
manual_activation_only=请è”系您的站点管ç†å‘˜æ¥å®Œæˆæ¿€æ´»ã€‚
remember_me=è®°ä½æ­¤è®¾å¤‡
remember_me.compromised=登录令牌ä¸å†æœ‰æ•ˆï¼Œå› ä¸ºå®ƒå¯èƒ½è¡¨æ˜Žå¸æˆ·å·²è¢«ç ´åã€‚è¯·æ£€æŸ¥æ‚¨çš„å¸æˆ·æ˜¯å¦æœ‰å¼‚常活动。
forgot_password_title=忘记密ç 
forgot_password=忘记密ç ï¼Ÿ
-need_account=需è¦ä¸€ä¸ªå¸æˆ·?
-sign_up_now=还没账å·ï¼Ÿé©¬ä¸Šæ³¨å†Œã€‚
+need_account=需è¦ä¸€ä¸ªå¸æˆ·ï¼Ÿ
+sign_up_tip=æ‚¨æ­£åœ¨ç³»ç»Ÿä¸­æ³¨å†Œç¬¬ä¸€ä¸ªå¸æˆ·ï¼Œå®ƒæ‹¥æœ‰ç®¡ç†å‘˜æƒé™ã€‚è¯·ä»”ç»†è®°ä½æ‚¨çš„用户å和密ç ã€‚ å¦‚æžœæ‚¨å¿˜è®°äº†ç”¨æˆ·åæˆ–密ç ï¼Œè¯·å‚阅 Gitea 文档以æ¢å¤è´¦æˆ·ã€‚
+sign_up_now=ç«‹å³æ³¨å†Œã€‚
sign_up_successful=叿ˆ·åˆ›å»ºæˆåŠŸã€‚æ¬¢è¿Žï¼
-confirmation_mail_sent_prompt_ex=䏀尿–°çš„确认邮件已ç»å‘é€åˆ° <b>%s</b>请在下一个 %s ä¸­æ£€æŸ¥æ‚¨çš„æ”¶ä»¶ç®±ä»¥å®Œæˆæ³¨å†Œè¿‡ç¨‹ã€‚ 如果您的注册电å­é‚®ä»¶åœ°å€ä¸æ­£ç¡®ï¼Œæ‚¨å¯ä»¥é‡æ–°ç™»å½•并更改它。
+confirmation_mail_sent_prompt_ex=䏀尿–°çš„确认邮件已ç»å‘é€åˆ° <b>%s</b>。请在下一个 %s ä¸­æ£€æŸ¥æ‚¨çš„æ”¶ä»¶ç®±ä»¥å®Œæˆæ³¨å†Œæµç¨‹ã€‚ 如果您的注册邮箱地å€ä¸æ­£ç¡®ï¼Œæ‚¨å¯ä»¥é‡æ–°ç™»å½•并更改它。
must_change_password=更新您的密ç 
allow_password_change=è¦æ±‚用户更改密ç ï¼ˆæŽ¨è)
-reset_password_mail_sent_prompt=确认电å­é‚®ä»¶å·²è¢«å‘é€åˆ° <b>%s</b>。请您在 %s 内检查您的收件箱 ,完æˆå¯†ç é‡ç½®è¿‡ç¨‹ã€‚
+reset_password_mail_sent_prompt=确认邮件已被å‘é€åˆ° <b>%s</b>。请您在 %s 内检查您的收件箱 ,完æˆå¯†ç é‡ç½®æµç¨‹ã€‚
active_your_account=æ¿€æ´»æ‚¨çš„å¸æˆ·
account_activated=叿ˆ·å·²æ¿€æ´»
prohibit_login=ç¦æ­¢ç™»å½•
prohibit_login_desc=æ‚¨çš„å¸æˆ·è¢«ç¦æ­¢ç™»å½•,请与网站管ç†å‘˜è”系。
resent_limit_prompt=您请求å‘逿¿€æ´»é‚®ä»¶è¿‡äºŽé¢‘ç¹ï¼Œè¯·ç­‰å¾… 3 分钟åŽå†è¯•ï¼
has_unconfirmed_mail=%s 您好,系统检测到您有一å°å‘é€è‡³ <b>%s</b> 但未被确认的邮件。如果您未收到激活邮件,或需è¦é‡æ–°å‘é€ï¼Œè¯·å•击下方的按钮。
-change_unconfirmed_mail_address=如果您的注册电å­é‚®ä»¶åœ°å€ä¸æ­£ç¡®ï¼Œæ‚¨å¯ä»¥åœ¨æ­¤æ›´æ”¹å¹¶é‡æ–°å‘逿–°çš„确认电å­é‚®ä»¶ã€‚
+change_unconfirmed_mail_address=如果您的注册邮箱地å€ä¸æ­£ç¡®ï¼Œæ‚¨å¯ä»¥åœ¨æ­¤æ›´æ”¹å¹¶é‡æ–°å‘逿–°çš„确认邮件。
resend_mail=å•å‡»æ­¤å¤„é‡æ–°å‘é€ç¡®è®¤é‚®ä»¶
email_not_associate=æ‚¨è¾“å…¥çš„é‚®ç®±åœ°å€æœªè¢«å…³è”到任何å¸å·ï¼
send_reset_mail=å‘é€è´¦æˆ·æ¢å¤é‚®ä»¶
reset_password=账户æ¢å¤
invalid_code=此确认密钥无效或已过期。
-invalid_code_forgot_password=ä½ çš„ç¡®è®¤ç æ— æ•ˆæˆ–者已过期,点击 <a href="%s">这里</a> 开始新的会è¯ã€‚
+invalid_code_forgot_password=æ‚¨çš„ç¡®è®¤ç æ— æ•ˆæˆ–已过期,点击 <a href="%s">这里</a> 开始新的会è¯ã€‚
invalid_password=您的密ç ä¸Žç”¨äºŽåˆ›å»ºè´¦æˆ·çš„密ç ä¸åŒ¹é…。
reset_password_helper=æ¢å¤è´¦æˆ·
reset_password_wrong_user=您以 %s 登录,但æ¢å¤è´¦å·é“¾æŽ¥æ˜¯ç”¨äºŽ %s。
password_too_short=密ç é•¿åº¦ä¸èƒ½å°‘于 %d ä½ã€‚
-non_local_account=éžæœ¬åœ°å¸æˆ·ä¸èƒ½é€šè¿‡ Gitea çš„ web ç•Œé¢æ›´æ”¹å¯†ç ã€‚
+non_local_account=éžæœ¬åœ°å¸æˆ·ä¸èƒ½é€šè¿‡ Gitea çš„ Web ç•Œé¢æ›´æ”¹å¯†ç ã€‚
verify=验è¯
scratch_code=验è¯å£ä»¤
use_scratch_code=使用验è¯å£ä»¤
-twofa_scratch_used=ä½ å·²ç»ä½¿ç”¨äº†ä½ çš„验è¯å£ä»¤ã€‚你将会转到两步验è¯è®¾ç½®é¡µé¢ä»¥ä¾¿ç§»é™¤ä½ çš„æ³¨å†Œè®¾å¤‡æˆ–è€…é‡æ–°ç”Ÿæˆæ–°çš„验è¯å£ä»¤ã€‚
-twofa_passcode_incorrect=你的验è¯ç ä¸æ­£ç¡®ã€‚如果你丢失了你的设备,请使用你的验è¯å£ä»¤ã€‚
-twofa_scratch_token_incorrect=你的验è¯å£ä»¤ä¸æ­£ç¡®ã€‚
+twofa_scratch_used=您已ç»ä½¿ç”¨äº†æ‚¨çš„验è¯å£ä»¤ã€‚您将会转到两步验è¯è®¾ç½®é¡µé¢ä»¥ä¾¿ç§»é™¤æ‚¨çš„æ³¨å†Œè®¾å¤‡æˆ–è€…é‡æ–°ç”Ÿæˆæ–°çš„验è¯å£ä»¤ã€‚
+twofa_passcode_incorrect=您的验è¯ç ä¸æ­£ç¡®ã€‚如果您丢失了您的设备,请使用您的验è¯å£ä»¤ã€‚
+twofa_scratch_token_incorrect=您的验è¯å£ä»¤ä¸æ­£ç¡®ã€‚
+twofa_required=æ‚¨å¿…é¡»è®¾ç½®ä¸¤æ­¥éªŒè¯æ¥è®¿é—®ä»“库,或者å°è¯•釿–°ç™»å½•。
login_userpass=登录
login_openid=OpenID
oauth_signup_tab=注册å¸å·
@@ -460,21 +464,21 @@ oauth_signin_submit=绑定账å·
oauth.signin.error.general=å¤„ç†æŽˆæƒè¯·æ±‚时出错:%s。如果此错误ä»ç„¶å­˜åœ¨ï¼Œè¯·ä¸Žç«™ç‚¹ç®¡ç†å‘˜è”系。
oauth.signin.error.access_denied=授æƒè¯·æ±‚被拒ç»ã€‚
oauth.signin.error.temporarily_unavailable=授æƒå¤±è´¥ï¼Œå› ä¸ºè®¤è¯æœåŠ¡å™¨æš‚æ—¶ä¸å¯ç”¨ã€‚请ç¨åŽå†è¯•。
-oauth_callback_unable_auto_reg=自动注册已å¯ç”¨ï¼Œä½†OAuth2 æä¾›å•† %[1]s 返回缺失的字段:%[2]sï¼Œæ— æ³•è‡ªåŠ¨åˆ›å»ºå¸æˆ·ï¼Œè¯·åˆ›å»ºæˆ–é“¾æŽ¥åˆ°ä¸€ä¸ªå¸æˆ·ï¼Œæˆ–è”系站点管ç†å‘˜ã€‚
+oauth_callback_unable_auto_reg=自动注册已å¯ç”¨ï¼Œä½† OAuth2 æä¾›å•† %[1]s 返回缺失的字段:%[2]sï¼Œæ— æ³•è‡ªåŠ¨åˆ›å»ºå¸æˆ·ï¼Œè¯·åˆ›å»ºæˆ–é“¾æŽ¥åˆ°ä¸€ä¸ªå¸æˆ·ï¼Œæˆ–è”系站点管ç†å‘˜ã€‚
openid_connect_submit=连接
openid_connect_title=è¿žæŽ¥åˆ°çŽ°æœ‰çš„å¸æˆ·
openid_connect_desc=所选的 OpenID URI 未知。在这里关è”ä¸€ä¸ªæ–°å¸æˆ·ã€‚
openid_register_title=åˆ›å»ºæ–°å¸æˆ·
openid_register_desc=所选的 OpenID URI 未知。在这里关è”ä¸€ä¸ªæ–°å¸æˆ·ã€‚
openid_signin_desc=输入您的OpenID地å€ã€‚例如:alice.openid.example.org 或 https://openid.example.org/alice.
-disable_forgot_password_mail=由于未设置电å­é‚®ä»¶ï¼Œå¸æˆ·æ¢å¤è¢«ç¦ç”¨ã€‚ 请è”系您的站点管ç†å‘˜ã€‚
-disable_forgot_password_mail_admin=叿ˆ·æ¢å¤ä»…在设置电å­é‚®ä»¶åŽå¯ç”¨ã€‚ 请设置电å­é‚®ä»¶ä»¥å¯ç”¨å¸æˆ·æ¢å¤ã€‚
-email_domain_blacklisted=您ä¸èƒ½ä½¿ç”¨æ‚¨çš„电å­é‚®ä»¶åœ°å€æ³¨å†Œã€‚
+disable_forgot_password_mail=ç”±äºŽæœªè®¾ç½®é‚®ç®±ï¼Œå¸æˆ·æ¢å¤è¢«ç¦ç”¨ã€‚ 请è”系您的站点管ç†å‘˜ã€‚
+disable_forgot_password_mail_admin=叿ˆ·æ¢å¤ä»…在设置邮箱åŽå¯ç”¨ã€‚ 请设置邮箱以å¯ç”¨å¸æˆ·æ¢å¤ã€‚
+email_domain_blacklisted=您ä¸èƒ½ä½¿ç”¨æ‚¨çš„é‚®ç®±åœ°å€æ³¨å†Œã€‚
authorize_application=应用授æƒ
authorize_redirect_notice=å¦‚æžœæ‚¨æŽˆæƒæ­¤åº”用,您将会被é‡å®šå‘到 %s。
-authorize_application_created_by=此应用由%s创建。
+authorize_application_created_by=此应用由 %s 创建。
authorize_application_description=如果您å…许,它将能够读å–å’Œä¿®æ”¹æ‚¨çš„æ‰€æœ‰å¸æˆ·ä¿¡æ¯ï¼ŒåŒ…括ç§äººä»“库和组织。
-authorize_application_with_scopes=范围: %s
+authorize_application_with_scopes=范围:%s
authorize_title=æŽˆæƒ %s è®¿é—®æ‚¨çš„å¸æˆ·ï¼Ÿ
authorization_failed=授æƒå¤±è´¥
authorization_failed_desc=因为检测到无效请求,授æƒå¤±è´¥ã€‚请å°è¯•è”系您授æƒåº”用的管ç†å‘˜ã€‚
@@ -482,7 +486,7 @@ sspi_auth_failed=SSPI 认è¯å¤±è´¥
password_pwned=此密ç å‡ºçŽ°åœ¨ <a target="_blank" rel="noopener noreferrer" href="%s">被盗密ç </a> 列表上并且曾ç»è¢«å…¬å¼€ã€‚ 请使用å¦ä¸€ä¸ªå¯†ç å†è¯•一次。
password_pwned_err=无法完æˆå¯¹ HaveIBeenPwned 的请求
last_admin=您ä¸èƒ½åˆ é™¤æœ€åŽä¸€ä¸ªç®¡ç†å‘˜ã€‚必须至少ä¿ç•™ä¸€ä¸ªç®¡ç†å‘˜ã€‚
-signin_passkey=使用密钥登录
+signin_passkey=使用通行密钥登录
back_to_sign_in=返回登录页é¢
[mail]
@@ -498,24 +502,24 @@ activate_account.text_2=请在 <b>%s</b> 时间内,点击以下链接激活您
activate_email=è¯·éªŒè¯æ‚¨çš„邮箱地å€
activate_email.title=%sï¼Œè¯·éªŒè¯æ‚¨çš„邮箱
-activate_email.text=请在 <b>%s</b> 时间内,点击以下链接,以验è¯ä½ çš„电å­é‚®ä»¶åœ°å€ï¼š
+activate_email.text=请在 <b>%s</b> æ—¶é—´å†…ï¼Œç‚¹å‡»ä»¥ä¸‹é“¾æŽ¥ï¼Œä»¥éªŒè¯æ‚¨çš„邮箱地å€ï¼š
register_notify=欢迎æ¥åˆ° %s
register_notify.title=%[1]s,欢迎æ¥åˆ° %[2]s
-register_notify.text_1=这是您的 %s 注册确认电å­é‚®ä»¶ ï¼
+register_notify.text_1=这是您的 %s 注册确认邮件 ï¼
register_notify.text_2=您现在å¯ä»¥ä»¥ç”¨æˆ·å %s 登录。
register_notify.text_3=如果此账户已为您创建,请先 <a href="%s">设置您的密ç </a>。
reset_password=æ¢å¤æ‚¨çš„账户
reset_password.title=%s,您已请求æ¢å¤æ‚¨çš„叿ˆ·
-reset_password.text=请在 <b>%s</b> 时间内,点击以下链接,æ¢å¤ä½ çš„账户:
+reset_password.text=请在 <b>%s</b> 时间内,点击以下链接,以æ¢å¤æ‚¨çš„账户:
register_success=注册æˆåŠŸ
issue_assigned.pull=@%[1]s 已将仓库 %[3]s 中的åˆå¹¶è¯·æ±‚ %[2]s 指派给您
issue_assigned.issue=@%[1]s 已将仓库 %[3]s ä¸­çš„å·¥å• %[2]s 指派给您
-issue.x_mentioned_you=<b>@%s</b> æåˆ°äº†æ‚¨ï¼š
+issue.x_mentioned_you=<b>@%s</b> æåŠäº†æ‚¨ï¼š
issue.action.force_push=<b>%[1]s</b> 强制从 %[3]s æŽ¨é€ <b>%[2]s</b> 至 [4]s。
issue.action.push_1=<b>@%[1]s</b> 推é€äº† %[3]d 个æäº¤åˆ° %[2]s
issue.action.push_n=<b>@%[1]s</b> 推é€äº† %[3]d 个æäº¤åˆ° %[2]s
@@ -530,26 +534,26 @@ issue.action.ready_for_review=<b>@%[1]s</b> 标记此åˆå¹¶è¯·æ±‚已评审通过
issue.action.new=<b>@%[1]s</b> 创建了 #%[2]d.
issue.in_tree_path=在 %s 中:
-release.new.subject=%[2]s 中的 %[1]s å‘布了
+release.new.subject=%[2]s 中的 %[1]s å·²å‘布
release.new.text=<b>@%[1]s</b> 于 %[3]s å‘布了 %[2]s
-release.title=标题: %s
+release.title=标题:%s
release.note=注释:
release.downloads=下载:
-release.download.zip=æºä»£ç  (ZIP)
-release.download.targz=æºä»£ç  (TAR.GZ)
+release.download.zip=æºä»£ç ï¼ˆZIP)
+release.download.targz=æºä»£ç ï¼ˆTAR.GZ)
-repo.transfer.subject_to=%s 想è¦å°† "%s" 转让给 %s
-repo.transfer.subject_to_you=%s 想è¦å°† "%s" 转让给你
-repo.transfer.to_you=ä½ 
+repo.transfer.subject_to=%s 想è¦å°†ã€Œ%sã€è½¬ç§»ç»™ %s
+repo.transfer.subject_to_you=%s 想è¦å°†ã€Œ%sã€è½¬ç§»ç»™æ‚¨
+repo.transfer.to_you=您
repo.transfer.body=访问 %s ä»¥æŽ¥å—æˆ–æ‹’ç»è½¬ç§»ï¼Œäº¦å¯å¿½ç•¥æ­¤é‚®ä»¶ã€‚
-repo.collaborator.added.subject=%s 把你添加到了 %s
-repo.collaborator.added.text=您已被添加为代ç åº“çš„å作者:
+repo.collaborator.added.subject=%s 把您添加到了 %s
+repo.collaborator.added.text=您已被添加为仓库的å作者:
team_invite.subject=%[1]s 邀请您加入组织 %[2]s
team_invite.text_1=%[1]s 邀请您加入组织 %[3]s 中的团队 %[2]s。
team_invite.text_2=请点击下é¢çš„链接加入团队:
-team_invite.text_3=注æ„:这是å‘é€ç»™ %[1]s 的邀请。如果您未曾收到过此类邀请,请忽略这å°ç”µå­é‚®ä»¶ã€‚
+team_invite.text_3=注æ„:此邀请是å‘é€ç»™ %[1]s 的。如果您未预期收到此邀请,请忽略这å°é‚®ä»¶ã€‚
[modal]
yes=确认æ“作
@@ -589,9 +593,9 @@ size_error=长度必须为 %s。
min_size_error=长度最å°ä¸º %s 个字符。
max_size_error=长度最大为 %s 个字符。
email_error=䏿˜¯ä¸€ä¸ªæœ‰æ•ˆçš„邮箱地å€ã€‚
-url_error=`'%s' 䏿˜¯ä¸€ä¸ªæœ‰æ•ˆçš„ URL。`
-include_error=`必须包å«å­å­—符串 "%s"。`
-glob_pattern_error=`åŒ¹é…æ¨¡å¼æ— æ•ˆï¼š%s.`
+url_error=`「%sã€ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„ URL。`
+include_error=`必须包å«å­å­—符串「%sã€ã€‚`
+glob_pattern_error=`匹é…è¡¨è¾¾å¼æ— æ•ˆï¼š%s.`
regex_pattern_error=`æ­£åˆ™è¡¨è¾¾å¼æ— æ•ˆï¼š%s.`
username_error=` åªèƒ½åŒ…å«å­—æ¯æ•°å­—字符('0-9','a-z','A-Z'), ç ´æŠ˜å· ('-'), 下划线 ('_') 和点 ('.'). ä¸èƒ½ä»¥éžå­—æ¯æ•°å­—字符开头或结尾,并且ä¸å…许连续的éžå­—æ¯æ•°å­—字符。`
invalid_group_team_map_error=`映射无效: %s`
@@ -600,26 +604,26 @@ captcha_incorrect=验è¯ç ä¸æ­£ç¡®ã€‚
password_not_match=密ç ä¸åŒ¹é…。
lang_select_error=从列表中选出语言
-username_been_taken=用户å已被使用。
+username_been_taken=用户å已使用。
username_change_not_local_user=éžæœ¬åœ°ç”¨æˆ·ä¸å…许更改用户å。
-change_username_disabled=更改用户å已被ç¦ç”¨ã€‚
+change_username_disabled=更改用户åå·²ç¦ç”¨ã€‚
change_full_name_disabled=更改用户全åå·²ç¦ç”¨
username_has_not_been_changed=ç”¨æˆ·åæœªæ›´æ”¹
-repo_name_been_taken=仓库å称已被使用。
-repository_force_private=â€œå¼ºåˆ¶ç§æœ‰â€å·²å¯ç”¨ï¼šç§æœ‰ä»“库ä¸èƒ½è¢«å…¬å¼€ã€‚
+repo_name_been_taken=仓库å称已使用。
+repository_force_private=ã€Œå¼ºåˆ¶ç§æœ‰ã€å·²å¯ç”¨ï¼šç§æœ‰ä»“库ä¸èƒ½è¢«å…¬å¼€ã€‚
repository_files_already_exist=此仓库已存在文件。请è”系系统管ç†å‘˜ã€‚
repository_files_already_exist.adopt=此仓库已存在文件,åªèƒ½è¢«æ”¶å½•。
repository_files_already_exist.delete=此仓库已存在文件,必须先删除他们。
repository_files_already_exist.adopt_or_delete=此仓库已存在文件,è¦ä¹ˆåˆ é™¤ä»–们,è¦ä¹ˆæ”¶å½•他们。
visit_rate_limit=远程访问达到速度é™åˆ¶ã€‚
2fa_auth_required=远程访问需è¦åŒé‡éªŒè¯ã€‚
-org_name_been_taken=组织å称已被使用。
-team_name_been_taken=团队å称已被使用。
+org_name_been_taken=组织å称已使用。
+team_name_been_taken=团队å称已使用。
team_no_units_error=至少选择一项仓库å•元。
-email_been_used=该电å­é‚®ä»¶åœ°å€å·²åœ¨ä½¿ç”¨ä¸­ã€‚
+email_been_used=该邮箱地å€å·²åœ¨ä½¿ç”¨ä¸­ã€‚
email_invalid=æ­¤é‚®ç®±åœ°å€æ— æ•ˆã€‚
email_domain_is_not_allowed=用户 <b>%s</b> 与EMAIL_DOMAIN_ALLOWLIT 或 EMAIL_DOMAIN_BLOCKLIT 冲çªã€‚è¯·ç¡®ä¿æ‚¨çš„æ“ä½œæ˜¯é¢„æœŸçš„ã€‚
-openid_been_used=OpenID åœ°å€ "%s" 已被使用。
+openid_been_used=OpenID 地å€ã€Œ%sã€å·²è¢«ä½¿ç”¨ã€‚
username_password_incorrect=ç”¨æˆ·åæˆ–密ç ä¸æ­£ç¡®ã€‚
password_complexity=å¯†ç æœªè¾¾åˆ°å¤æ‚ç¨‹åº¦è¦æ±‚:
password_lowercase_one=至少一个å°å†™å­—符
@@ -634,14 +638,14 @@ unset_password=登录用户没有设置密ç ã€‚
unsupported_login_type=æ­¤ç™»å½•ç±»åž‹ä¸æ”¯æŒæ‰‹åŠ¨åˆ é™¤å¸æˆ·ã€‚
user_not_exist=该用户ä¸å­˜åœ¨
team_not_exist=团队ä¸å­˜åœ¨
-last_org_owner=您ä¸èƒ½ä»Ž "所有者" 团队中删除最åŽä¸€ä¸ªç”¨æˆ·ã€‚组织中必须至少有一个所有者。
-cannot_add_org_to_team=组织ä¸èƒ½è¢«åŠ å…¥åˆ°å›¢é˜Ÿä¸­ã€‚
+last_org_owner=您ä¸èƒ½ä»Žã€Œæ‰€æœ‰è€…ã€å›¢é˜Ÿä¸­åˆ é™¤æœ€åŽä¸€ä¸ªç”¨æˆ·ã€‚组织中必须至少有一个所有者。
+cannot_add_org_to_team=组织ä¸èƒ½åŠ å…¥åˆ°å›¢é˜Ÿä¸­ã€‚
duplicate_invite_to_team=此用户已被邀请为团队æˆå‘˜ã€‚
organization_leave_success=您已æˆåŠŸç¦»å¼€ç»„ç»‡ %s。
-invalid_ssh_key=æ— æ³•éªŒè¯æ‚¨çš„ SSH 密钥: %s
-invalid_gpg_key=æ— æ³•éªŒè¯æ‚¨çš„ GPG 密钥: %s
-invalid_ssh_principal=无效的规则: %s
+invalid_ssh_key=æ— æ³•éªŒè¯æ‚¨çš„ SSH 密钥:%s
+invalid_gpg_key=æ— æ³•éªŒè¯æ‚¨çš„ GPG 密钥:%s
+invalid_ssh_principal=无效的规则:%s
must_use_public_key=您æä¾›çš„密钥是ç§é’¥ã€‚ä¸è¦åœ¨ä»»ä½•地方上传您的ç§é’¥ï¼Œè¯·æ”¹ç”¨æ‚¨çš„公钥。
unable_verify_ssh_key=æ— æ³•éªŒè¯ SSH å¯†é’¥ï¼Œè¯·ä»”ç»†æ£€æŸ¥æ˜¯å¦æœ‰é”™è¯¯ã€‚
auth_failed=授æƒéªŒè¯å¤±è´¥ï¼š%v
@@ -674,27 +678,27 @@ follow=关注
unfollow=å–æ¶ˆå…³æ³¨
user_bio=简历
disabled_public_activity=该用户已éšè—活动记录。
-email_visibility.limited=所有已认è¯ç”¨æˆ·å‡å¯çœ‹åˆ°æ‚¨çš„电å­é‚®ä»¶åœ°å€
-email_visibility.private=åªæœ‰ä½ æœ¬äººå’Œç®¡ç†å‘˜å¯ä»¥çœ‹åˆ°ä½ çš„电å­é‚®ä»¶åœ°å€
+email_visibility.limited=所有已认è¯ç”¨æˆ·å‡å¯çœ‹åˆ°æ‚¨çš„邮箱地å€
+email_visibility.private=åªæœ‰æ‚¨æœ¬äººå’Œç®¡ç†å‘˜å¯ä»¥çœ‹åˆ°æ‚¨çš„邮箱地å€
show_on_map=在地图上显示这个ä½ç½®
settings=用户设置
-form.name_reserved=用户å "%s" 被ä¿ç•™ã€‚
-form.name_pattern_not_allowed=用户å中ä¸å…许使用 "%s" æ ¼å¼ã€‚
-form.name_chars_not_allowed=用户å "%s" åŒ…å«æ— æ•ˆå­—符。
+form.name_reserved=用户å「%sã€è¢«ä¿ç•™ã€‚
+form.name_pattern_not_allowed=用户å中ä¸å…许使用「%sã€æ ¼å¼ã€‚
+form.name_chars_not_allowed=用户å「%sã€åŒ…嫿— æ•ˆå­—符。
block.block=å±è”½
block.block.user=å±è”½ç”¨æˆ·
block.block.org=å±è”½ç”¨æˆ·è®¿é—®ç»„织
-block.block.failure=å±è”½ç”¨æˆ·å¤±è´¥: %s
+block.block.failure=å±è”½ç”¨æˆ·å¤±è´¥ï¼š%s
block.unblock=å–æ¶ˆå±è”½
-block.unblock.failure=å±è”½ç”¨æˆ·å¤±è´¥: %s
+block.unblock.failure=å–æ¶ˆå±è”½ç”¨æˆ·å¤±è´¥ï¼š%s
block.blocked=您已å±è”½æ­¤ç”¨æˆ·ã€‚
block.title=å±è”½ä¸€ä¸ªç”¨æˆ·
block.info=å±è”½ç”¨æˆ·ä¼šé˜»æ­¢ä»–们与仓库进行交互,例如打开或评论åˆå¹¶è¯·æ±‚或出现问题。了解更多关于å±è”½ç”¨æˆ·çš„ä¿¡æ¯ã€‚
block.info_1=é˜»æ­¢ç”¨æˆ·åœ¨æ‚¨çš„å¸æˆ·å’Œä»“库中进行以下æ“作:
-block.info_2=关注你的账å·
-block.info_3=通过@æåŠæ‚¨çš„用户å呿‚¨å‘é€é€šçŸ¥
+block.info_2=关注您的账å·
+block.info_3=通过 @ æåŠæ‚¨çš„用户å呿‚¨å‘é€é€šçŸ¥
block.info_4=邀请您作为å作者到他们的仓库中
block.info_5=åœ¨ä»“åº“ä¸­ç‚¹èµžã€æ´¾ç”Ÿæˆ–关注
block.info_6=æ‰“å¼€å’Œè¯„è®ºå·¥å•æˆ–åˆå¹¶è¯·æ±‚
@@ -727,18 +731,18 @@ uid=UID
webauthn=两步验è¯ï¼ˆå®‰å…¨å¯†é’¥ï¼‰
public_profile=公开信æ¯
-biography_placeholder=å‘Šè¯‰æˆ‘ä»¬ä¸€ç‚¹æ‚¨è‡ªå·±ï¼ (您å¯ä»¥ä½¿ç”¨Markdown)
-location_placeholder=与他人分享你的大概ä½ç½®
-profile_desc=控制您的个人资料对其他用户的显示方å¼ã€‚您的主è¦ç”µå­é‚®ä»¶åœ°å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤å’ŒåŸºäºŽç½‘页界é¢çš„ Git æ“作
-password_username_disabled=您ä¸è¢«å…许更改你的用户å。更多详情请è”系您的系统管ç†å‘˜ã€‚
-password_full_name_disabled=您ä¸è¢«å…许更改你的全å。请è”系您的站点管ç†å‘˜äº†è§£æ›´å¤šè¯¦æƒ…。
+biography_placeholder=å‘Šè¯‰æˆ‘ä»¬ä¸€ç‚¹æ‚¨è‡ªå·±ï¼ (您å¯ä»¥ä½¿ç”¨ Markdown)
+location_placeholder=与他人分享您的大概ä½ç½®
+profile_desc=控制您的个人资料对其他用户的显示方å¼ã€‚您的主邮箱地å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤å’ŒåŸºäºŽç½‘页的 Git æ“作。
+password_username_disabled=您ä¸è¢«å…许更改您的用户å。更多详情请è”系您的系统管ç†å‘˜ã€‚
+password_full_name_disabled=您ä¸è¢«å…许更改您的全å。请è”系您的站点管ç†å‘˜äº†è§£æ›´å¤šè¯¦æƒ…。
full_name=自定义åç§°
website=个人网站
location=所在地区
update_theme=更新主题
update_profile=æ›´æ–°ä¿¡æ¯
update_language=更新语言
-update_language_not_found=语言 %s ä¸å¯ç”¨ã€‚
+update_language_not_found=语言「%sã€ä¸å¯ç”¨ã€‚
update_language_success=语言已更新。
update_profile_success=您的资料信æ¯å·²ç»æ›´æ–°
change_username=您的用户å已更改。
@@ -749,7 +753,7 @@ cancel=å–æ¶ˆæ“作
language=界é¢è¯­è¨€
ui=主题
hidden_comment_types=éšè—的评论类型
-hidden_comment_types_description=此处选中的注释类型ä¸ä¼šæ˜¾ç¤ºåœ¨é—®é¢˜é¡µé¢ä¸­ã€‚æ¯”å¦‚ï¼Œå‹¾é€‰â€æ ‡ç­¾â€œåˆ é™¤æ‰€æœ‰ "<user> 添加/删除的 <label>" 注释。
+hidden_comment_types_description=此处选中的注释类型ä¸ä¼šæ˜¾ç¤ºåœ¨å·¥å•页é¢ä¸­ã€‚比如,勾选「标记ã€åˆ é™¤æ‰€æœ‰ã€Œ{user} 添加/删除的 {label}ã€æ³¨é‡Šã€‚
hidden_comment_types.ref_tooltip=注释此问题在何处被æåŠè¿‡ï¼Œå¦‚å¦ä¸€ä¸ªé—®é¢˜ã€ä»£ç æäº¤ç­‰
hidden_comment_types.issue_ref_tooltip=注释用户在何处更改了与此问题相关è”的分支/标签
comment_type_group_reference=引用
@@ -771,7 +775,7 @@ privacy=éšç§è®¾ç½®
keep_activity_private=éšè—个人资料页é¢ä¸­çš„æ´»åЍ
keep_activity_private_popup=使活动仅对您和管ç†å‘˜å¯è§
-lookup_avatar_by_mail=从电å­é‚®ç®±åœ°å€æŸ¥æ‰¾å¤´åƒ
+lookup_avatar_by_mail=ä»Žé‚®ç®±åœ°å€æŸ¥æ‰¾å¤´åƒ
federated_avatar_lookup=Federated Avatar 查找
enable_custom_avatar=å¯åŠ¨è‡ªå®šä¹‰å¤´åƒ
choose_new_avatar=选择新的头åƒ
@@ -795,61 +799,61 @@ emails=邮箱地å€
manage_emails=管ç†é‚®ç®±åœ°å€
manage_themes=选择默认主题
manage_openid=ç®¡ç† OpenID 地å€
-email_desc=您的主è¦ç”µå­é‚®ä»¶åœ°å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤ï¼ŒåŸºäºŽç½‘页界é¢çš„Gitæ“作(åªè¦å®ƒä¸æ˜¯è®¾ç½®ä¸ºéšè—çš„)。
+email_desc=您的主邮箱地å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤ä»¥åŠåŸºäºŽç½‘页的 Git æ“作(如果它未设为éšè—)。
theme_desc=这将是您在整个网站上的默认主题。
theme_colorblindness_help=颜色障ç¢ä¸»é¢˜æ”¯æŒ
-theme_colorblindness_prompt=Gitea åªèƒ½èŽ·å¾—ä¸€äº›åŸºæœ¬çš„é¢œè‰²éšœç¢æ”¯æŒï¼Œè¿™äº›ä¸»é¢˜åªå®šä¹‰äº†å°‘数颜色。 这项工作ä»åœ¨è¿›è¡Œä¸­ï¼Œå¯ä»¥é€šè¿‡åœ¨ä¸»é¢˜çš„ CSS 文件中定义更多颜色æ¥åšæ›´å¤šçš„æ”¹è¿›ã€‚
+theme_colorblindness_prompt=Gitea åªèƒ½èŽ·å¾—ä¸€äº›åŸºæœ¬çš„é¢œè‰²éšœç¢æ”¯æŒï¼Œè¿™äº›ä¸»é¢˜åªå®šä¹‰äº†å°‘数颜色。这项工作ä»åœ¨è¿›è¡Œä¸­ï¼Œå¯ä»¥é€šè¿‡åœ¨ä¸»é¢˜çš„ CSS 文件中定义更多颜色æ¥åšæ›´å¤šçš„æ”¹è¿›ã€‚
primary=主è¦
activated=已激活
requires_activation=éœ€è¦æ¿€æ´»
-primary_email=设为主è¦é‚®ä»¶åœ°å€
+primary_email=设为主邮箱
activate_email=å‘逿¿€æ´»é‚®ä»¶
activations_pending=等待激活
-can_not_add_email_activations_pending=有一个待处ç†çš„æ¿€æ´»è¯·æ±‚,请ç¨ç­‰å‡ åˆ†é’ŸåŽå†å°è¯•添加新的电å­é‚®ä»¶åœ°å€ã€‚
+can_not_add_email_activations_pending=有一个待处ç†çš„æ¿€æ´»è¯·æ±‚,请ç¨ç­‰å‡ åˆ†é’ŸåŽå†å°è¯•添加新的邮箱地å€ã€‚
delete_email=移除
-email_deletion=移除电å­é‚®ä»¶åœ°å€
-email_deletion_desc=电å­é‚®ç®±åœ°å€å’Œç›¸å…³ä¿¡æ¯å°†ä¼šè¢«åˆ é™¤ã€‚使用此电å­é‚®ç®±åœ°å€å‘é€çš„Gitæäº¤å°†ä¼šä¿ç•™ï¼Œç»§ç»­ï¼Ÿ
-email_deletion_success=您的电å­é‚®ç®±åœ°å€å·²è¢«ç§»é™¤ã€‚
+email_deletion=移除邮箱地å€
+email_deletion_desc=邮箱地å€å’Œç›¸å…³ä¿¡æ¯å°†ä¼šè¢«åˆ é™¤ã€‚使用此邮箱地å€å‘é€çš„Gitæäº¤å°†ä¼šä¿ç•™ï¼Œç»§ç»­ï¼Ÿ
+email_deletion_success=您的邮箱地å€å·²ç§»é™¤ã€‚
theme_update_success=您的主题已更新。
theme_update_error=所选主题ä¸å­˜åœ¨ã€‚
openid_deletion=移除 OpenID 地å€
-openid_deletion_desc=删除此 OpenID 地å€å°†ä¼šé˜»æ­¢ä½ ä½¿ç”¨å®ƒè¿›è¡Œç™»å½•。你确定è¦ç»§ç»­å—?
-openid_deletion_success=OpenID地å€å·²è¢«ç§»é™¤ã€‚
+openid_deletion_desc=删除此 OpenID 地å€å°†ä¼šé˜»æ­¢æ‚¨ä½¿ç”¨å®ƒè¿›è¡Œç™»å½•。您确定è¦ç»§ç»­å—?
+openid_deletion_success=OpenID 地å€å·²ç§»é™¤ã€‚
add_new_email=添加新的邮箱地å€
add_new_openid=添加新的 OpenID URI
-add_email=增加电å­é‚®ä»¶åœ°å€
+add_email=新增邮箱地å€
add_openid=添加 OpenID URI
-add_email_confirmation_sent=一å°ç¡®è®¤é‚®ä»¶å·²ç»è¢«å‘é€è‡³ %s,请检查您的收件箱并在 %s 内完æˆç¡®è®¤æ³¨å†Œæ“作。
-add_email_success=新的电å­é‚®ä»¶åœ°å€å·²æ·»åŠ ã€‚
-email_preference_set_success=电å­é‚®ä»¶é¦–选项已æˆåŠŸè®¾ç½®ã€‚
+add_email_confirmation_sent=一å°ç¡®è®¤é‚®ä»¶å·²ç»å‘é€è‡³ã€Œ%sã€ï¼Œè¯·æ£€æŸ¥æ‚¨çš„æ”¶ä»¶ç®±å¹¶åœ¨ %s 内完æˆç¡®è®¤æ³¨å†Œæ“作。
+add_email_success=新邮箱地å€å·²æ·»åŠ ã€‚
+email_preference_set_success=邮件首选项已æˆåŠŸè®¾ç½®ã€‚
add_openid_success=æ–°çš„ OpenID 地å€å·²æ·»åŠ ã€‚
-keep_email_private=éšè—电å­é‚®ä»¶åœ°å€
-keep_email_private_popup=这将会éšè—您的电å­é‚®ä»¶åœ°å€ï¼Œä¸ä»…在您的个人资料中,还在您使用Web界é¢åˆ›å»ºåˆå¹¶è¯·æ±‚或编辑文件时。已推é€çš„æäº¤å°†ä¸ä¼šè¢«ä¿®æ”¹ã€‚在æäº¤ä¸­ä½¿ç”¨ %s 以和您的账å·å…³è”。
-openid_desc=OpenID 让你å¯ä»¥å°†è®¤è¯è½¬å‘到外部æœåŠ¡ã€‚
+keep_email_private=éšè—邮箱地å€
+keep_email_private_popup=这将会éšè—您的邮箱地å€ï¼Œä¸ä»…在您的个人资料中,还在您使用 Web 界é¢åˆ›å»ºåˆå¹¶è¯·æ±‚或编辑文件时。已推é€çš„æäº¤å°†ä¸ä¼šè¢«ä¿®æ”¹ã€‚在æäº¤ä¸­ä½¿ç”¨ %s 以和您的账å·å…³è”。
+openid_desc=OpenID 让您å¯ä»¥å°†è®¤è¯è½¬å‘到外部æœåŠ¡ã€‚
manage_ssh_keys=ç®¡ç† SSH 密钥
-manage_ssh_principals=管ç†SSHè¯ä¹¦è§„则
+manage_ssh_principals=ç®¡ç† SSH è¯ä¹¦è§„则
manage_gpg_keys=ç®¡ç† GPG 密钥
add_key=增加密钥
-ssh_desc=这些 SSH 公钥已ç»å…³è”到你的账å·ã€‚相应的ç§é’¥æ‹¥æœ‰å®Œå…¨æ“作你的仓库的æƒé™ã€‚
-principal_desc=这些SSHè¯ä¹¦è§„则已关è”到你的账å·å°†å…许完全访问你的所有仓库。
-gpg_desc=这些 GPG 公钥已ç»å…³è”到你的账å·ã€‚请妥善ä¿ç®¡ä½ çš„ç§é’¥å› ä¸ºä»–ä»¬å°†è¢«ç”¨äºŽè®¤è¯æäº¤ã€‚
+ssh_desc=这些 SSH 公钥已ç»å…³è”到您的账å·ã€‚相应的ç§é’¥æ‹¥æœ‰å®Œå…¨æ“作您仓库的æƒé™ã€‚
+principal_desc=这些 SSH è¯ä¹¦è§„则已关è”到您的账å·å°†å…许完全访问您所有仓库。
+gpg_desc=这些 GPG 公钥已ç»å…³è”到您的账å·ã€‚请妥善ä¿ç®¡æ‚¨çš„ç§é’¥å› ä¸ºä»–ä»¬å°†è¢«ç”¨äºŽè®¤è¯æäº¤ã€‚
ssh_helper=<strong>需è¦å¸®åŠ©ï¼Ÿ</strong> 请查看有关 <a href="%s">å¦‚ä½•ç”Ÿæˆ SSH 密钥</a> 或 <a href="%s">å¸¸è§ SSH 问题</a> 寻找答案。
-gpg_helper=<strong>需è¦å¸®åŠ©å—?</strong>看一看 GitHub <a href="%s">关于GPG</a> 的指导。
+gpg_helper=<strong>需è¦å¸®åŠ©ï¼Ÿ</strong>看一看 GitHub <a href="%s">关于 GPG</a> 的指导。
add_new_key=增加 SSH 密钥
add_new_gpg_key=添加的 GPG 密钥
key_content_ssh_placeholder=以 'ssh-ed25519'〠'ssh-rsa'〠'ecdsa-sha2-nistp256'ã€'ecdsa-sha2-nistp384'ã€'ecdsa-sha2-nistp521'〠'sk-ecdsa-sha2-nistp256@openssh.com' 或 'sk-ssh-ed25519@openssh.com' 开头
key_content_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 开头
add_new_principal=添加规则
ssh_key_been_used=æ­¤ SSH 密钥已添加到æœåŠ¡å™¨ã€‚
-ssh_key_name_used=使用相åŒåç§°çš„SSH公钥已ç»å­˜åœ¨ï¼
+ssh_key_name_used=使用相åŒåç§°çš„ SSH 公钥已ç»å­˜åœ¨ã€‚
ssh_principal_been_used=此规则已ç»åŠ å…¥åˆ°äº†æœåŠ¡å™¨ã€‚
-gpg_key_id_used=使用相åŒåç§°çš„GPG公钥已ç»å­˜åœ¨ï¼
-gpg_no_key_email_found=æ­¤ GPG å¯†é’¥ä¸Žæ‚¨å¸æˆ·å…³è”的任何已激活电å­é‚®ä»¶åœ°å€å‡ä¸åŒ¹é…。如果您在æä¾›çš„令牌上签å,它ä»ç„¶å¯ä»¥è¢«æ·»åŠ ã€‚
+gpg_key_id_used=使用相åŒåç§°çš„ GPG 公钥已ç»å­˜åœ¨ã€‚
+gpg_no_key_email_found=æ­¤ GPG å¯†é’¥ä¸Žæ‚¨å¸æˆ·å…³è”的任何已激活邮箱地å€å‡ä¸åŒ¹é…。如果您已对æä¾›çš„令牌进行签å,ä»å¯æ·»åŠ è¯¥å¯†é’¥ã€‚
gpg_key_matched_identities=匹é…的身份:
-gpg_key_matched_identities_long=此密钥中包å«çš„身份信æ¯ä¸Žä¸‹é¢è¿™ä¸ªè¯¥ç”¨æˆ·å·²æ¿€æ´»ç”µå­é‚®ä»¶åœ°å€æ˜¯ç›¸åŒ¹é…的。因此,能与这些电å­é‚®ä»¶åœ°å€ç›¸åŒ¹é…çš„æäº¤å¯ä»¥é€šè¿‡æ­¤å¯†é’¥è¿›è¡ŒéªŒè¯ã€‚
+gpg_key_matched_identities_long=此密钥中包å«çš„身份信æ¯ä¸Žä»¥ä¸‹æ­¤ç”¨æˆ·å·²æ¿€æ´»é‚®ç®±åœ°å€åŒ¹é…。与这些邮箱地å€ç›¸åŒ¹é…çš„æäº¤å¯é€šè¿‡æ­¤å¯†é’¥è¿›è¡ŒéªŒè¯ã€‚
gpg_key_verified=已验è¯çš„密钥
-gpg_key_verified_long=密钥已ç»ç”¨ä»¤ç‰Œè¿›è¡Œäº†éªŒè¯ï¼Œå¹¶ä¸”å¯ä»¥ç”¨æ¥éªŒè¯åŒ¹é…此用户任何已激活电å­é‚®ä»¶åœ°å€çš„æäº¤ï¼Œä»¥åŠåŒ¹é…此密钥的任何身份。
+gpg_key_verified_long=密钥已通过令牌验è¯ï¼Œé™¤ä¸Žæ­¤å¯†é’¥åŒ¹é…的任何身份外,还å¯ç”¨äºŽéªŒè¯ä¸Žè¯¥ç”¨æˆ·ä»»ä½•已激活邮箱地å€åŒ¹é…çš„æäº¤ã€‚
gpg_key_verify=验è¯
gpg_invalid_token_signature=æä¾›çš„ GPG 密钥ã€ç­¾å和令牌ä¸åŒ¹é…或过期。
gpg_token_required=您必须为下é¢çš„令牌æä¾›ç­¾å
@@ -857,9 +861,9 @@ gpg_token=令牌
gpg_token_help=您å¯ä»¥ä½¿ç”¨ä»¥ä¸‹æ–¹å¼ç”Ÿæˆç­¾å:
gpg_token_signature=GPG 增强签å
key_signature_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 开头
-verify_gpg_key_success=GPG 密钥 %s 已被验è¯ã€‚
+verify_gpg_key_success=GPG 密钥「%sã€å·²éªŒè¯ã€‚
ssh_key_verified=已验è¯çš„密钥
-ssh_key_verified_long=密钥已ç»ç”¨ä»¤ç‰Œè¿›è¡Œäº†éªŒè¯ï¼Œå¹¶ä¸”å¯ä»¥ç”¨æ¥éªŒè¯åŒ¹é…此用户任何已激活电å­é‚®ä»¶åœ°å€çš„æäº¤ã€‚
+ssh_key_verified_long=密钥已通过令牌验è¯ï¼Œå¯ç”¨äºŽéªŒè¯ä¸Žè¯¥ç”¨æˆ·ä»»ä½•已激活邮箱地å€åŒ¹é…çš„æäº¤ã€‚
ssh_key_verify=验è¯
ssh_invalid_token_signature=æä¾›çš„ SSH 密钥ã€ç­¾å或令牌ä¸åŒ¹é…或令牌已过期。
ssh_token_required=您必须为下é¢çš„令牌æä¾›ç­¾å
@@ -867,24 +871,24 @@ ssh_token=令牌
ssh_token_help=您å¯ä»¥ä½¿ç”¨ä»¥ä¸‹æ–¹å¼ç”Ÿæˆç­¾å:
ssh_token_signature=增强 SSH ç­¾å
key_signature_ssh_placeholder=以 '-----BEGIN SSH SIGNATURE -----' 开头
-verify_ssh_key_success=SSH 密钥 %s 已被验è¯ã€‚
+verify_ssh_key_success=SSH 密钥「%sã€å·²éªŒè¯ã€‚
subkeys=å­é¡¹
-key_id=é”®ID
+key_id=密钥 ID
key_name=密钥åç§°
key_content=密钥内容
principal_content=内容
-add_key_success=SSH 密钥 %s 添加æˆåŠŸã€‚
-add_gpg_key_success=GPG 密钥 %s 添加æˆåŠŸã€‚
-add_principal_success=SSHè¯ä¹¦è§„则 %s 添加æˆåŠŸã€‚
+add_key_success=SSH 密钥「%sã€æ·»åŠ æˆåŠŸã€‚
+add_gpg_key_success=GPG 密钥「%sã€æ·»åŠ æˆåŠŸã€‚
+add_principal_success=SSH è¯ä¹¦è§„则「%sã€æ·»åŠ æˆåŠŸã€‚
delete_key=删除
ssh_key_deletion=删除 SSH 密钥
gpg_key_deletion=删除 GPG 密钥
ssh_principal_deletion=删除 SSH è¯ä¹¦è§„则
ssh_key_deletion_desc=删除 SSH 公钥将喿¶ˆå¯¹åº”çš„ç§é’¥å¯¹æ‚¨çš„ Gitea 叿ˆ·çš„访问æƒé™ã€‚继续?
-gpg_key_deletion_desc=删除 GPG 公钥将无法认知使用对应ç§é’¥ç­¾åçš„æäº¤ï¼Œç»§ç»­ï¼Ÿ
+gpg_key_deletion_desc=删除 GPG 公钥将无法认è¯å¯¹åº”ç§é’¥ç­¾åçš„æäº¤ï¼Œç»§ç»­ï¼Ÿ
ssh_principal_deletion_desc=删除此 SSH è¯ä¹¦è§„åˆ™å°†å–æ¶ˆå®ƒå¯¹æ‚¨çš„账户的访问æƒé™ã€‚继续?
-ssh_key_deletion_success=GPG 密钥已被删除。
-gpg_key_deletion_success=GPG 密钥已被删除。
+ssh_key_deletion_success=SSH 密钥已删除。
+gpg_key_deletion_success=GPG 密钥已删除。
ssh_principal_deletion_success=此规则删除æˆåŠŸã€‚
added_on=添加于 %s
valid_until_date=有效期至 %s
@@ -898,7 +902,7 @@ token_state_desc=7 天内使用过该密钥
principal_state_desc=7 天内使用过该规则
show_openid=在个人信æ¯ä¸Šæ˜¾ç¤º
hide_openid=在个人信æ¯ä¸Šéšè—
-ssh_disabled=SSH 被ç¦ç”¨
+ssh_disabled=SSH å·²ç¦ç”¨
ssh_signonly=SSH ç›®å‰å·²ç¦ç”¨ï¼Œå› æ­¤è¿™äº›å¯†é’¥ä»…用于æäº¤ç­¾å验è¯ã€‚
ssh_externally_managed=æ­¤ SSH 密钥是由外部管ç†çš„
manage_social=管ç†å…³è”ç¤¾äº¤å¸æˆ·
@@ -918,10 +922,10 @@ access_token_deletion=删除 Access Token
access_token_deletion_cancel_action=å–æ¶ˆ
access_token_deletion_confirm_action=刪除
access_token_deletion_desc=删除令牌将撤销程åºå¯¹æ‚¨è´¦æˆ·çš„访问æƒé™ã€‚æ­¤æ“作无法撤消。是å¦ç»§ç»­ï¼Ÿ
-delete_token_success=令牌已ç»è¢«åˆ é™¤ã€‚使用该令牌的应用将ä¸å†èƒ½å¤Ÿè®¿é—®ä½ çš„è´¦å·ã€‚
+delete_token_success=令牌已ç»è¢«åˆ é™¤ã€‚使用该令牌的应用将ä¸å†èƒ½å¤Ÿè®¿é—®æ‚¨çš„è´¦å·ã€‚
repo_and_org_access=仓库和组织访问æƒé™
permissions_public_only=仅公开
-permissions_access_all=全部(公开ã€ç§æœ‰å’Œå—é™)
+permissions_access_all=全部(公开ã€ç§æœ‰å’Œå—é™ï¼‰
permission_not_set=未设置
permission_no_access=无访问æƒé™
permission_read=å¯è¯»
@@ -930,7 +934,7 @@ permission_anonymous_read=匿å读
permission_everyone_read=所有人å¯è¯»
permission_everyone_write=所有人å¯å†™
access_token_desc=所选令牌æƒé™ä»…é™äºŽå¯¹åº”çš„ <a %s>API</a> 路由的授æƒã€‚阅读 <a %s>文档</a> ä»¥èŽ·å–æ›´å¤šä¿¡æ¯ã€‚
-at_least_one_permission=你需è¦é€‰æ‹©è‡³å°‘一个æƒé™æ‰èƒ½åˆ›å»ºä»¤ç‰Œ
+at_least_one_permission=您需è¦é€‰æ‹©è‡³å°‘一个æƒé™æ‰èƒ½åˆ›å»ºä»¤ç‰Œ
permissions_list=æƒé™ï¼š
manage_oauth2_applications=ç®¡ç† OAuth2 应用程åº
@@ -938,13 +942,13 @@ edit_oauth2_application=编辑 OAuth2 应用程åº
oauth2_applications_desc=OAuth2 应用å…许第三方应用程åºåœ¨æ­¤ Gitea 实例中安全验è¯ç”¨æˆ·ã€‚
remove_oauth2_application=删除 OAuth2 应用程åº
remove_oauth2_application_desc=删除 OAuth2 应用将撤销所有签å的访问令牌。继续å—?
-remove_oauth2_application_success=该应用已被删除。
+remove_oauth2_application_success=该应用已删除。
create_oauth2_application=创建新的 OAuth2 应用程åº
create_oauth2_application_button=创建应用
create_oauth2_application_success=您已æˆåŠŸåˆ›å»ºäº†ä¸€ä¸ªæ–°çš„ OAuth2 应用。
update_oauth2_application_success=您已æˆåŠŸæ›´æ–°äº†æ­¤ OAuth2 应用。
oauth2_application_name=应用åç§°
-oauth2_confidential_client=æœºå¯†å®¢æˆ·ç«¯ã€‚æ˜¯å¦æ˜¯èƒ½å¤Ÿç»´æŒå‡­æ®æœºå¯†æ€§çš„应用,比如网页应用程åºã€‚如果是本地应用程åºè¯·ä¸è¦å‹¾é€‰ï¼ŒåŒ…括桌é¢å’Œç§»åŠ¨ç«¯åº”ç”¨ã€‚
+oauth2_confidential_client=机密客户端。对于需è¦ä¿å¯†çš„应用(例如 Web 应用),请选择此选项。对于包括桌é¢å’Œç§»åŠ¨åº”ç”¨åœ¨å†…çš„æœ¬æœºåº”ç”¨ï¼Œè¯·å‹¿é€‰æ‹©æ­¤é€‰é¡¹ã€‚
oauth2_skip_secondary_authorization=首次授æƒåŽå…è®¸å…¬å…±å®¢æˆ·ç«¯è·³è¿‡æŽˆæƒæ­¥éª¤ã€‚ <strong>å¯èƒ½ä¼šå¸¦æ¥å®‰å…¨é£Žé™©ã€‚</strong>
oauth2_redirect_uris=é‡å®šå‘ URI。æ¯è¡Œä¸€ä¸ª URI。
save_application=ä¿å­˜
@@ -956,7 +960,7 @@ oauth2_client_secret_hint=您离开或刷新此页é¢åŽå°†ä¸ä¼šå†æ˜¾ç¤ºæ­¤å¯†
oauth2_application_edit=编辑
oauth2_application_create_description=OAuth2 应用å…许您的第三方应用程åºè®¿é—®æ­¤å®žä¾‹çš„ç”¨æˆ·å¸æˆ·ã€‚
oauth2_application_remove_description=移除一个OAuth2应用将会阻止它访问此实例上的已授æƒç”¨æˆ·è´¦æˆ·ã€‚是å¦ç»§ç»­ï¼Ÿ
-oauth2_application_locked=如果é…ç½®å¯ç”¨ï¼ŒGitea预注册一些OAuth2应用程åºã€‚ 为了防止æ„外的行为, 这些应用既ä¸èƒ½ç¼–辑也ä¸èƒ½åˆ é™¤ã€‚请å‚阅OAuth2æ–‡æ¡£ä»¥èŽ·å–æ›´å¤šä¿¡æ¯ã€‚
+oauth2_application_locked=如果é…ç½®å¯ç”¨ï¼ŒGitea 将在å¯åŠ¨æ—¶é¢„æ³¨å†Œä¸€äº› OAuth2 应用程åºã€‚ 为了防止æ„外的行为, 这些应用既ä¸èƒ½ç¼–辑也ä¸èƒ½åˆ é™¤ã€‚请å‚阅 OAuth2 æ–‡æ¡£ä»¥èŽ·å–æ›´å¤šä¿¡æ¯ã€‚
authorized_oauth2_applications=已授æƒçš„ OAuth2 应用
authorized_oauth2_applications_description=您已授予这些第三方应用程åºè®¿é—®æ‚¨çš„个人 Gitea 账户的æƒé™ã€‚请撤销那些您ä¸å†éœ€è¦çš„应用程åºçš„访问æƒé™ã€‚
@@ -965,10 +969,10 @@ revoke_oauth2_grant=撤回æƒé™
revoke_oauth2_grant_description=确定撤销此三方应用程åºçš„æŽˆæƒï¼Œå¹¶é˜»æ­¢æ­¤åº”用程åºè®¿é—®æ‚¨çš„æ•°æ®ï¼Ÿ
revoke_oauth2_grant_success=æˆåŠŸæ’¤é”€äº†è®¿é—®æƒé™ã€‚
-twofa_desc=ä¸ºä¿æŠ¤ä½ çš„è´¦å·å¯†ç å®‰å…¨ï¼Œä½ å¯ä»¥ä½¿ç”¨æ™ºèƒ½æ‰‹æœºæˆ–å…¶å®ƒè®¾å¤‡æ¥æŽ¥æ”¶æ—¶é—´å¼ºç›¸å…³çš„ä¸€æ¬¡æ€§å¯†ç ï¼ˆTOTP)。
+twofa_desc=ä¸ºä¿æŠ¤æ‚¨çš„è´¦å·å¯†ç å®‰å…¨ï¼Œæ‚¨å¯ä»¥ä½¿ç”¨æ™ºèƒ½æ‰‹æœºæˆ–å…¶å®ƒè®¾å¤‡æ¥æŽ¥æ”¶æ—¶é—´å¼ºç›¸å…³çš„ä¸€æ¬¡æ€§å¯†ç ï¼ˆTOTP)。
twofa_recovery_tip=如果您丢失了您的设备,您将能够使用一次性æ¢å¤å¯†é’¥æ¥é‡æ–°èŽ·å¾—å¯¹æ‚¨è´¦æˆ·çš„è®¿é—®ã€‚
-twofa_is_enrolled=你的账å·<strong>å·²å¯ç”¨</strong>了两步验è¯ã€‚
-twofa_not_enrolled=ä½ çš„è´¦å·æœªå¼€å¯ä¸¤æ­¥éªŒè¯ã€‚
+twofa_is_enrolled=您的账å·<strong>å·²å¯ç”¨</strong>了两步验è¯ã€‚
+twofa_not_enrolled=æ‚¨çš„è´¦å·æœªå¼€å¯ä¸¤æ­¥éªŒè¯ã€‚
twofa_disable=ç¦ç”¨ä¸¤æ­¥è®¤è¯
twofa_scratch_token_regenerate=釿–°ç”Ÿæˆåˆå§‹ä»¤ç‰Œ
twofa_scratch_token_regenerated=您的åˆå§‹ä»¤ç‰ŒçŽ°åœ¨æ˜¯ %s。将其存放在安全的地方,它将ä¸ä¼šå†æ¬¡æ˜¾ç¤ºã€‚
@@ -976,12 +980,12 @@ twofa_enroll=å¯ç”¨ä¸¤æ­¥éªŒè¯
twofa_disable_note=如果需è¦, å¯ä»¥ç¦ç”¨åŒå› ç´ èº«ä»½éªŒè¯ã€‚
twofa_disable_desc=关掉两步验è¯ä¼šä½¿å¾—您的账å·ä¸å®‰å…¨ï¼Œç»§ç»­æ‰§è¡Œï¼Ÿ
regenerate_scratch_token_desc=如果您丢失了您的æ¢å¤å¯†é’¥æˆ–å·²ç»ä½¿ç”¨å®ƒç™»å½•, 您å¯ä»¥åœ¨è¿™é‡Œé‡ç½®å®ƒã€‚
-twofa_disabled=两步验è¯å·²è¢«ç¦ç”¨ã€‚
+twofa_disabled=两步验è¯å·²ç¦ç”¨ã€‚
scan_this_image=使用您的授æƒåº”用扫æè¿™å¼ å›¾ç‰‡ï¼š
or_enter_secret=或者输入密钥:%s
then_enter_passcode=并输入应用程åºä¸­æ˜¾ç¤ºçš„密ç :
passcode_invalid=密ç ä¸æ­£ç¡®ã€‚å†è¯•一次。
-twofa_enrolled=你的账å·å·²ç»å¯ç”¨äº†ä¸¤æ­¥éªŒè¯ã€‚请ä¿å­˜åˆå§‹ä»¤ç‰Œï¼ˆ%s)到一个安全的地方,此令牌仅显示一次。
+twofa_enrolled=您的账å·å·²ç»å¯ç”¨äº†ä¸¤æ­¥éªŒè¯ã€‚请ä¿å­˜åˆå§‹ä»¤ç‰Œï¼ˆ%s)到一个安全的地方,此令牌仅显示一次。
twofa_failed_get_secret=èŽ·å– secret 失败。
webauthn_desc=安全密钥是包å«åŠ å¯†å¯†é’¥çš„ç¡¬ä»¶è®¾å¤‡ã€‚å®ƒä»¬å¯ä»¥ç”¨äºŽåŒå› ç´ èº«ä»½éªŒè¯ã€‚å®‰å…¨å¯†é’¥å¿…é¡»æ”¯æŒ <a rel="noreferrer" target="_blank" href="%s">WebAuthn 身份验è¯å™¨</a> 标准。
@@ -1000,23 +1004,23 @@ remove_account_link=删除已绑定的账å·
remove_account_link_desc=åˆ é™¤å·²ç»‘å®šå¸æˆ·å°†åŠé”€å…¶å¯¹æ‚¨çš„ Gitea 叿ˆ·çš„访问æƒé™ã€‚继续?
remove_account_link_success=已喿¶ˆç»‘å®šå¸æˆ·ã€‚
-hooks.desc=添加 Webhooks,它们将会在您拥有的<strong>所有仓库</strong>上触å‘
+hooks.desc=添加 Web é’©å­ï¼Œå®ƒä»¬å°†ä¼šåœ¨æ‚¨æ‹¥æœ‰çš„<strong>所有仓库</strong>上触å‘。
orgs_none=æ‚¨çŽ°åœ¨è¿˜ä¸æ˜¯ä»»ä½•组织的æˆå‘˜ã€‚
-repos_none=你并䏿‹¥æœ‰ä»»ä½•仓库。
+repos_none=æ‚¨å¹¶ä¸æ‹¥æœ‰ä»»ä½•仓库。
delete_account=删除当å‰å¸æˆ·
-delete_prompt=æ­¤æ“ä½œå°†æ°¸ä¹…åˆ é™¤æ‚¨çš„ç”¨æˆ·å¸æˆ·ã€‚它 <strong>ä¸èƒ½</strong> 被撤消。
-delete_with_all_comments=ä½ çš„å¸æˆ·å¹´é¾„å°äºŽ %s。为了é¿å…å¹½çµè¯„论,所有工å•/åˆå¹¶è¯·æ±‚的评论都将与它一起被删除。
+delete_prompt=æ­¤æ“ä½œå°†æ°¸ä¹…åˆ é™¤æ‚¨çš„ç”¨æˆ·å¸æˆ·ã€‚它 <strong>无法</strong> 被撤消。
+delete_with_all_comments=æ‚¨çš„å¸æˆ·å¹´é¾„å°äºŽ %s。为了é¿å…å¹½çµè¯„论,所有工å•/åˆå¹¶è¯·æ±‚的评论都将与它一起被删除。
confirm_delete_account=ç¡®è®¤åˆ é™¤å¸æˆ·
delete_account_title=删除当å‰å¸æˆ·
delete_account_desc=ç¡®å®žè¦æ°¸ä¹…åˆ é™¤æ­¤ç”¨æˆ·å¸æˆ·å—?
email_notifications.enable=å¯ç”¨é‚®ä»¶é€šçŸ¥
-email_notifications.onmention=åªåœ¨è¢«æåˆ°æ—¶é‚®ä»¶é€šçŸ¥
+email_notifications.onmention=仅被æåŠæ—¶é€šçŸ¥
email_notifications.disable=åœç”¨é‚®ä»¶é€šçŸ¥
-email_notifications.submit=邮件通知设置
-email_notifications.andyourown=和您自己的通知
+email_notifications.submit=设置邮件通知
+email_notifications.andyourown=仅与您相关的通知
visibility=用户å¯è§æ€§
visibility.public=公开
@@ -1031,9 +1035,9 @@ new_repo_helper=代ç ä»“库包å«äº†æ‰€æœ‰çš„项目文件,包括版本历å²è
owner=拥有者
owner_helper=由于最大仓库数é‡é™åˆ¶ï¼Œä¸€äº›ç»„织å¯èƒ½ä¸ä¼šæ˜¾ç¤ºåœ¨ä¸‹æ‹‰åˆ—表中。
repo_name=仓库åç§°
-repo_name_profile_public_hint=.profile 是一个特殊的存储库,您å¯ä»¥ä½¿ç”¨å®ƒå°† README.md 添加到您的公共组织资料中,任何人都å¯ä»¥çœ‹åˆ°ã€‚请确ä¿å®ƒæ˜¯å…¬å¼€çš„,并使用个人资料目录中的 README 对其进行åˆå§‹åŒ–以开始使用。
-repo_name_profile_private_hint=.profile-private 是一个特殊的存储库,您å¯ä»¥ä½¿ç”¨å®ƒå‘您的组织æˆå‘˜ä¸ªäººèµ„料添加 README.md,仅对组织æˆå‘˜å¯è§ã€‚请确ä¿å®ƒæ˜¯ç§æœ‰çš„,并使用个人资料目录中的 README 对其进行åˆå§‹åŒ–以开始使用。
-repo_name_helper=ç†æƒ³çš„仓库åç§°åº”ç”±ç®€çŸ­ã€æœ‰æ„义和独特的关键è¯ç»„æˆã€‚".profile" å’Œ ".profile-private" å¯ç”¨äºŽä¸ºç”¨æˆ·/组织添加 README.md。
+repo_name_profile_public_hint=.profile 是一个特殊的仓库,您å¯ä»¥ä½¿ç”¨å®ƒå°† README.md 添加到您的公共组织资料中,任何人都å¯ä»¥çœ‹åˆ°ã€‚请确ä¿å®ƒæ˜¯å…¬å¼€çš„,并使用个人资料目录中的 README 对其进行åˆå§‹åŒ–以开始使用。
+repo_name_profile_private_hint=.profile-private 是一个特殊的仓库,您å¯ä»¥ä½¿ç”¨å®ƒå‘您的组织æˆå‘˜ä¸ªäººèµ„料添加 README.md,仅对组织æˆå‘˜å¯è§ã€‚请确ä¿å®ƒæ˜¯ç§æœ‰çš„,并使用个人资料目录中的 README 对其进行åˆå§‹åŒ–以开始使用。
+repo_name_helper=ç†æƒ³çš„仓库åç§°åº”ç”±ç®€çŸ­ã€æœ‰æ„义和独特的关键è¯ç»„æˆã€‚「.profileã€å’Œã€Œ.profile-privateã€å¯ç”¨äºŽä¸ºç”¨æˆ·/组织添加 README.md。
repo_size=仓库大å°
template=模æ¿
template_select=选择模æ¿
@@ -1047,10 +1051,10 @@ visibility_fork_helper=(修改该值将会影å“到所有派生仓库)
clone_helper=ä¸çŸ¥é“如何克隆?查看<a target="_blank" rel="noopener noreferrer" href="%s">帮助</a> 。
fork_repo=派生仓库
fork_from=派生自
-already_forked=ä½ å·²ç»æ´¾ç”Ÿè¿‡ %s
+already_forked=æ‚¨å·²ç»æ´¾ç”Ÿè¿‡ %s
fork_to_different_account=派生到其他账å·
fork_visibility_helper=无法更改派生仓库的å¯è§æ€§ã€‚
-fork_branch=è¦å…‹éš†åˆ° Fork 的分支
+fork_branch=è¦å…‹éš†ä¸ºæ´¾ç”Ÿçš„分支
all_branches=所有分支
view_all_branches=查看所有分支
view_all_tags=查看所有标签
@@ -1058,30 +1062,31 @@ fork_no_valid_owners=这个代ç ä»“库无法被派生,因为没有有效的æ‰
fork.blocked_user=无法克隆仓库,因为您被仓库所有者å±è”½ã€‚
use_template=使用此模æ¿
open_with_editor=用 %s 打开
+
download_zip=下载 ZIP
download_tar=下载 TAR.GZ
download_bundle=下载 BUNDLE
generate_repo=生æˆä»“库
generate_from=生æˆè‡ª
-repo_desc=仓库æè¿°
+repo_desc=æè¿°
repo_desc_helper=è¾“å…¥ç®€è¦æè¿° (å¯é€‰)
repo_no_desc=无详细信æ¯
-repo_lang=编程语言
-repo_gitignore_helper=选择 .gitignore 模æ¿ã€‚
+repo_lang=语言
+repo_gitignore_helper=选择 .gitignore 模æ¿
repo_gitignore_helper_desc=从常è§è¯­è¨€çš„æ¨¡æ¿åˆ—è¡¨ä¸­é€‰æ‹©å¿½ç•¥è·Ÿè¸ªçš„æ–‡ä»¶ã€‚é»˜è®¤æƒ…å†µä¸‹ï¼Œç”±å¼€å‘æˆ–构建工具生æˆçš„特殊文件都包å«åœ¨ .gitignore 中。
issue_labels=工啿 ‡ç­¾
issue_labels_helper=é€‰æ‹©ä¸€ä¸ªå·¥å•æ ‡ç­¾é›†
license=授æƒè®¸å¯
-license_helper=选择授æƒè®¸å¯æ–‡ä»¶ã€‚
-license_helper_desc=许å¯è¯è¯´æ˜Žäº†å…¶ä»–人å¯ä»¥å’Œä¸å¯ä»¥ç”¨æ‚¨çš„代ç åšä»€ä¹ˆã€‚ä¸ç¡®å®šå“ªä¸€ä¸ªé€‚åˆä½ çš„é¡¹ç›®ï¼Ÿè§ <a target="_blank" rel="noopener noreferrer" href="%s">选择一个许å¯è¯</a>
+license_helper=选择授æƒè®¸å¯æ–‡ä»¶
+license_helper_desc=许å¯è¯è¯´æ˜Žäº†å…¶ä»–人å¯ä»¥å’Œä¸å¯ä»¥ç”¨æ‚¨çš„代ç åšä»€ä¹ˆã€‚ä¸ç¡®å®šå“ªä¸€ä¸ªé€‚åˆæ‚¨çš„é¡¹ç›®ï¼Ÿè§ <a target="_blank" rel="noopener noreferrer" href="%s">选择一个许å¯è¯</a>
multiple_licenses=多许å¯è¯
object_format=对象格å¼
object_format_helper=仓库的对象格å¼ã€‚ä¹‹åŽæ— æ³•更改。SHA1 是最兼容的。
readme=自述
readme_helper=选择自述文件模æ¿ã€‚
readme_helper_desc=这是您å¯ä»¥ä¸ºæ‚¨çš„项目撰写完整æè¿°çš„地方。
-auto_init=åˆå§‹åŒ–仓库(添加. gitignoreã€è®¸å¯è¯å’Œè‡ªè¿°æ–‡ä»¶)
-trust_model_helper=选择签å验è¯çš„“信任模型â€ã€‚å¯èƒ½çš„选项是:
+auto_init=åˆå§‹åŒ–仓库(添加 .gitignoreã€è®¸å¯è¯å’Œè‡ªè¿°æ–‡ä»¶ï¼‰
+trust_model_helper=选择签å验è¯çš„信任模型。å¯èƒ½çš„选项是:
trust_model_helper_collaborator=å作者:信任å作者的签å
trust_model_helper_committer=æäº¤è€…:信任与æäº¤è€…相符的签å
trust_model_helper_collaborator_committer=å作者+æäº¤è€…:信任åä½œè€…åŒæ—¶æ˜¯æäº¤è€…的签å
@@ -1103,13 +1108,13 @@ mirror_address_protocol_invalid=æä¾›çš„URL无效。åªèƒ½ä½¿ç”¨http(s)://或gi
mirror_lfs=大文件存储 (LFS)
mirror_lfs_desc=é•œåƒ LFS æ•°æ®ã€‚
mirror_lfs_endpoint=LFS 网å€
-mirror_lfs_endpoint_desc=åŒæ­¥å°†å°è¯•ä½¿ç”¨å…‹éš†ç½‘å€æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å­˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œä½ è¿˜å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚
+mirror_lfs_endpoint_desc=åŒæ­¥å°†å°è¯•ä½¿ç”¨å…‹éš†ç½‘å€æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å­˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œæ‚¨è¿˜å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚
mirror_last_synced=ä¸Šæ¬¡åŒæ­¥
mirror_password_placeholder=(未更改)
mirror_password_blank_placeholder=(未设置)
mirror_password_help=更改用户å以删除已储存的密ç ã€‚
watchers=关注者
-stargazers=称赞者
+stargazers=已赞者
stars_remove_warning=这将清除此仓库的所有点赞数。
forks=派生仓库
stars=点赞数
@@ -1133,11 +1138,11 @@ user_search_tooltip=最多显示30å用户
tree_path_not_found=%[2]s 中ä¸å­˜åœ¨è·¯å¾„ %[1]s
transfer.accept=接å—转移
-transfer.accept_desc=`转移到 "%s"`
+transfer.accept_desc=转移到「%sã€
transfer.reject=æ‹’ç»è½¬ç§»
-transfer.reject_desc=`å–æ¶ˆè½¬ç§»åˆ° "%s"`
-transfer.no_permission_to_accept=您没有æƒé™æŽ¥å—此转让。
-transfer.no_permission_to_reject=您没有æƒé™æ‹’ç»æ­¤è½¬è®©ã€‚
+transfer.reject_desc=å–æ¶ˆè½¬ç§»åˆ°ã€Œ%sã€
+transfer.no_permission_to_accept=您没有æƒé™æŽ¥å—此转移。
+transfer.no_permission_to_reject=您没有æƒé™æ‹’ç»æ­¤è½¬ç§»ã€‚
desc.private=ç§æœ‰åº“
desc.public=公开
@@ -1148,9 +1153,9 @@ desc.archived=已存档
desc.sha256=SHA256
template.items=模æ¿é€‰é¡¹
-template.git_content=Gitæ•°æ®(默认分支)
+template.git_content=Git æ•°æ®ï¼ˆé»˜è®¤åˆ†æ”¯ï¼‰
template.git_hooks=Git é’©å­
-template.git_hooks_tooltip=ä½ ç›®å‰æ— æ³•修改或删除被添加过的 Git Hook。仅当你信任模æ¿ä»“库时æ‰å¯ä»¥é€‰æ‹©æ­¤é¡¹ã€‚
+template.git_hooks_tooltip=æ‚¨ç›®å‰æ— æ³•修改或删除被添加过的 Git é’©å­ã€‚仅当您信任模æ¿ä»“库时æ‰å¯ä»¥é€‰æ‹©æ­¤é¡¹ã€‚
template.webhooks=Web é’©å­
template.topics=主题
template.avatar=头åƒ
@@ -1163,10 +1168,10 @@ archive.title_date=该仓库已于 %s 归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶æˆ–克隆它
archive.issue.nocomment=此仓库已存档,您ä¸èƒ½åœ¨æ­¤å·¥å•添加评论。
archive.pull.nocomment=此仓库已存档,您ä¸èƒ½åœ¨æ­¤åˆå¹¶è¯·æ±‚添加评论。
-form.reach_limit_of_creation_1=ä½ å·²ç»è¾¾åˆ°äº† %d 仓库的上é™ã€‚
-form.reach_limit_of_creation_n=ä½ å·²ç»è¾¾åˆ°äº† %d 个仓库的上é™ã€‚
-form.name_reserved=仓库åç§° %s 是被ä¿ç•™çš„。
-form.name_pattern_not_allowed=仓库å称中ä¸å…许使用 %s æ ¼å¼ã€‚
+form.reach_limit_of_creation_1=您已ç»è¾¾åˆ°äº† %d 仓库的上é™ã€‚
+form.reach_limit_of_creation_n=您已ç»è¾¾åˆ°äº† %d 个仓库的上é™ã€‚
+form.name_reserved=仓库åç§° %s 是ä¿ç•™çš„。
+form.name_pattern_not_allowed=仓库å中ä¸å…许使用「%sã€æ ¼å¼ã€‚
need_auth=授æƒ
migrate_options=è¿ç§»é€‰é¡¹
@@ -1174,7 +1179,7 @@ migrate_service=è¿ç§»æœåŠ¡
migrate_options_mirror_helper=该仓库将是一个镜åƒ
migrate_options_lfs=è¿ç§» LFS 文件
migrate_options_lfs_endpoint.label=LFS 网å€
-migrate_options_lfs_endpoint.description=è¿ç§»å°†å°è¯•使用你的 Git remote æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å­˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œä½ è¿˜å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚
+migrate_options_lfs_endpoint.description=è¿ç§»å°†å°è¯•使用您的 Git remote æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å­˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œæ‚¨è¿˜å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚
migrate_options_lfs_endpoint.description.local=æ”¯æŒæœ¬åœ°æœåŠ¡å™¨è·¯å¾„ã€‚
migrate_options_lfs_endpoint.placeholder=如果留空,网å€å°†ä»Žå…‹éš† URL 中得到
migrate_items=è¿ç§»é¡¹ç›®
@@ -1184,7 +1189,7 @@ migrate_items_labels=标签
migrate_items_issues=å·¥å•
migrate_items_pullrequests=åˆå¹¶è¯·æ±‚
migrate_items_merge_requests=åˆå¹¶è¯·æ±‚
-migrate_items_releases=版本å‘布
+migrate_items_releases=å‘布
migrate_repo=è¿ç§»ä»“库
migrate.clone_address=从 URL è¿ç§»/克隆
migrate.clone_address_desc=现有仓库的 HTTP(s) 或 Git "clone" URL
@@ -1225,6 +1230,7 @@ migrate.migrating_issues=è¿ç§»å·¥å•
migrate.migrating_pulls=è¿ç§»åˆå¹¶è¯·æ±‚
migrate.cancel_migrating_title=å–æ¶ˆè¿ç§»
migrate.cancel_migrating_confirm=您想è¦å–消此次è¿ç§»å—?
+migration_status=è¿ç§»çжæ€
mirror_from=镜åƒè‡ªåœ°å€
forked_from=派生自
@@ -1257,14 +1263,14 @@ branch=分支
tree=目录树
clear_ref=`清除当å‰å¼•用`
filter_branch_and_tag=过滤分支或标签
-find_tag=查找Git标签
+find_tag=查找标签
branches=分支列表
tags=标签列表
issues=å·¥å•
pulls=åˆå¹¶è¯·æ±‚
projects=项目
packages=软件包
-actions=Actions
+actions=工作æµ
labels=标签
org_labels_desc=组织级别的标签,å¯ä»¥è¢«æœ¬ç»„织下的 <strong>所有仓库</strong> 使用
org_labels_desc_manage=管ç†
@@ -1273,9 +1279,9 @@ milestone=里程碑
milestones=里程碑
commits=æäº¤
commit=æäº¤
-release=版本å‘布
-releases=版本å‘布
-tag=Git标签
+release=å‘布
+releases=å‘布
+tag=标签
released_this=å‘布
tagged_this=已标记
file.title=%s ä½äºŽ %s
@@ -1328,16 +1334,18 @@ editor.upload_file=上传文件
editor.edit_file=编辑文件
editor.preview_changes=é¢„è§ˆå˜æ›´
editor.cannot_edit_lfs_files=无法在 web 界é¢ä¸­ç¼–辑 lfs 文件。
+editor.cannot_edit_too_large_file=文件过大,无法编辑。
editor.cannot_edit_non_text_files=网页ä¸èƒ½ç¼–辑二进制文件。
+editor.file_not_editable_hint=但您ä»ç„¶å¯ä»¥é‡å‘½å或移动它。
editor.edit_this_file=编辑文件
editor.this_file_locked=文件已é”定
editor.must_be_on_a_branch=您必须在æŸä¸ªåˆ†æ”¯ä¸Šæ‰èƒ½å¯¹æ­¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作。
-editor.fork_before_edit=您必须在派生这个仓库æ‰èƒ½å¯¹æ­¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作
+editor.fork_before_edit=您必须派生这个仓库æ‰èƒ½å¯¹æ­¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作。
editor.delete_this_file=删除文件
editor.must_have_write_access=您必须具有写æƒé™æ‰èƒ½å¯¹æ­¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作。
-editor.file_delete_success=文件 %s 已被删除。
+editor.file_delete_success=文件「%sã€å·²åˆ é™¤ã€‚
editor.name_your_file=命忖‡ä»¶...
-editor.filename_help=通过键入åç§°åŽè·Ÿæ–œçº¿ ("/") æ¥æ·»åŠ ç›®å½•ã€‚é€šè¿‡åœ¨è¾“å…¥æ¡†çš„å¼€å¤´é”®å…¥ "退格" æ¥åˆ é™¤ç›®å½•。
+editor.filename_help=通过键入åç§°åŽè·Ÿæ–œçº¿ ("/") æ¥æ·»åŠ ç›®å½•ã€‚é€šè¿‡åœ¨è¾“å…¥æ¡†çš„å¼€å¤´é”®å…¥ã€Œé€€æ ¼ã€æ¥åˆ é™¤ç›®å½•。
editor.or=或
editor.cancel_lower=å–æ¶ˆ
editor.commit_signed_changes=æäº¤å·²ç­¾å的更改
@@ -1348,54 +1356,60 @@ editor.update=æ›´æ–° %s
editor.delete=删除 %s
editor.patch=应用补ä¸
editor.patching=打补ä¸ï¼š
-editor.fail_to_apply_patch=æ— æ³•åº”ç”¨è¡¥ä¸ %s
+editor.fail_to_apply_patch=无法应用补ä¸
editor.new_patch=æ–°è¡¥ä¸
editor.commit_message_desc=添加一个å¯é€‰çš„æ‰©å±•æè¿°...
editor.signoff_desc=在æäº¤æ—¥å¿—æ¶ˆæ¯æœ«å°¾æ·»åŠ ç­¾ç½²äººä¿¡æ¯ã€‚
editor.commit_directly_to_this_branch=直接æäº¤è‡³ <strong class="branch-name">%s</strong> 分支。
editor.create_new_branch=为此æäº¤åˆ›å»ºä¸€ä¸ª <strong>新的分支</strong> å¹¶å‘èµ·åˆå¹¶è¯·æ±‚。
editor.create_new_branch_np=为此æäº¤åˆ›å»º <strong>新分支</strong>。
-editor.propose_file_change=æè®®æ–‡ä»¶æ›´æ”¹
+editor.propose_file_change=建议文件更改
editor.new_branch_name=为这次æäº¤çš„æ–°åˆ†æ”¯å‘½å
editor.new_branch_name_desc=新的分支åç§°...
editor.cancel=å–æ¶ˆ
editor.filename_cannot_be_empty=文件åä¸èƒ½ä¸ºç©ºã€‚
-editor.filename_is_invalid=文件å %s 无效
+editor.filename_is_invalid=æ–‡ä»¶åæ— æ•ˆï¼šã€Œ%sã€ã€‚
editor.commit_email=æäº¤é‚®ç®±åœ°å€
editor.invalid_commit_email=æäº¤çš„é‚®ç®±åœ°å€æ— æ•ˆã€‚
-editor.branch_does_not_exist=此仓库中ä¸å­˜åœ¨å为 %s 的分支。
-editor.branch_already_exists=此仓库已存在å为 %s 的分支。
-editor.directory_is_a_file=%s å·²ç»ä½œä¸ºæ–‡ä»¶å在此仓库中存在。
-editor.file_is_a_symlink=`"%s" 是一个符å·é“¾æŽ¥ï¼Œæ— æ³•在 web 编辑器中编辑`
-editor.filename_is_a_directory=此仓库中已存在å为“%s†的目录。
-editor.file_editing_no_longer_exists=正在编辑的文件 %s å·²ä¸å­˜åœ¨ã€‚
-editor.file_deleting_no_longer_exists=正在删除的文件 %s å·²ä¸å­˜åœ¨ã€‚
+editor.branch_does_not_exist=此仓库中ä¸å­˜åœ¨å为「%sã€çš„分支。
+editor.branch_already_exists=此仓库已存在å为「%sã€çš„分支。
+editor.directory_is_a_file=目录å「%sã€å·²ä½œä¸ºæ–‡ä»¶å在此仓库中存在。
+editor.file_is_a_symlink=`「%sã€æ˜¯ä¸€ä¸ªç¬¦å·é“¾æŽ¥ï¼Œæ— æ³•在 Web 编辑器中编辑`
+editor.filename_is_a_directory=文件å「%sã€å·²ä½œä¸ºç›®å½•å在此仓库中存在。
+editor.file_modifying_no_longer_exists=正在修改的文件「%sã€å·²ä¸å­˜åœ¨äºŽæ­¤ä»“库。
editor.file_changed_while_editing=文件内容在您进行编辑时已ç»å‘生å˜åŠ¨ã€‚<a target="_blank" rel="noopener noreferrer" href="%s">å•击此处</a> 查看å˜åŠ¨çš„å…·ä½“å†…å®¹ï¼Œæˆ–è€… <strong>冿¬¡æäº¤</strong> 覆盖已å‘生的å˜åŠ¨ã€‚
-editor.file_already_exists=此仓库已ç»å­˜åœ¨å为 %s 的文件。
-editor.commit_id_not_matching=æäº¤ID与您开始编辑时的IDä¸åŒ¹é…。请æäº¤åˆ°è¡¥ä¸åˆ†æ”¯ç„¶åŽåˆå¹¶ã€‚
+editor.file_already_exists=此仓库已ç»å­˜åœ¨å为「%sã€çš„æ–‡ä»¶ã€‚
+editor.commit_id_not_matching=æäº¤ ID 与您开始编辑时的 ID ä¸åŒ¹é…。请æäº¤åˆ°è¡¥ä¸åˆ†æ”¯ç„¶åŽåˆå¹¶ã€‚
editor.push_out_of_date=推é€ä¼¼ä¹Žå·²ç»è¿‡æ—¶ã€‚
editor.commit_empty_file_header=æäº¤ä¸€ä¸ªç©ºæ–‡ä»¶
editor.commit_empty_file_text=æ‚¨è¦æäº¤çš„æ–‡ä»¶æ˜¯ç©ºçš„ï¼Œç»§ç»­å—?
editor.no_changes_to_show=没有å¯ä»¥æ˜¾ç¤ºçš„å˜æ›´ã€‚
-editor.fail_to_update_file=更新/创建文件 %s 失败。
-editor.fail_to_update_file_summary=错误信æ¯ï¼š
-editor.push_rejected_no_message=此修改被æœåŠ¡å™¨æ‹’ç»å¹¶ä¸”没有å馈消æ¯ã€‚请检查 Git Hook。
-editor.push_rejected=此修改被æœåŠ¡å™¨æ‹’ç»ã€‚请检查 Git Hook。
+editor.push_rejected_no_message=此修改被æœåŠ¡å™¨æ‹’ç»å¹¶ä¸”没有å馈消æ¯ã€‚请检查 Git é’©å­ã€‚
+editor.push_rejected=此修改被æœåŠ¡å™¨æ‹’ç»ã€‚请检查 Git é’©å­ã€‚
editor.push_rejected_summary=详细拒ç»ä¿¡æ¯ï¼š
editor.add_subdir=添加目录
-editor.unable_to_upload_files=上传文件至 %s æ—¶å‘生错误:%v
-editor.upload_file_is_locked=文件 %s 被 %s é”定。
-editor.upload_files_to_dir=上传文件至 %s
-editor.cannot_commit_to_protected_branch=ä¸å¯ä»¥æäº¤åˆ°å—ä¿æŠ¤çš„åˆ†æ”¯ %s。
+editor.unable_to_upload_files=上传文件至「%sã€å¤±è´¥ï¼š%v
+editor.upload_file_is_locked=文件「%sã€è¢« %s é”定。
+editor.upload_files_to_dir=上传文件至「%sã€
+editor.cannot_commit_to_protected_branch=ä¸å¯ä»¥æäº¤åˆ°å—ä¿æŠ¤çš„åˆ†æ”¯ã€Œ%sã€ã€‚
editor.no_commit_to_branch=无法直接æäº¤åˆ†æ”¯ï¼Œå› ä¸ºï¼š
editor.user_no_push_to_branch=用户ä¸èƒ½æŽ¨é€åˆ°åˆ†æ”¯
editor.require_signed_commit=分支需è¦ç­¾åæäº¤
-editor.cherry_pick=Cherry-pick %s 到:
+editor.cherry_pick=拣选æäº¤ %s 到:
editor.revert=将 %s 还原到:
+editor.failed_to_commit=æäº¤æ›´æ”¹å¤±è´¥ã€‚
+editor.failed_to_commit_summary=错误信æ¯ï¼š
+
+editor.fork_create=派生仓库å‘èµ·è¯·æ±‚å˜æ›´
+editor.fork_create_description=您ä¸èƒ½ç›´æŽ¥ç¼–辑此仓库。您å¯ä»¥ä»Žæ­¤ä»“库派生,进行编辑并创建一个拉å–请求。
+editor.fork_edit_description=您ä¸èƒ½ç›´æŽ¥ç¼–辑此仓库。 更改将写入您的派生仓库 <b>%s</b>,以便您å¯ä»¥åˆ›å»ºä¸€ä¸ªæ‹‰å–请求。
+editor.fork_not_editable=ä½ å·²ç»æ´¾ç”Ÿäº†è¿™ä¸ªä»“åº“ï¼Œä½†æ˜¯ä½ çš„åˆ†å‰æ˜¯ä¸å¯ç¼–辑的。
+editor.fork_failed_to_push_branch=推é€åˆ†æ”¯ %s 到仓库失败。
+editor.fork_branch_exists=分支 "%s" 已存在于您的派生仓库中,请选择一个新的分支å称。
commits.desc=æµè§ˆä»£ç ä¿®æ”¹åކå²
commits.commits=æ¬¡ä»£ç æäº¤
-commits.no_commits=没有共åŒçš„æäº¤ã€‚%s å’Œ %s 的历å²å®Œå…¨ä¸åŒã€‚
+commits.no_commits=没有共åŒçš„æäº¤ã€‚「%sã€å’Œã€Œ%sã€çš„历å²å®Œå…¨ä¸åŒã€‚
commits.nothing_to_compare=这些分支是相åŒçš„。
commits.search.tooltip=`您å¯ä»¥åœ¨å…³é”®è¯å‰åŠ ä¸Šå‰ç¼€ï¼Œå¦‚"author:", "committer:", "after:", 或"before:", 例如 "retrin author:Alice before:2019-01-13"`
commits.search_branch=此分支
@@ -1415,11 +1429,11 @@ commits.view_file_diff=查看æäº¤ä¸­çš„æ–‡ä»¶æ›´æ”¹
commit.operations=æ“作
commit.revert=还原
-commit.revert-header=还原: %s
+commit.revert-header=还原:%s
commit.revert-content=选择è¦è¿˜åŽŸçš„åˆ†æ”¯ï¼š
-commit.cherry-pick=Cherry-pick
-commit.cherry-pick-header=Cherry-pick: %s
-commit.cherry-pick-content=选择 cherry-pick 的目标分支:
+commit.cherry-pick=拣选æäº¤
+commit.cherry-pick-header=拣选æäº¤ï¼š%s
+commit.cherry-pick-content=é€‰æ‹©è¦æ‹£é€‰æäº¤çš„目标分支:
commitstatus.error=错误
commitstatus.failure=失败
@@ -1436,14 +1450,14 @@ projects.create=创建项目
projects.title=标题
projects.new=创建项目
projects.new_subheader=在一个地方åè°ƒã€è·Ÿè¸ªå’Œæ›´æ–°æ‚¨çš„å·¥ä½œï¼Œè®©é¡¹ç›®ä¿æŒé€æ˜Žå¹¶æŒ‰è®¡åˆ’进行。
-projects.create_success=项目 %s 创建æˆåŠŸã€‚
+projects.create_success=项目「%sã€åˆ›å»ºæˆåŠŸã€‚
projects.deletion=删除项目
projects.deletion_desc=删除项目会从所有相关的工å•中移除它。是å¦ç»§ç»­ï¼Ÿ
-projects.deletion_success=该项目已被删除。
+projects.deletion_success=该项目已删除。
projects.edit=编辑项目
projects.edit_subheader=项目用于组织工å•和跟踪进展情况。
projects.modify=更新项目
-projects.edit_success=项目 %s æ›´æ–°æˆåŠŸã€‚
+projects.edit_success=项目「%sã€æ›´æ–°æˆåŠŸã€‚
projects.type.none=æ— 
projects.type.basic_kanban=基础看æ¿
projects.type.bug_triage=Bug分类看æ¿
@@ -1457,7 +1471,7 @@ projects.column.new=创建列
projects.column.set_default=设为默认
projects.column.set_default_desc=设置此列为未分类问题和åˆå¹¶è¯·æ±‚的默认值
projects.column.delete=删除列
-projects.column.deletion_desc=删除项目列会将所有相关问题移到“未分类â€ã€‚是å¦ç»§ç»­ï¼Ÿ
+projects.column.deletion_desc=删除项目列会将所有相关问题移至默认列。是å¦ç»§ç»­ï¼Ÿ
projects.column.color=颜色
projects.open=å¼€å¯
projects.close=关闭
@@ -1502,19 +1516,19 @@ issues.choose.blank_about=从默认模æ¿åˆ›å»ºä¸€ä¸ªå·¥å•。
issues.choose.ignore_invalid_templates=已忽略无效模æ¿
issues.choose.invalid_templates=å‘现了 %v 个无效模æ¿
issues.choose.invalid_config=问题é…置包å«é”™è¯¯ï¼š
-issues.no_ref=分支/标记未指定
+issues.no_ref=分支/Git标签未指定
issues.create=创建工å•
issues.new_label=创建标签
issues.new_label_placeholder=标签åç§°
issues.new_label_desc_placeholder=æè¿°
issues.create_label=创建标签
issues.label_templates.title=加载预定义的标签模æ¿
-issues.label_templates.info=还没有任何标签。您å¯ä»¥ä½¿ç”¨'创建标签'按钮或者加载预定义的标签集创建标签
+issues.label_templates.info=还没有任何标签。您å¯ä»¥ä½¿ç”¨ã€Œåˆ›å»ºæ ‡ç­¾ã€æŒ‰é’®æˆ–者加载预定义的标签集创建标签:
issues.label_templates.helper=选择标签模æ¿
issues.label_templates.use=使用标签集
-issues.label_templates.fail_to_load_file=åŠ è½½æ ‡ç­¾æ¨¡æ¿æ–‡ä»¶ %s æ—¶å‘生错误:%v
+issues.label_templates.fail_to_load_file=åŠ è½½æ ‡ç­¾æ¨¡æ¿æ–‡ä»¶ã€Œ%sã€æ—¶å‘生错误:%v
issues.add_label=于 %[2]s 添加了标签 %[1]s
-issues.add_labels=于 %s 添加 %s 标签
+issues.add_labels=于 %[2]s 添加了标签 %[1]s
issues.remove_label=于 %[2]s 删除了标签 %[1]s
issues.remove_labels=于 %[2]s 删除了标签 %[1]s
issues.add_remove_labels=于 %[3]s 添加了标签 %[1]s ,删除了标签 %[2]s
@@ -1524,11 +1538,11 @@ issues.move_to_column_of_project=`将此对象移至 %s 的 %s 中在 %s 上`
issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>`
issues.change_project_at=于 %[3]s 将此从项目 <b>%[1]s</b> 移到 <b>%[2]s</b>
issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>`
-issues.remove_project_at=`从 <b>%s</b> 项目 %s 中删除`
+issues.remove_project_at=`于 %[2]s 将此工å•从项目 <b>%[1]s</b> 中删除`
issues.deleted_milestone=(已删除)
issues.deleted_project=`(已删除)`
issues.self_assign_at=`于 %s 指派给自己`
-issues.add_assignee_at=`于 %[2]s 被 <b>%[1]s</b> 指派`
+issues.add_assignee_at=`于 %[2]s 由 <b>%[1]s</b> 指派`
issues.remove_assignee_at=`<b>%s</b> å–æ¶ˆäº†æŒ‡æ´¾åœ¨ %s`
issues.remove_self_assignment=`于 %s å–æ¶ˆäº†æŒ‡æ´¾`
issues.change_title_at=`于 %[3]s 修改标题 <b><strike>%[1]s</strike></b> 为 <b>%[2]s</b>`
@@ -1556,6 +1570,7 @@ issues.filter_user_placeholder=æœç´¢ç”¨æˆ·
issues.filter_user_no_select=所有用户
issues.filter_type=类型筛选
issues.filter_type.all_issues=所有工å•
+issues.filter_type.all_pull_requests=所有åˆå¹¶è¯·æ±‚
issues.filter_type.assigned_to_you=指派给您的
issues.filter_type.created_by_you=由您创建的
issues.filter_type.mentioning_you=æåŠæ‚¨çš„
@@ -1584,11 +1599,11 @@ issues.action_assignee_no_select=未指派
issues.action_check=选中/å–æ¶ˆé€‰ä¸­
issues.action_check_all=选中/å–æ¶ˆé€‰ä¸­æ‰€æœ‰é¡¹ç›®
issues.opened_by=由 <a href="%[2]s">%[3]s</a> 于 %[1]s创建
-pulls.merged_by=ç”± <a href="%[2]s">%[3]s</a> 创建,被åˆå¹¶äºŽ %[1]s
-pulls.merged_by_fake=ç”± %[2]s 创建,被åˆå¹¶äºŽ %[1]s
-issues.closed_by=由 <a href="%[2]s">%[3]s</a> 创建,被关闭于 %[1]s
+pulls.merged_by=ç”± <a href="%[2]s">%[3]s</a> 创建,åˆå¹¶äºŽ %[1]s
+pulls.merged_by_fake=ç”± %[2]s 创建,åˆå¹¶äºŽ %[1]s
+issues.closed_by=由 <a href="%[2]s">%[3]s</a> 创建,关闭于 %[1]s
issues.opened_by_fake=由 %[2]s 于 %[1]s 打开
-issues.closed_by_fake=由 %[2]s 创建,被关闭于 %[1]s
+issues.closed_by_fake=由 %[2]s 创建,关闭于 %[1]s
issues.previous=上一页
issues.next=下一页
issues.open_title=å¼€å¯ä¸­
@@ -1608,8 +1623,8 @@ issues.close=关闭工å•
issues.comment_pull_merged_at=å·²åˆå¹¶æäº¤ %[1]s 到 %[2]s %[3]s
issues.comment_manually_pull_merged_at=手动åˆå¹¶æäº¤ %[1]s 到 %[2]s %[3]s
issues.close_comment_issue=评论并关闭
-issues.reopen_issue=釿–°å¼€å¯
-issues.reopen_comment_issue=è¯„è®ºå¹¶é‡æ–°å¼€å¯
+issues.reopen_issue=釿–°æ‰“å¼€
+issues.reopen_comment_issue=è¯„è®ºå¹¶é‡æ–°æ‰“å¼€
issues.create_comment=评论
issues.comment.blocked_user=无法创建或编辑评论,因为您已被仓库所有者或工å•创建者å±è”½ã€‚
issues.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闭此工å•`
@@ -1645,40 +1660,41 @@ issues.edit=编辑
issues.cancel=å–æ¶ˆ
issues.save=ä¿å­˜
issues.label_title=标签åç§°
-issues.label_description=标签æè¿°
-issues.label_color=标签颜色
-issues.label_exclusive=互斥标签
+issues.label_description=æè¿°
+issues.label_color=颜色
+issues.label_color_invalid=无效的颜色
+issues.label_exclusive=互斥
issues.label_archive=归档标签
-issues.label_archived_filter=显示存档标签
-issues.label_archive_tooltip=在标签æœç´¢æ—¶ï¼Œé»˜è®¤æƒ…况下存档标签将被排除在外。
+issues.label_archived_filter=显示已归档标签
+issues.label_archive_tooltip=在标签æœç´¢æ—¶ï¼Œé»˜è®¤æƒ…况下已归档标签将被排除在外。
issues.label_exclusive_desc=命忠‡ç­¾ä¸º <code>scope/item</code> 以使其与其他以 <code>scope/</code> 开头的标签互斥。
issues.label_exclusive_warning=åœ¨ç¼–è¾‘å·¥å•æˆ–åˆå¹¶è¯·æ±‚的标签时,任何冲çªçš„范围标签都将被删除。
issues.label_exclusive_order=排åºé¡ºåº
issues.label_exclusive_order_tooltip=在åŒä¸€ä¸ªèŒƒå›´å†…的互斥标签将按照这个数字进行排åº
issues.label_count=%d 个标签
-issues.label_open_issues=%d 个开å¯çš„å·¥å•
+issues.label_open_issues=%d 个已打开工å•/åˆå¹¶è¯·æ±‚
issues.label_edit=编辑
issues.label_delete=删除
issues.label_modify=编辑标签
issues.label_deletion=删除标签
issues.label_deletion_desc=删除标签会将其从所有问题中删除。继续?
-issues.label_deletion_success=该标签已被删除。
+issues.label_deletion_success=该标签已删除。
issues.label.filter_sort.alphabetically=按字æ¯é¡ºåºæŽ’åº
issues.label.filter_sort.reverse_alphabetically=按字æ¯é€†åºæŽ’åº
issues.label.filter_sort.by_size=最å°å°ºå¯¸
issues.label.filter_sort.reverse_by_size=最大尺寸
issues.num_participants=%d åå‚与者
-issues.attachment.open_tab=`在新的标签页中查看 '%s'`
-issues.attachment.download=`点击下载 '%s'`
+issues.attachment.open_tab=`在新的标签页中查看「%sã€`
+issues.attachment.download=`点击下载「%sã€`
issues.subscribe=订阅
issues.unsubscribe=å–æ¶ˆè®¢é˜…
issues.unpin=å–æ¶ˆç½®é¡¶
issues.max_pinned=您ä¸èƒ½ç½®é¡¶æ›´å¤šå·¥å•
-issues.pin_comment=于 %s 被置顶
+issues.pin_comment=于 %s 置顶
issues.unpin_comment=于 %s å–æ¶ˆç½®é¡¶
issues.lock=é”定对è¯
issues.unlock=è§£é”对è¯
-issues.lock_duplicate=一个工å•ä¸èƒ½è¢«é”定两次。
+issues.lock_duplicate=一个工å•ä¸èƒ½é”定两次。
issues.unlock_error=无法解é”一个未é”定的工å•。
issues.lock_with_reason=因为 <strong>%s</strong> 而é”定,并将对è¯é™åˆ¶ä¸ºå作者 %s
issues.lock_no_reason=é”定并é™åˆ¶ä»…å作者 %s
@@ -1705,13 +1721,15 @@ issues.timetracker_timer_discard=删除计时器
issues.timetracker_timer_manually_add=添加时间
issues.time_estimate_set=设置预计时间
-issues.time_estimate_display=预计: %s
+issues.time_estimate_display=预估:%s
issues.change_time_estimate_at=预估时间已修改为 <b>%[1]s</b> %[2]s
-issues.remove_time_estimate_at=删除预计时间 %s
+issues.remove_time_estimate_at=删除预估时间 %s
issues.time_estimate_invalid=é¢„è®¡æ—¶é—´æ ¼å¼æ— æ•ˆ
issues.start_tracking_history=`开始工作 %s`
issues.tracker_auto_close=当此工å•å…³é—­æ—¶ï¼Œè‡ªåŠ¨åœæ­¢è®¡æ—¶å™¨
-issues.tracking_already_started=`ä½ å·²ç»å¼€å§‹å¯¹ <a href="%s">å¦ä¸€ä¸ªå·¥å•</a> 进行时间跟踪ï¼`
+issues.stopwatch_already_stopped=此工å•的计时器已ç»åœæ­¢
+issues.stopwatch_already_created=此工å•的计时器已ç»å­˜åœ¨
+issues.tracking_already_started=`您已ç»å¼€å§‹å¯¹ <a href="%s">å¦ä¸€ä¸ªå·¥å•</a> 进行时间跟踪ï¼`
issues.stop_tracking=åœæ­¢è®¡æ—¶å™¨
issues.stop_tracking_history=工作 <b>%[1]s</b> 于 %[2]s åœæ­¢
issues.cancel_tracking=å–æ¶ˆ
@@ -1727,7 +1745,7 @@ issues.time_spent_total=总用时
issues.time_spent_from_all_authors=`总花费时间:%s`
issues.due_date=到期时间
-issues.invalid_due_date_format=到期时间的格å¼å¿…须是 'yyyy-mm-dd' 的形å¼ã€‚
+issues.invalid_due_date_format=到期时间的格å¼å¿…须是「yyyy-mm-ddã€çš„å½¢å¼ã€‚
issues.error_modifying_due_date=修改到期时间失败。
issues.error_removing_due_date=删除到期时间失败。
issues.push_commit_1=于 %[2]s 推é€äº† %[1]d 个æäº¤
@@ -1770,7 +1788,7 @@ issues.dependency.remove_header=删除ä¾èµ–项
issues.dependency.issue_remove_text=æ­¤æ“作将从工å•中删除ä¾èµ–。是å¦è¦ç»§ç»­?
issues.dependency.pr_remove_text=æ­¤æ“作将从åˆå¹¶è¯·æ±‚中删除ä¾èµ–。是å¦è¦ç»§ç»­?
issues.dependency.setting=为工å•å’Œåˆå¹¶è¯·æ±‚å¯ç”¨ä¾èµ–
-issues.dependency.add_error_same_issue=ä½ ä¸èƒ½è®©ä¸€ä¸ªå·¥å•ä¾èµ–于自己。
+issues.dependency.add_error_same_issue=您ä¸èƒ½è®©ä¸€ä¸ªå·¥å•ä¾èµ–于自身。
issues.dependency.add_error_dep_issue_not_exist=ä¾èµ–项ä¸å­˜åœ¨ã€‚
issues.dependency.add_error_dep_not_exist=ä¾èµ–项ä¸å­˜åœ¨ã€‚
issues.dependency.add_error_dep_exists=ä¾èµ–项已存在。
@@ -1812,9 +1830,9 @@ issues.review.stale=批准åŽå·²æ›´æ–°
issues.review.unofficial=éžå®˜æ–¹å®¡æ‰¹æ•°
issues.assignee.error=å› ä¸ºæœªçŸ¥åŽŸå› ï¼Œå¹¶éžæ‰€æœ‰çš„æŒ‡æ´¾éƒ½æˆåŠŸã€‚
issues.reference_issue.body=内容
-issues.content_history.deleted=删除于
-issues.content_history.edited=最åŽç¼–辑于
-issues.content_history.created=创建于
+issues.content_history.deleted=已删除
+issues.content_history.edited=已编辑
+issues.content_history.created=已创建
issues.content_history.delete_from_history=从历å²è®°å½•中删除
issues.content_history.delete_from_history_confirm=从历å²è®°å½•中删除å—?
issues.content_history.options=选项
@@ -1868,7 +1886,7 @@ pulls.merged=å·²åˆå¹¶
pulls.merged_success=åˆå¹¶è¯·æ±‚å·²æˆåŠŸåˆå¹¶å’Œå…³é—­
pulls.closed=åˆå¹¶è¯·æ±‚已关闭
pulls.manually_merged=已手动åˆå¹¶
-pulls.merged_info_text=分支 %s 现在å¯ä»¥è¢«åˆ é™¤äº†ã€‚
+pulls.merged_info_text=分支 %s 现在å¯ä»¥åˆ é™¤äº†ã€‚
pulls.is_closed=åˆå¹¶è¯·æ±‚å·²ç»å…³é—­ã€‚
pulls.title_wip_desc=`<a href="#">标题以 <strong>%s</strong> 开头</a>以å…åˆå¹¶è¯·æ±‚æ„外åˆå¹¶ã€‚`
pulls.cannot_merge_work_in_progress=æ­¤åˆå¹¶è¯·æ±‚被标记为正在进行的工作。
@@ -1877,6 +1895,7 @@ pulls.add_prefix=添加 <strong>%s</strong> å‰ç¼€
pulls.remove_prefix=删除 <strong>%s</strong> å‰ç¼€
pulls.data_broken=æ­¤åˆå¹¶è¯·æ±‚因为派生仓库信æ¯ç¼ºå¤±è€Œä¸­æ–­ã€‚
pulls.files_conflicted=æ­¤åˆå¹¶è¯·æ±‚æœ‰å˜æ›´ä¸Žç›®æ ‡åˆ†æ”¯å†²çªã€‚
+pulls.is_checking=正在进行åˆå¹¶å†²çªæ£€æµ‹ ...
pulls.is_ancestor=此分支已ç»åŒ…å«åœ¨ç›®æ ‡åˆ†æ”¯ä¸­ï¼Œæ²¡æœ‰ä»€ä¹ˆå¯ä»¥åˆå¹¶ã€‚
pulls.is_empty=此分支上的更改已ç»åœ¨ç›®æ ‡åˆ†æ”¯ä¸Šã€‚这将是一个空æäº¤ã€‚
pulls.required_status_check_failed=一些必è¦çš„æ£€æŸ¥æ²¡æœ‰æˆåŠŸ
@@ -1902,7 +1921,7 @@ pulls.waiting_count_1=%d 个正在等待审核
pulls.waiting_count_n=%d 个正在等待审核
pulls.wrong_commit_id=æäº¤ id 必须在目标分支 上
-pulls.no_merge_desc=由于未å¯ç”¨åˆå¹¶é€‰é¡¹ï¼Œæ­¤åˆå¹¶è¯·æ±‚无法被åˆå¹¶ã€‚
+pulls.no_merge_desc=由于未å¯ç”¨åˆå¹¶é€‰é¡¹ï¼Œæ­¤åˆå¹¶è¯·æ±‚无法åˆå¹¶ã€‚
pulls.no_merge_helper=在仓库设置中å¯ç”¨åˆå¹¶é€‰é¡¹æˆ–者手工åˆå¹¶è¯·æ±‚。
pulls.no_merge_wip=这个åˆå¹¶è¯·æ±‚无法åˆå¹¶ï¼Œå› ä¸ºè¢«æ ‡è®°ä¸ºå°šæœªå®Œæˆçš„工作。
pulls.no_merge_not_ready=æ­¤åˆå¹¶è¯·æ±‚尚未准备好åˆå¹¶ï¼Œè¯·æ£€æŸ¥å®¡æ ¸çжæ€å’ŒçŠ¶æ€æ£€æŸ¥ã€‚
@@ -1916,7 +1935,7 @@ pulls.merge_manually=手动åˆå¹¶
pulls.merge_commit_id=åˆå¹¶æäº¤ ID
pulls.require_signed_wont_sign=分支需è¦ç­¾åçš„æäº¤ï¼Œä½†è¿™ä¸ªåˆå¹¶å°†ä¸ä¼šè¢«ç­¾å
-pulls.invalid_merge_option=ä½ å¯ä»¥åœ¨æ­¤åˆå¹¶è¯·æ±‚中使用åˆå¹¶é€‰é¡¹ã€‚
+pulls.invalid_merge_option=您å¯ä»¥åœ¨æ­¤åˆå¹¶è¯·æ±‚中使用åˆå¹¶é€‰é¡¹ã€‚
pulls.merge_conflict=åˆå¹¶å¤±è´¥ï¼šåˆå¹¶æ—¶æœ‰å†²çªå‘生。æç¤ºï¼šé‡‡ç”¨å…¶å®ƒåˆå¹¶ç­–ç•¥
pulls.merge_conflict_summary=错误信æ¯
pulls.rebase_conflict=åˆå¹¶å¤±è´¥ï¼šå˜åŸºæäº¤æœ‰å†²çªï¼š%[1]s。æç¤ºï¼šé‡‡ç”¨å…¶å®ƒåˆå¹¶ç­–ç•¥
@@ -1924,7 +1943,7 @@ pulls.rebase_conflict_summary=错误信æ¯
pulls.unrelated_histories=åˆå¹¶å¤±è´¥ï¼šä¸¤ä¸ªåˆ†æ”¯æ²¡æœ‰å…±åŒåކå²ã€‚æç¤ºï¼šå°è¯•ä¸åŒçš„ç­–ç•¥
pulls.merge_out_of_date=åˆå¹¶å¤±è´¥ï¼šåœ¨ç”Ÿæˆåˆå¹¶æ—¶ï¼Œä¸»åˆ†æ”¯å·²æ›´æ–°ã€‚æç¤ºï¼šå†è¯•一次。
pulls.head_out_of_date=åˆå¹¶å¤±è´¥ï¼šåœ¨ç”Ÿæˆåˆå¹¶æ—¶ï¼Œhead 已更新。æç¤ºï¼šå†è¯•一次。
-pulls.has_merged=失败:åˆå¹¶è¯·æ±‚å·²ç»è¢«åˆå¹¶ï¼Œæ‚¨ä¸èƒ½å†æ¬¡åˆå¹¶æˆ–更改目标分支。
+pulls.has_merged=失败:åˆå¹¶è¯·æ±‚å·²ç»åˆå¹¶ï¼Œæ‚¨ä¸èƒ½å†æ¬¡åˆå¹¶æˆ–更改目标分支。
pulls.push_rejected=推é€å¤±è´¥ï¼šæŽ¨é€è¢«æ‹’ç»ã€‚审查此仓库的 Git é’©å­ã€‚
pulls.push_rejected_summary=详细拒ç»ä¿¡æ¯
pulls.push_rejected_no_message=推é€å¤±è´¥ï¼šæ­¤æŽ¨é€è¢«æ‹’ç»ä½†æœªæä¾›å…¶ä»–ä¿¡æ¯ã€‚请检查此仓库的 Git é’©å­ã€‚
@@ -1948,12 +1967,12 @@ pulls.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闭此åˆå¹¶è¯·æ±‚
pulls.reopened_at=`釿–°æ‰“开此åˆå¹¶è¯·æ±‚ <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.cmd_instruction_hint=查看命令行æç¤º
pulls.cmd_instruction_checkout_title=检出
-pulls.cmd_instruction_checkout_desc=ä»Žä½ çš„ä»“åº“ä¸­æ£€å‡ºä¸€ä¸ªæ–°çš„åˆ†æ”¯å¹¶æµ‹è¯•å˜æ›´ã€‚
+pulls.cmd_instruction_checkout_desc=ä»Žæ‚¨çš„ä»“åº“ä¸­æ£€å‡ºä¸€ä¸ªæ–°çš„åˆ†æ”¯å¹¶æµ‹è¯•å˜æ›´ã€‚
pulls.cmd_instruction_merge_title=åˆå¹¶
pulls.cmd_instruction_merge_desc=åˆå¹¶å˜æ›´å¹¶æ›´æ–°åˆ° Gitea 上
-pulls.cmd_instruction_merge_warning=警告:此æ“作ä¸èƒ½åˆå¹¶è¯¥åˆå¹¶è¯·æ±‚,因为“自动检测手动åˆå¹¶â€æœªå¯ç”¨
+pulls.cmd_instruction_merge_warning=警告:此æ“作ä¸èƒ½åˆå¹¶è¯¥åˆå¹¶è¯·æ±‚,因为「自动检测手动åˆå¹¶ã€æœªå¯ç”¨
pulls.clear_merge_message=清除åˆå¹¶ä¿¡æ¯
-pulls.clear_merge_message_hint=清除åˆå¹¶æ¶ˆæ¯åªä¼šåˆ é™¤æäº¤æ¶ˆæ¯å†…容,并ä¿ç•™ç”Ÿæˆçš„ git 附加内容,如“Co-Authored-By …â€ã€‚
+pulls.clear_merge_message_hint=清除åˆå¹¶æ¶ˆæ¯åªä¼šåˆ é™¤æäº¤æ¶ˆæ¯å†…容,并ä¿ç•™ç”Ÿæˆçš„ Git 附加内容,如「Co-Authored-By…ã€ã€‚
pulls.auto_merge_button_when_succeed=(当检查æˆåŠŸæ—¶)
pulls.auto_merge_when_succeed=在所有检查æˆåŠŸåŽè‡ªåЍåˆå¹¶
@@ -1968,7 +1987,7 @@ pulls.auto_merge_newly_scheduled_comment=`已于 %[1]s 设置此åˆå¹¶è¯·æ±‚在æ
pulls.auto_merge_canceled_schedule_comment=`已于 %[1]s å–æ¶ˆäº†è‡ªåЍåˆå¹¶è®¾ç½® `
pulls.delete.title=删除此åˆå¹¶è¯·æ±‚?
-pulls.delete.text=你真的è¦åˆ é™¤è¿™ä¸ªåˆå¹¶è¯·æ±‚å—? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它)
+pulls.delete.text=您真的è¦åˆ é™¤è¿™ä¸ªåˆå¹¶è¯·æ±‚å—?(这将永久删除所有内容。如果您打算将内容存档,请考虑关闭它)
pulls.recently_pushed_new_branches=您已ç»äºŽ%[2]s推é€äº†åˆ†æ”¯ <strong>%[1]s</strong>
pulls.upstream_diverging_prompt_behind_1=该分支è½åŽäºŽ %[2]s %[1]d 个æäº¤
@@ -1977,7 +1996,7 @@ pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改
pulls.upstream_diverging_merge=åŒæ­¥æ´¾ç”Ÿ
pulls.upstream_diverging_merge_confirm=è¦å°† %[1]s åˆå¹¶åˆ° %[2]s å—?
-pull.deleted_branch=(已删除): %s
+pull.deleted_branch=(已删除):%s
pull.agit_documentation=查看有关 AGit 的文档
comments.edit.already_changed=无法ä¿å­˜å¯¹è¯„论的更改。其内容似乎已被其他用户更改。 请刷新页é¢å¹¶é‡æ–°ç¼–辑以é¿å…覆盖他们的更改
@@ -1986,7 +2005,7 @@ milestones.new=新的里程碑
milestones.closed=于 %s关闭
milestones.update_ago=已更新 %s
milestones.no_due_date=暂无截止日期
-milestones.open=å¼€å¯
+milestones.open=打开
milestones.close=关闭
milestones.new_subheader=里程碑å¯ä»¥å¸®åŠ©æ‚¨ç»„ç»‡å·¥å•并跟踪其进度。
milestones.completeness=<strong>%d%%</strong> 已完æˆ
@@ -1995,16 +2014,16 @@ milestones.title=标题
milestones.desc=æè¿°
milestones.due_date=截止日期(å¯é€‰ï¼‰
milestones.clear=清除
-milestones.invalid_due_date_format=到期时间的格å¼å¿…须是 'yyyy-mm-dd' 的形å¼ã€‚
-milestones.create_success=里程碑 %s 创建æˆåŠŸã€‚
+milestones.invalid_due_date_format=到期时间的格å¼å¿…须是「yyyy-mm-ddã€çš„å½¢å¼ã€‚
+milestones.create_success=里程碑「%sã€åˆ›å»ºæˆåŠŸã€‚
milestones.edit=编辑里程碑
milestones.edit_subheader=里程碑组织工å•,åˆå¹¶è¯·æ±‚和跟踪进度。
milestones.cancel=å–æ¶ˆ
milestones.modify=更新里程碑
-milestones.edit_success=里程碑 %s å·²ç»æ›´æ–°ã€‚
+milestones.edit_success=里程碑「%sã€æ›´æ–°æˆåŠŸã€‚
milestones.deletion=删除里程碑
milestones.deletion_desc=删除该里程碑将会移除所有工å•中相关的信æ¯ã€‚是å¦ç»§ç»­ï¼Ÿ
-milestones.deletion_success=里程碑已被删除。
+milestones.deletion_success=里程碑已删除。
milestones.filter_sort.name=åç§°
milestones.filter_sort.earliest_due_data=到期日从远到近
milestones.filter_sort.latest_due_date=到期日从近到远
@@ -2013,7 +2032,7 @@ milestones.filter_sort.most_complete=完æˆåº¦ä»Žé«˜åˆ°ä½Ž
milestones.filter_sort.most_issues=å·¥å•从多到少
milestones.filter_sort.least_issues=å·¥å•从少到多
-signing.will_sign=这个æäº¤å°†ç”¨å¯†é’¥ "%s" ç­¾å。
+signing.will_sign=这个æäº¤å°†ç”¨å¯†é’¥ã€Œ%sã€ç­¾å。
signing.wont_sign.error=在检查æäº¤æ˜¯å¦å¯ä»¥è¢«ç­¾å时出错。
signing.wont_sign.nokey=没有å¯ç”¨çš„密钥æ¥ç­¾ç½²è¿™ä¸ªæäº¤ã€‚
signing.wont_sign.never=æäº¤ä»Žæœªç­¾å。
@@ -2028,11 +2047,11 @@ signing.wont_sign.approved=åˆå¹¶å°†ä¸ä¼šè¢«ç­¾å,因为åˆå¹¶è¯·æ±‚未被æ‰
signing.wont_sign.not_signed_in=您还没有登录。
ext_wiki=访问外部百科
-ext_wiki.desc=链接到外部 wiki。
+ext_wiki.desc=链接到外部百科。
wiki=百科
-wiki.welcome=欢迎æ¥åˆ°ç™¾ç§‘!
-wiki.welcome_desc=百科å…许你撰写和与å作者分享文档
+wiki.welcome=欢迎æ¥åˆ°ç™¾ç§‘。
+wiki.welcome_desc=百科å…许您撰写和与å作者分享文档。
wiki.desc=撰写和与å作者分享文档
wiki.create_first_page=创建第一个页é¢
wiki.page=页é¢
@@ -2049,95 +2068,95 @@ wiki.file_revision=页é¢åކå²
wiki.wiki_page_revisions=页é¢åކå²
wiki.back_to_wiki=返回百科
wiki.delete_page_button=删除页é¢
-wiki.delete_page_notice_1=ç™¾ç§‘é¡µé¢ %s åˆ é™¤åŽæ— æ³•æ¢å¤ï¼Œæ˜¯å¦ç»§ç»­ï¼Ÿ
+wiki.delete_page_notice_1=百科页é¢ã€Œ%sã€åˆ é™¤åŽæ— æ³•æ¢å¤ï¼Œæ˜¯å¦ç»§ç»­ï¼Ÿ
wiki.page_already_exists=相åŒåç§°çš„ Wiki 页é¢å·²ç»å­˜åœ¨ã€‚
-wiki.reserved_page=百科页é¢åç§° %s 是被ä¿ç•™çš„。
+wiki.reserved_page=百科页é¢å称「%sã€æ˜¯ä¿ç•™çš„。
wiki.pages=所有页é¢
wiki.last_updated=æœ€åŽæ›´æ–°äºŽ %s
-wiki.page_name_desc=输入此 Wiki 页é¢çš„å称。特殊å称有:'Home', '_Sidebar' å’Œ '_Footer'。
+wiki.page_name_desc=输入此百科页é¢çš„å称。特殊å称有:「Homeã€ï¼Œã€Œ_Sidebarã€å’Œã€Œ_Footerã€ã€‚
wiki.original_git_entry_tooltip=查看原始的 Git æ–‡ä»¶è€Œä¸æ˜¯ä½¿ç”¨å‹å¥½é“¾æŽ¥ã€‚
-activity=动æ€
+activity=活动
activity.navbar.pulse=活动
activity.navbar.code_frequency=代ç é¢‘率
activity.navbar.contributors=贡献者
-activity.navbar.recent_commits=最近的æäº¤
+activity.navbar.recent_commits=最近æäº¤
activity.period.filter_label=周期:
activity.period.daily=1 天
activity.period.halfweekly=3 天
-activity.period.weekly=1周
-activity.period.monthly=1 个月
-activity.period.quarterly=3个月
-activity.period.semiyearly=6 个月
-activity.period.yearly=1å¹´
+activity.period.weekly=1 周
+activity.period.monthly=1 月
+activity.period.quarterly=3 月
+activity.period.semiyearly=6 月
+activity.period.yearly=1 å¹´
activity.overview=概览
-activity.active_prs_count_1=<strong>%d</strong> åˆå¹¶è¯·æ±‚
-activity.active_prs_count_n=<strong>%d</strong> åˆå¹¶è¯·æ±‚
-activity.merged_prs_count_1=åˆå¹¶è¯·æ±‚
-activity.merged_prs_count_n=åˆå¹¶è¯·æ±‚
-activity.opened_prs_count_1=æ–°åˆå¹¶è¯·æ±‚
-activity.opened_prs_count_n=æ–°åˆå¹¶è¯·æ±‚
-activity.title.user_1=%d 用户
-activity.title.user_n=%d 用户
-activity.title.prs_1=%d åˆå¹¶è¯·æ±‚
-activity.title.prs_n=%d åˆå¹¶è¯·æ±‚
+activity.active_prs_count_1=<strong>%d</strong> 个åˆå¹¶è¯·æ±‚
+activity.active_prs_count_n=<strong>%d</strong> 个åˆå¹¶è¯·æ±‚
+activity.merged_prs_count_1=å·²åˆå¹¶çš„åˆå¹¶è¯·æ±‚
+activity.merged_prs_count_n=å·²åˆå¹¶çš„åˆå¹¶è¯·æ±‚
+activity.opened_prs_count_1=创建的åˆå¹¶è¯·æ±‚
+activity.opened_prs_count_n=创建的åˆå¹¶è¯·æ±‚
+activity.title.user_1=%d ä½ç”¨æˆ·
+activity.title.user_n=%d ä½ç”¨æˆ·
+activity.title.prs_1=%d 个åˆå¹¶è¯·æ±‚
+activity.title.prs_n=%d 个åˆå¹¶è¯·æ±‚
activity.title.prs_merged_by=%[2]s ç”± %[1]s åˆå¹¶
activity.title.prs_opened_by=%[2]s 创建了 %[1]s
activity.merged_prs_label=å·²åˆå¹¶
activity.opened_prs_label=已创建
-activity.active_issues_count_1=<strong>%d</strong> å·¥å•
-activity.active_issues_count_n=<strong>%d</strong> å·¥å•
+activity.active_issues_count_1=<strong>%d</strong> å¼ å·¥å•
+activity.active_issues_count_n=<strong>%d</strong> å¼ å·¥å•
activity.closed_issues_count_1=已关闭的工å•
activity.closed_issues_count_n=已关闭的工å•
-activity.title.issues_1=%d å·¥å•
-activity.title.issues_n=%d å·¥å•
-activity.title.issues_closed_from=%s 从 %s 被关闭
+activity.title.issues_1=%d å¼ å·¥å•
+activity.title.issues_n=%d å¼ å·¥å•
+activity.title.issues_closed_from=%s 从 %s 关闭
activity.title.issues_created_by=%[2]s 创建了 %[1]s
activity.closed_issue_label=已关闭
-activity.new_issues_count_1=创建工å•
-activity.new_issues_count_n=创建工å•
+activity.new_issues_count_1=已打开的工å•
+activity.new_issues_count_n=已打开的工å•
activity.new_issue_label=打开的
activity.title.unresolved_conv_1=%d 未解决的会è¯
activity.title.unresolved_conv_n=%d 未解决的会è¯
activity.unresolved_conv_desc=这些最近更新的工å•å’Œåˆå¹¶è¯·æ±‚还没有解决。
activity.unresolved_conv_label=打开
-activity.title.releases_1=%d 版本å‘布
-activity.title.releases_n=%d 版本å‘布
+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_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_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 行
+activity.git_stats_deletion_1=%d 行删除
+activity.git_stats_deletion_n=%d 行删除
contributors.contribution_type.filter_label=贡献类型:
contributors.contribution_type.commits=æäº¤
-contributors.contribution_type.additions=更多
+contributors.contribution_type.additions=新增
contributors.contribution_type.deletions=删除
settings=设置
-settings.desc=设置是你å¯ä»¥ç®¡ç†ä»“库设置的地方
+settings.desc=设置是您å¯ä»¥ç®¡ç†ä»“库设置的地方
settings.options=仓库
settings.public_access=公开访问
-settings.public_access_desc=é…置公共访客访问æƒé™ä»¥è¦†ç›–此存储库的默认值。
-settings.public_access.docs.not_set=未设置:没有é¢å¤–的公共访问æƒé™ã€‚访客æƒé™éµå¾ªå­˜å‚¨åº“çš„å¯è§æ€§å’Œæˆå‘˜æƒé™ã€‚
+settings.public_access_desc=é…置公共访客访问æƒé™ä»¥è¦†ç›–此仓库的默认值。
+settings.public_access.docs.not_set=未设置:没有é¢å¤–的公共访问æƒé™ã€‚访客æƒé™éµå¾ªä»“库的å¯è§æ€§å’Œæˆå‘˜æƒé™ã€‚
settings.public_access.docs.anonymous_read=匿åå¯è¯»ï¼šæœªç™»å½•的用户å¯ä»¥é€šè¿‡è¯»å–æƒé™è®¿é—®å•元。
settings.public_access.docs.everyone_read=所有人å¯è¯»ï¼šæ‰€æœ‰ç™»å½•用户都å¯ä»¥é€šè¿‡è¯»å–æƒé™è®¿é—®å•元。读å–问题/拉å–请求å•元的æƒé™ä¹Ÿæ„味ç€ç”¨æˆ·å¯ä»¥åˆ›å»ºæ–°çš„问题/拉å–请求。
settings.public_access.docs.everyone_write=所有人å¯å†™ï¼šæ‰€æœ‰ç™»å½•用户都有写入æƒé™ã€‚åªæœ‰ç™¾ç§‘æ”¯æŒæ­¤æƒé™ã€‚
@@ -2147,6 +2166,7 @@ settings.collaboration.write=å¯å†™æƒé™
settings.collaboration.read=å¯è¯»æƒé™
settings.collaboration.owner=所有者
settings.collaboration.undefined=未定义
+settings.collaboration.per_unit=å•å…ƒæƒé™
settings.hooks=Web é’©å­
settings.githooks=ç®¡ç† Git é’©å­
settings.basic_settings=基本设置
@@ -2154,14 +2174,14 @@ settings.mirror_settings=镜åƒè®¾ç½®
settings.mirror_settings.docs=è®¾ç½®æ‚¨çš„ä»“åº“ä»¥è‡ªåŠ¨åŒæ­¥å¦ä¸€ä¸ªä»“库的æäº¤ã€æ ‡ç­¾å’Œåˆ†æ”¯ã€‚
settings.mirror_settings.docs.disabled_pull_mirror.instructions=设置您的项目以自动将æäº¤ã€æ ‡ç­¾å’Œåˆ†æ”¯æŽ¨é€åˆ°å¦ä¸€ä¸ªä»“库。您的站点管ç†å‘˜å·²ç¦ç”¨äº†æ‹‰å–镜åƒã€‚
settings.mirror_settings.docs.disabled_push_mirror.instructions=å°†æ‚¨çš„é¡¹ç›®è®¾ç½®ä¸ºè‡ªåŠ¨ä»Žä¸€ä¸ªä»“åº“æ‹‰å–æäº¤ã€æ ‡ç­¾å’Œåˆ†æ”¯ã€‚
-settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=现在,这åªèƒ½åœ¨â€œè¿ç§»å¤–部仓库â€èœå•中完æˆã€‚欲了解更多信æ¯ï¼Œè¯·å’¨è¯¢ï¼š
+settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=现在,这åªèƒ½åœ¨ã€Œè¿ç§»å¤–部仓库ã€èœå•中完æˆã€‚欲了解更多信æ¯ï¼Œè¯·å‚考:
settings.mirror_settings.docs.disabled_push_mirror.info=您的站点管ç†å‘˜å·²ç¦ç”¨æŽ¨é€é•œåƒã€‚
settings.mirror_settings.docs.no_new_mirrors=您的仓库将镜åƒåŒæ­¥å¦ä¸€ä¸ªä»“库的更改。请注æ„,您现在ä¸èƒ½åˆ›å»ºä»»ä½•新的镜åƒã€‚
settings.mirror_settings.docs.can_still_use=虽然您ä¸èƒ½ä¿®æ”¹çŽ°æœ‰é•œåƒæˆ–创建新镜åƒï¼Œä½†æ‚¨ä»ç„¶å¯ä»¥ä½¿ç”¨å·²å­˜åœ¨çš„镜åƒã€‚
settings.mirror_settings.docs.pull_mirror_instructions=è¦åˆ›å»ºä¸€ä¸ªæ‹‰å–镜åƒï¼Œè¯·å‚阅:
settings.mirror_settings.docs.more_information_if_disabled=您å¯ä»¥åœ¨è¿™é‡Œæ‰¾åˆ°æ›´å¤šå…³äºŽæŽ¨é€å’Œæ‹‰å–镜åƒçš„ä¿¡æ¯ï¼š
settings.mirror_settings.docs.doc_link_title=如何镜åƒä»“库?
-settings.mirror_settings.docs.doc_link_pull_section=文档中的 “从远程仓库拉å–†部分。
+settings.mirror_settings.docs.doc_link_pull_section=文档中的「从远程仓库拉å–ã€éƒ¨åˆ†ã€‚
settings.mirror_settings.docs.pulling_remote_title=从远程仓库拉å–代ç 
settings.mirror_settings.mirrored_repository=镜åƒåº“
settings.mirror_settings.pushed_repository=推é€ä»“库
@@ -2189,22 +2209,22 @@ settings.use_internal_wiki=使用内置百科
settings.default_wiki_branch_name=默认百科分支åç§°
settings.failed_to_change_default_wiki_branch=更改百科默认分支失败。
settings.use_external_wiki=使用外部百科
-settings.external_wiki_url=外部 Wiki 链接
+settings.external_wiki_url=外部百科链接
settings.external_wiki_url_error=外部百科链接无效
-settings.external_wiki_url_desc=当点击百科标签时,访问者将被é‡å®šå‘到外部百科系统的URL。
+settings.external_wiki_url_desc=当点击百科标签时,访问者将被é‡å®šå‘到外部百科系统的 URL。
settings.issues_desc=å¯ç”¨å·¥å•系统
-settings.use_internal_issue_tracker=使用内置的轻é‡çº§å·¥å•管ç†ç³»ç»Ÿ
-settings.use_external_issue_tracker=使用外部的工å•管ç†ç³»ç»Ÿ
+settings.use_internal_issue_tracker=使用内置工å•系统
+settings.use_external_issue_tracker=使用外部工å•系统
settings.external_tracker_url=外部工å•系统 URL
settings.external_tracker_url_error=外部百科链接无效
-settings.external_tracker_url_desc=å½“ç‚¹å‡»å·¥å•æ ‡ç­¾æ—¶ï¼Œè®¿é—®è€…将被é‡å®šå‘到外部工å•系统的URL。
+settings.external_tracker_url_desc=å½“ç‚¹å‡»å·¥å•æ ‡ç­¾æ—¶ï¼Œè®¿é—®è€…将被é‡å®šå‘到外部工å•系统的 URL。
settings.tracker_url_format=外部工å•管ç†ç³»ç»Ÿçš„ URL æ ¼å¼
settings.tracker_url_format_error=外部工å•链接无效
settings.tracker_issue_style=外部工å•管ç†ç³»ç»Ÿçš„ç¼–å·æ ¼å¼
settings.tracker_issue_style.numeric=纯数字形å¼
settings.tracker_issue_style.alphanumeric=è‹±æ–‡å­—æ¯æ•°å­—组åˆå½¢å¼
settings.tracker_issue_style.regexp=正则表达å¼
-settings.tracker_issue_style.regexp_pattern=æ­£åˆ™è¡¨è¾¾å¼æ¨¡å¼
+settings.tracker_issue_style.regexp_pattern=正则表达å¼
settings.tracker_issue_style.regexp_pattern_desc=第一个被æ•获的组将å–代 <code>{index}</code>。
settings.tracker_url_format_desc=使用å ä½ç¬¦ <code>{user}</code>, <code>{repo}</code> å’Œ <code>{index}</code> 作为用户åã€ä»“库å和工å•索引。
settings.enable_timetracker=å¯ç”¨æ—¶é—´è·Ÿè¸ª
@@ -2214,15 +2234,15 @@ settings.pulls.ignore_whitespace=忽略空白冲çª
settings.pulls.enable_autodetect_manual_merge=å¯ç”¨è‡ªåŠ¨æ£€æµ‹æ‰‹åŠ¨åˆå¹¶ (注æ„:在æŸäº›ç‰¹æ®Šæƒ…况下å¯èƒ½å‘生错误判断)
settings.pulls.allow_rebase_update=å…许通过å˜åŸºæ›´æ–°åˆå¹¶è¯·æ±‚分支
settings.pulls.default_delete_branch_after_merge=默认åˆå¹¶åŽåˆ é™¤åˆå¹¶è¯·æ±‚分支
-settings.pulls.default_allow_edits_from_maintainers=默认开å¯å…许维护者编辑
-settings.releases_desc=å¯ç”¨å‘布
+settings.pulls.default_allow_edits_from_maintainers=默认å…许维护者编辑
+settings.releases_desc=å¯ç”¨ä»“库å‘布
settings.packages_desc=å¯ç”¨ä»“库软件包注册中心
settings.projects_desc=å¯ç”¨é¡¹ç›®
settings.projects_mode_desc=é¡¹ç›®æ¨¡å¼ (è¦æ˜¾ç¤ºçš„项目类型)
settings.projects_mode_repo=仅仓库项目
settings.projects_mode_owner=ä»…é™ç”¨æˆ·æˆ–组织项目
settings.projects_mode_all=所有项目
-settings.actions_desc=å¯ç”¨ Actions
+settings.actions_desc=å¯ç”¨ä»“库工作æµ
settings.admin_settings=管ç†å‘˜è®¾ç½®
settings.admin_enable_health_check=å¯ç”¨ä»“库å¥åº·æ£€æŸ¥ (git fsck)
settings.admin_code_indexer=代ç ç´¢å¼•器
@@ -2245,23 +2265,23 @@ settings.convert_fork_notices_1=该æ“作会将派生仓库转æ¢ä¸ºæ™®é€šä»“库
settings.convert_fork_confirm=转æ¢ä»“库
settings.convert_fork_succeed=此派生仓库已ç»è½¬æ¢ä¸ºæ™®é€šä»“库。
settings.transfer=转移仓库所有æƒ
-settings.transfer.rejected=代ç åº“转移被拒ç»ã€‚
-settings.transfer.success=代ç åº“转移æˆåŠŸã€‚
-settings.transfer.blocked_user=无法传输仓库,因为您被新的所有者å±è”½ã€‚
+settings.transfer.rejected=仓库转移被拒ç»ã€‚
+settings.transfer.success=仓库转移æˆåŠŸã€‚
+settings.transfer.blocked_user=无法转移仓库,因为您已被新所有者å±è”½ã€‚
settings.transfer_abort=å–æ¶ˆè½¬ç§»
-settings.transfer_abort_invalid=ä½ ä¸èƒ½å–消ä¸å­˜åœ¨çš„代ç åº“转移。
-settings.transfer_abort_success=æˆåŠŸå–æ¶ˆäº†å°†ä»£ç åº“转让给 %s。
+settings.transfer_abort_invalid=您ä¸èƒ½å–消ä¸å­˜åœ¨çš„仓库转移。
+settings.transfer_abort_success=æˆåŠŸå–æ¶ˆå°†ä»“库转移给 %s。
settings.transfer_desc=您å¯ä»¥å°†ä»“库转移至您拥有管ç†å‘˜æƒé™çš„叿ˆ·æˆ–组织。
-settings.transfer_form_title=输入仓库å称以åšç¡®è®¤:
-settings.transfer_in_progress=当剿­£åœ¨è¿›è¡Œè½¬è®©ã€‚ 如果你想将此代ç åº“转让给å¦ä¸€ä¸ªç”¨æˆ·ï¼Œè¯·å–消它。
-settings.transfer_notices_1=- 如果将此仓库转移给其他用户, 您将失去对此仓库的访问æƒé™ã€‚
-settings.transfer_notices_2=-如果将其转移到您 (å…±åŒ) 拥有的组织,您å¯ä»¥ç»§ç»­è®¿é—®è¯¥ä»“库。
-settings.transfer_notices_3=- å¦‚æžœä»“åº“æ˜¯ç§æœ‰çš„并且被转移给æŸä¸ªç”¨æˆ·ï¼Œé‚£ä¹ˆæ­¤æ“作å¯ä»¥ç¡®ä¿è¯¥ç”¨æˆ·è‡³å°‘具有读æƒé™(以åŠå¿…è¦æ—¶çš„æ›´æ”¹æƒé™)。
-settings.transfer_notices_4=- 如果存储库属于æŸä¸ªç»„织,而您将其转移给å¦ä¸€ä¸ªç»„织或个人,那么您将失去存储库工å•与其组织项目系统之间的链接。
+settings.transfer_form_title=输入仓库å称以确认:
+settings.transfer_in_progress=当剿­£åœ¨è¿›è¡Œè½¬ç§»ã€‚ 如果您想将此仓库转移给å¦ä¸€ä¸ªç”¨æˆ·ï¼Œè¯·å–消它。
+settings.transfer_notices_1=- 如果将此仓库转移给其他用户,您将失去对此仓库的访问æƒé™ã€‚
+settings.transfer_notices_2=- 如果将其转移到您(共åŒï¼‰æ‹¥æœ‰çš„组织,您å¯ä»¥ç»§ç»­è®¿é—®è¯¥ä»“库。
+settings.transfer_notices_3=- å¦‚æžœä»“åº“æ˜¯ç§æœ‰çš„并且被转移给æŸä¸ªç”¨æˆ·ï¼Œé‚£ä¹ˆæ­¤æ“作å¯ä»¥ç¡®ä¿è¯¥ç”¨æˆ·è‡³å°‘具有读æƒé™ï¼ˆä»¥åŠå¿…è¦æ—¶çš„æ›´æ”¹æƒé™ï¼‰ã€‚
+settings.transfer_notices_4=- 如果仓库属于æŸä¸ªç»„织,而您将其转移给å¦ä¸€ä¸ªç»„织或个人,那么您将失去仓库工å•与其组织项目系统之间的链接。
settings.transfer_owner=新拥有者
-settings.transfer_perform=执行转让
-settings.transfer_started=该代ç åº“已被标记为转让并等待æ¥è‡ª %s 的确认
-settings.transfer_succeed=仓库已被转移。
+settings.transfer_perform=执行转移
+settings.transfer_started=该仓库已标记为转移并等待æ¥è‡ª %s 的确认
+settings.transfer_succeed=仓库已转移。
settings.signing_settings=ç­¾å验è¯è®¾ç½®
settings.trust_model=ç­¾å信任模型
settings.trust_model.default=默认信任模型
@@ -2271,7 +2291,7 @@ settings.trust_model.collaborator.long=å作者:信任å作者的签å
settings.trust_model.collaborator.desc=此仓库中å作者的有效签å将被标记为「å¯ä¿¡ã€(æ— è®ºå®ƒä»¬æ˜¯å¦æ˜¯æäº¤è€…),签ååªç¬¦åˆæäº¤è€…时将标记为「ä¸å¯ä¿¡ã€ï¼Œéƒ½ä¸åŒ¹é…时标记为「ä¸åŒ¹é…ã€ã€‚
settings.trust_model.committer=æäº¤è€…
settings.trust_model.committer.long=æäº¤è€…: 信任与æäº¤è€…相符的签å (此特性类似 GitHub,这会强制采用 Gitea 作为æäº¤è€…和签å者)
-settings.trust_model.committer.desc=有效签ååªæœ‰å’Œæäº¤è€…ç›¸åŒ¹é…æ‰ä¼šè¢«æ ‡è®°ä¸ºâ€œå—ä¿¡ä»»â€ï¼Œå¦åˆ™å®ƒä»¬å°†è¢«æ ‡è®°ä¸ºâ€œä¸åŒ¹é…â€ã€‚这强制 Gitea æˆä¸ºç­¾åæäº¤çš„æäº¤è€…,而实际æäº¤è€…被加上 Co-authored-by: å’Œ Co-committed-by: 的标记。 默认的 Gitea å¯†é’¥å¿…é¡»åŒ¹é…æ•°æ®åº“中的一å用户。
+settings.trust_model.committer.desc=有效签ååªæœ‰å’Œæäº¤è€…ç›¸åŒ¹é…æ‰ä¼šè¢«æ ‡è®°ä¸ºã€Œå—ä¿¡ä»»ã€ï¼Œå¦åˆ™å®ƒä»¬å°†è¢«æ ‡è®°ä¸ºã€Œä¸åŒ¹é…ã€ã€‚这强制 Gitea æˆä¸ºç­¾åæäº¤çš„æäº¤è€…,而实际æäº¤è€…被加上 Co-authored-by: å’Œ Co-committed-by: 的标记。 默认的 Gitea å¯†é’¥å¿…é¡»åŒ¹é…æ•°æ®åº“中的一å用户。
settings.trust_model.collaboratorcommitter=å作者+æäº¤è€…
settings.trust_model.collaboratorcommitter.long=å作者+æäº¤è€…:信任åä½œè€…åŒæ—¶æ˜¯æäº¤è€…的签å
settings.trust_model.collaboratorcommitter.desc=此仓库中å作者的有效签ååœ¨ä»–åŒæ—¶æ˜¯æäº¤è€…时将被标记为「å¯ä¿¡ã€ï¼Œç­¾ååªåŒ¹é…了æäº¤è€…时将标记为「ä¸å¯ä¿¡ã€ï¼Œéƒ½ä¸åŒ¹é…时标记为「ä¸åŒ¹é…ã€ã€‚这会强制 Gitea æˆä¸ºç­¾å者和æäº¤è€…,实际的æäº¤è€…将被标记于æäº¤æ¶ˆæ¯ç»“尾处的「Co-Authored-By:ã€å’Œã€ŒCo-Committed-By:ã€ã€‚默认的 Gitea ç­¾åå¯†é’¥å¿…é¡»åŒ¹é…æ•°æ®åº“中的一个用户密钥。
@@ -2282,19 +2302,19 @@ settings.confirm_wiki_delete=删除百科数æ®
settings.wiki_deletion_success=仓库百科数æ®åˆ é™¤æˆåŠŸï¼
settings.delete=删除本仓库
settings.delete_desc=删除仓库是永久性的, 无法撤消。
-settings.delete_notices_1=- æ­¤æ“作 <strong>ä¸å¯ä»¥</strong> 被回滚。
+settings.delete_notices_1=- æ­¤æ“作 <strong>无法</strong> 被回滚。
settings.delete_notices_2=- æ­¤æ“作将永久删除仓库 <strong>%s</strong>,包括 Git æ•°æ®ã€ å·¥å•ã€è¯„论ã€ç™¾ç§‘å’Œå作者的æ“作æƒé™ã€‚
settings.delete_notices_fork_1=- 在此仓库删除åŽï¼Œå®ƒçš„æ´¾ç”Ÿä»“åº“å°†å˜æˆç‹¬ç«‹ä»“库。
-settings.deletion_success=仓库已被删除。
+settings.deletion_success=仓库已删除。
settings.update_settings_success=仓库设置已更新。
-settings.update_settings_no_unit=该代ç åº“应该至少å…许æŸç§å½¢å¼çš„交互。
+settings.update_settings_no_unit=该仓库应至少å…许æŸç§å½¢å¼çš„交互。
settings.confirm_delete=删除本仓库
settings.add_collaborator=增加å作者
settings.add_collaborator_success=å作者添加æˆåŠŸï¼
settings.add_collaborator_inactive_user=无法添加未激活的用户作为åˆä½œè€…。
settings.add_collaborator_owner=ä¸èƒ½å°†æ‰€æœ‰è€…添加为å作者。
-settings.add_collaborator_duplicate=åˆä½œè€…å·²ç»è¢«æ·»åŠ åˆ°æœ¬ä»“åº“ã€‚
-settings.add_collaborator.blocked_user=此写作者被仓库所有者å±è”½ï¼Œå之亦然。
+settings.add_collaborator_duplicate=åä½œè€…å·²ç»æ·»åŠ åˆ°æœ¬ä»“åº“ã€‚
+settings.add_collaborator.blocked_user=æ­¤å作者被仓库所有者å±è”½ï¼Œå之亦然。
settings.delete_collaborator=删除
settings.collaborator_deletion=删除å作者
settings.collaborator_deletion_desc=删除å作者åŽä»–将无法å†å¯¹æ­¤ä»“库的访问。继续?
@@ -2308,25 +2328,25 @@ settings.add_team_duplicate=å›¢é˜Ÿå·²ç»æ‹¥æœ‰ä»“库
settings.add_team_success=团队现在å¯ä»¥è®¿é—®ä»“库。
settings.change_team_permission_tip=团队æƒé™è®¾ç½®äºŽå›¢é˜Ÿè®¾ç½®é¡µé¢ï¼Œä¸èƒ½æ ¹æ®ä»“库更改
settings.delete_team_tip=è¯¥å›¢é˜Ÿä»æœ‰ä»“库, 无法删除
-settings.remove_team_success=团队访问仓库的æƒé™å·²è¢«åˆ é™¤ã€‚
+settings.remove_team_success=团队访问仓库的æƒé™å·²åˆ é™¤ã€‚
settings.add_webhook=添加 Web é’©å­
-settings.add_webhook.invalid_channel_name=Webhook 通é“åç§°ä¸èƒ½ä¸ºç©ºä¸”ä¸èƒ½ä»…包å«ä¸€ä¸ª # 字符。
-settings.hooks_desc=当Gitea事件å‘生时,Webé’©å­è‡ªåЍå‘出HTTP POST请求。在 <a target="_blank" rel="noopener noreferrer" href="%s"> 指å—</a> 中阅读更多内容。
+settings.add_webhook.invalid_channel_name=Web é’©å­é€šé“åç§°ä¸èƒ½ä¸ºç©ºä¸”ä¸èƒ½ä»…包å«ä¸€ä¸ª # 字符。
+settings.hooks_desc=当 Gitea 事件å‘生时,Web é’©å­è‡ªåЍå‘出 HTTP POST 请求。在 <a target="_blank" rel="noopener noreferrer" href="%s"> 指å—</a> 中阅读更多内容。
settings.webhook_deletion=删除 Web é’©å­
-settings.webhook_deletion_desc=删除 webé’©å­ å°†åˆ é™¤å…¶è®¾ç½®å’ŒåŽ†å²è®°å½•。继续?
+settings.webhook_deletion_desc=删除 Web é’©å­å°†åˆ é™¤å…¶è®¾ç½®å’Œåކå²è®°å½•。继续?
settings.webhook_deletion_success=Web é’©å­åˆ é™¤æˆåŠŸï¼
settings.webhook.test_delivery=测试推é€
-settings.webhook.test_delivery_desc=用å‡äº‹ä»¶æµ‹è¯•这个 webé’©å­ã€‚
-settings.webhook.test_delivery_desc_disabled=è¦ç”¨ 虚å‡äº‹ä»¶ 测试这个Webhook,请激活它。
+settings.webhook.test_delivery_desc=用å‡äº‹ä»¶æµ‹è¯•这个 Web é’©å­ã€‚
+settings.webhook.test_delivery_desc_disabled=è¦ç”¨å‡äº‹ä»¶æµ‹è¯•这个 Webé’©å­ï¼Œè¯·æ¿€æ´»å®ƒã€‚
settings.webhook.request=请求内容
settings.webhook.response=å“应内容
settings.webhook.headers=头信æ¯
settings.webhook.payload=内容
settings.webhook.body=å“应体
-settings.webhook.replay.description=釿”¾æ­¤ webhook。
-settings.webhook.replay.description_disabled=è‹¥è¦é‡æ’­æ­¤ WebHook,请激活它。
-settings.webhook.delivery.success=一个事件已被添加到推é€é˜Ÿåˆ—。å¯èƒ½éœ€è¦è¿‡å‡ ç§’é’Ÿæ‰ä¼šæ˜¾ç¤ºåœ¨æŽ¨é€è®°å½•中。
-settings.githooks_desc=Git Hook 是 Git 本身æä¾›çš„功能。您å¯ä»¥åœ¨ä¸‹æ–¹ç¼–辑 hook 文件以设置自定义æ“作。
+settings.webhook.replay.description=釿”¾æ­¤ Web é’©å­ã€‚
+settings.webhook.replay.description_disabled=è‹¥è¦é‡æ–°è¿è¡Œæ­¤ Web é’©å­ï¼Œè¯·æ¿€æ´»å®ƒã€‚
+settings.webhook.delivery.success=一个事件已添加到推é€é˜Ÿåˆ—。å¯èƒ½éœ€è¦è¿‡å‡ ç§’é’Ÿæ‰ä¼šæ˜¾ç¤ºåœ¨æŽ¨é€è®°å½•中。
+settings.githooks_desc=Git é’©å­æ˜¯ Git 本身æä¾›çš„功能。您å¯ä»¥åœ¨ä¸‹æ–¹ç¼–辑 hook 文件以设置自定义æ“作。
settings.githook_edit_desc=å¦‚æžœé’©å­æœªå¯åŠ¨ï¼Œåˆ™ä¼šæ˜¾ç¤ºæ ·ä¾‹æ–‡ä»¶ä¸­çš„å†…å®¹ã€‚å¦‚æžœæƒ³è¦åˆ é™¤æŸä¸ªé’©å­ï¼Œåˆ™æäº¤ç©ºç™½æ–‡æœ¬å³å¯ã€‚
settings.githook_name=é’©å­åç§°
settings.githook_content=é’©å­æ–‡æœ¬
@@ -2334,8 +2354,8 @@ settings.update_githook=æ›´æ–°é’©å­è®¾ç½®
settings.add_webhook_desc=Gitea å°†å‘目标 URL å‘é€å…·æœ‰æŒ‡å®šå†…容类型的 <code>POST</code> 请求。在 <a target="_blank" rel="noopener noreferrer" href="%s">webhooks 指å—</a> 中阅读更多内容。
settings.payload_url=目标 URL
settings.http_method=HTTP 方法
-settings.content_type=POST Content Type
-settings.secret=密钥文本
+settings.content_type=POST 内容类型
+settings.secret=密钥
settings.slack_username=æœåŠ¡åç§°
settings.slack_icon_url=图标 URL
settings.slack_color=颜色
@@ -2351,12 +2371,12 @@ settings.event_create_desc=创建分支或标签
settings.event_delete=刪除
settings.event_delete_desc=分支或标签已删除。
settings.event_fork=派生
-settings.event_fork_desc=仓库被派生。
+settings.event_fork_desc=仓库已派生。
settings.event_wiki=百科
settings.event_wiki_desc=创建ã€é‡å‘½åã€ç¼–辑或删除了百科页é¢ã€‚
settings.event_statuses=状æ€
settings.event_statuses_desc=已从 API æ›´æ–°æäº¤çжæ€ã€‚
-settings.event_release=版本å‘布
+settings.event_release=å‘布
settings.event_release_desc=å‘å¸ƒã€æ›´æ–°æˆ–删除版本时。
settings.event_push=推é€
settings.event_force_push=强制推é€
@@ -2367,43 +2387,45 @@ settings.event_header_issue=å·¥å•事件
settings.event_issues=å·¥å•
settings.event_issues_desc=å·¥å•已打开ã€å·²å…³é—­ã€å·²é‡æ–°æ‰“开或已编辑。
settings.event_issue_assign=å·¥å•已指派
-settings.event_issue_assign_desc=å·¥å•å·²è¢«æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚
-settings.event_issue_label=已标记工å•
-settings.event_issue_label_desc=工啿 ‡ç­¾è¢«æ›´æ–°æˆ–清除。
-settings.event_issue_milestone=å·¥å•被收入里程碑中
-settings.event_issue_milestone_desc=å·¥å•è¢«æ”¶å…¥æˆ–å–æ¶ˆæ”¶å…¥é‡Œç¨‹ç¢‘中。
+settings.event_issue_assign_desc=å·¥å•å·²æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚
+settings.event_issue_label=å·¥å•增删标签
+settings.event_issue_label_desc=工啿 ‡ç­¾å·²æ›´æ–°æˆ–清除。
+settings.event_issue_milestone=å·¥å•已收入里程碑中
+settings.event_issue_milestone_desc=å·¥å•å·²æ”¶å…¥æˆ–å–æ¶ˆæ”¶å…¥é‡Œç¨‹ç¢‘中。
settings.event_issue_comment=å·¥å•评论
-settings.event_issue_comment_desc=å·¥å•评论被创建ã€ç¼–辑或删除
+settings.event_issue_comment_desc=å·¥å•评论已创建ã€ç¼–辑或删除。
settings.event_header_pull_request=åˆå¹¶è¯·æ±‚事件
settings.event_pull_request=åˆå¹¶è¯·æ±‚
-settings.event_pull_request_desc=åˆå¹¶è¯·æ±‚被打开ã€è¢«å…³é—­ã€è¢«é‡æ–°æ‰“开或被编辑。
-settings.event_pull_request_assign=åˆå¹¶è¯·æ±‚被指派
-settings.event_pull_request_assign_desc=åˆå¹¶è¯·æ±‚è¢«æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚
-settings.event_pull_request_label=åˆå¹¶è¯·æ±‚被贴上标签
-settings.event_pull_request_label_desc=åˆå¹¶è¯·æ±‚的标签被更新或清除。
-settings.event_pull_request_milestone=åˆå¹¶è¯·æ±‚被记录于里程碑中
-settings.event_pull_request_milestone_desc=åˆå¹¶è¯·æ±‚è¢«è®°å½•æˆ–å–æ¶ˆè®°å½•于里程碑中。
-settings.event_pull_request_comment=åˆå¹¶è¯·æ±‚被评论
-settings.event_pull_request_comment_desc=åˆå¹¶è¯·æ±‚评论被创建ã€ç¼–辑或删除。
+settings.event_pull_request_desc=åˆå¹¶è¯·æ±‚已打开ã€å…³é—­ã€é‡æ–°æ‰“开或编辑。
+settings.event_pull_request_assign=åˆå¹¶è¯·æ±‚已指派
+settings.event_pull_request_assign_desc=åˆå¹¶è¯·æ±‚å·²æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚
+settings.event_pull_request_label=åˆå¹¶è¯·æ±‚增删标签
+settings.event_pull_request_label_desc=åˆå¹¶è¯·æ±‚的标签已更新或清除。
+settings.event_pull_request_milestone=åˆå¹¶è¯·æ±‚已记录于里程碑中
+settings.event_pull_request_milestone_desc=åˆå¹¶è¯·æ±‚å·²è®°å½•æˆ–å–æ¶ˆè®°å½•于里程碑中。
+settings.event_pull_request_comment=åˆå¹¶è¯·æ±‚已评论
+settings.event_pull_request_comment_desc=åˆå¹¶è¯·æ±‚评论已创建ã€ç¼–辑或删除。
settings.event_pull_request_review=已审核的åˆå¹¶è¯·æ±‚
-settings.event_pull_request_review_desc=åˆå¹¶è¯·æ±‚è¢«æ‰¹å‡†ã€æ‹’ç»æˆ–æå‡ºå®¡æŸ¥æ„è§
-settings.event_pull_request_sync=åˆå¹¶è¯·æ±‚è¢«åŒæ­¥
-settings.event_pull_request_sync_desc=åˆå¹¶è¯·æ±‚è¢«åŒæ­¥ã€‚
+settings.event_pull_request_review_desc=åˆå¹¶è¯·æ±‚å·²æ‰¹å‡†ã€æ‹’ç»æˆ–æå‡ºå®¡æŸ¥æ„è§ã€‚
+settings.event_pull_request_sync=åˆå¹¶è¯·æ±‚å·²åŒæ­¥
+settings.event_pull_request_sync_desc=åˆå¹¶è¯·æ±‚å·²åŒæ­¥ã€‚
settings.event_pull_request_review_request=å‘èµ·åˆå¹¶è¯·æ±‚评审
settings.event_pull_request_review_request_desc=åˆå¹¶è¯·æ±‚è¯„å®¡å·²è¯·æ±‚æˆ–å·²å–æ¶ˆ
settings.event_pull_request_approvals=åˆå¹¶è¯·æ±‚批准
settings.event_pull_request_merge=åˆå¹¶è¯·æ±‚åˆå¹¶
settings.event_header_workflow=工作æµç¨‹äº‹ä»¶
+settings.event_workflow_run=工作æµè¿è¡Œ
+settings.event_workflow_run_desc=Gitea 工作æµé˜Ÿåˆ—中ã€ç­‰å¾…ä¸­ã€æ­£åœ¨è¿›è¡Œæˆ–已完æˆçš„任务。
settings.event_workflow_job=工作æµä»»åŠ¡
-settings.event_workflow_job_desc=Gitea Actions 工作æµé˜Ÿåˆ—中ã€ç­‰å¾…ä¸­ã€æ­£åœ¨è¿›è¡Œæˆ–已完æˆä»»åŠ¡ã€‚
+settings.event_workflow_job_desc=Gitea 工作æµé˜Ÿåˆ—中ã€ç­‰å¾…ä¸­ã€æ­£åœ¨è¿›è¡Œæˆ–已完æˆçš„任务。
settings.event_package=软件包
-settings.event_package_desc=软件包已在仓库中被创建或删除。
+settings.event_package_desc=软件包在仓库中已创建或删除。
settings.branch_filter=分支过滤
-settings.branch_filter_desc=推é€ã€åˆ›å»ºï¼Œåˆ é™¤åˆ†æ”¯äº‹ä»¶çš„分支白åå•,使用 glob 模å¼åŒ¹é…指定。若为空或 <code>*</code>ï¼Œåˆ™å°†æŠ¥å‘Šæ‰€æœ‰åˆ†æ”¯çš„äº‹ä»¶ã€‚è¯­æ³•æ–‡æ¡£è§ <a href="%[1]s">%[2]s</a>。示例:<code>master</code>,<code>{master,release*}</code>。
+settings.branch_filter_desc=推é€ã€åˆ›å»ºï¼Œåˆ é™¤åˆ†æ”¯äº‹ä»¶çš„分支白åå•,使用 glob 表达å¼åŒ¹é…指定。若为空或 <code>*</code>ï¼Œåˆ™ä¼šæŠ¥å‘Šæ‰€æœ‰åˆ†æ”¯çš„äº‹ä»¶ã€‚è¯­æ³•æ–‡æ¡£è§ <a href="%[1]s">%[2]s</a>。示例:<code>master</code>,<code>{master,release*}</code>。
settings.authorization_header=æŽˆæƒæ ‡å¤´
settings.authorization_header_desc=å½“å­˜åœ¨æ—¶å°†è¢«ä½œä¸ºæŽˆæƒæ ‡å¤´åŒ…å«åœ¨å†…。例如: %s。
settings.active=激活
-settings.active_helper=触å‘事件的信æ¯å°†å‘é€åˆ°æ­¤ webhook 网å€ã€‚
+settings.active_helper=触å‘事件的信æ¯å°†å‘é€åˆ°æ­¤ Web é’©å­ URL。
settings.add_hook_success=Web é’©å­æ·»åŠ æˆåŠŸï¼
settings.update_webhook=æ›´æ–° Web é’©å­
settings.update_hook_success=Web é’©å­æ›´æ–°æˆåŠŸï¼
@@ -2413,7 +2435,7 @@ settings.hook_type=é’©å­ç±»åž‹
settings.slack_token=令牌
settings.slack_domain=域å
settings.slack_channel=频é“
-settings.add_web_hook_desc=å°† <a target="_blank" rel="noreferrer" href="%s">%s</a>集æˆåˆ°æ‚¨çš„代ç åº“。
+settings.add_web_hook_desc=å°† <a target="_blank" rel="noreferrer" href="%s">%s</a> 集æˆåˆ°æ‚¨çš„仓库。
settings.web_hook_name_gitea=Gitea
settings.web_hook_name_gogs=Gogs
settings.web_hook_name_slack=Slack
@@ -2440,7 +2462,7 @@ settings.title=标题
settings.deploy_key_content=密钥文本
settings.key_been_used=具有相åŒå†…容的部署密钥已在使用中。
settings.key_name_used=使用相åŒå称的部署密钥已ç»å­˜åœ¨ï¼
-settings.add_key_success=部署密钥 %s 添加æˆåŠŸã€‚
+settings.add_key_success=部署密钥「%sã€æ·»åŠ æˆåŠŸã€‚
settings.deploy_key_deletion=删除部署密钥
settings.deploy_key_deletion_desc=åˆ é™¤éƒ¨ç½²å¯†é’¥å°†å–æ¶ˆæ­¤å¯†é’¥å¯¹æ­¤ä»“库的访问æƒé™ã€‚继续?
settings.deploy_key_deletion_success=部署密钥已删除。
@@ -2449,11 +2471,11 @@ settings.protected_branch=åˆ†æ”¯ä¿æŠ¤
settings.protected_branch.save_rule=ä¿å­˜è§„则
settings.protected_branch.delete_rule=删除规则
settings.protected_branch_can_push=是å¦å…许推é€ï¼Ÿ
-settings.protected_branch_can_push_yes=ä½ å¯ä»¥æŽ¨
-settings.protected_branch_can_push_no=ä½ ä¸èƒ½æŽ¨é€
-settings.branch_protection=分支 '<b>%s</b>' çš„ä¿æŠ¤è§„åˆ™
+settings.protected_branch_can_push_yes=您å¯ä»¥æŽ¨é€
+settings.protected_branch_can_push_no=您ä¸èƒ½æŽ¨é€
+settings.branch_protection=分支「<b>%s</b>ã€çš„ä¿æŠ¤è§„åˆ™
settings.protect_this_branch=å¯ç”¨åˆ†æ”¯ä¿æŠ¤
-settings.protect_this_branch_desc=阻止删除并é™åˆ¶Git推é€å’Œåˆå¹¶åˆ°åˆ†æ”¯ã€‚
+settings.protect_this_branch_desc=阻止删除并é™åˆ¶ Git 推é€å’Œåˆå¹¶åˆ°åˆ†æ”¯ã€‚
settings.protect_disable_push=ç¦ç”¨æŽ¨é€
settings.protect_disable_push_desc=此分支ä¸å…许推é€ã€‚
settings.protect_disable_force_push=ç¦ç”¨å¼ºåˆ¶æŽ¨é€
@@ -2479,13 +2501,13 @@ settings.protect_merge_whitelist_committers_desc=ä»…å…许白åå•用户或团é
settings.protect_merge_whitelist_users=åˆå¹¶ç™½åå•用户:
settings.protect_merge_whitelist_teams=åˆå¹¶ç™½åå•团队:
settings.protect_check_status_contexts=å¯ç”¨çŠ¶æ€æ£€æŸ¥
-settings.protect_status_check_patterns=çŠ¶æ€æ£€æŸ¥æ¨¡å¼ï¼š
-settings.protect_status_check_patterns_desc=输入模å¼ï¼ŒæŒ‡å®šå“ªäº›çŠ¶æ€æ£€æŸ¥å¿…须通过,æ‰èƒ½å°†åˆ†æ”¯åˆå¹¶åˆ°ç¬¦åˆæ­¤è§„则的分支中去。æ¯ä¸€è¡ŒæŒ‡å®šä¸€ä¸ªæ¨¡å¼ï¼Œæ¨¡å¼ä¸èƒ½ä¸ºç©ºã€‚
+settings.protect_status_check_patterns=çŠ¶æ€æ£€æŸ¥è¡¨è¾¾å¼ï¼š
+settings.protect_status_check_patterns_desc=输入表达å¼ä»¥æŒ‡å®šåœ¨åˆ†æ”¯åˆå¹¶åˆ°åŒ¹é…此规则的分支之å‰å¿…é¡»é€šè¿‡å“ªäº›çŠ¶æ€æ£€æŸ¥ã€‚æ¯ä¸€è¡ŒæŒ‡å®šä¸€ä¸ªè¡¨è¾¾å¼ä¸”表达å¼ä¸èƒ½ä¸ºç©ºã€‚
settings.protect_check_status_contexts_desc=è¦æ±‚çŠ¶æ€æ£€æŸ¥é€šè¿‡æ‰èƒ½åˆå¹¶ã€‚如果å¯ç”¨ï¼Œæäº¤å¿…须先推é€åˆ°å¦ä¸€ä¸ªåˆ†æ”¯ï¼Œç„¶åŽå†åˆå¹¶æˆ–推é€åˆ°åŒ¹é…è¿™äº›ä¿æŠ¤è§„åˆ™çš„åˆ†æ”¯ã€‚å¦‚æžœæ²¡æœ‰é€‰æ‹©å…·ä½“çš„çŠ¶æ€æ£€æŸ¥ä¸Šä¸‹æ–‡ï¼Œåˆ™æ‰€æœ‰çš„çŠ¶æ€æ£€æŸ¥éƒ½é€šè¿‡æ‰èƒ½åˆå¹¶ã€‚
settings.protect_check_status_contexts_list=æ­¤ä»“åº“ä¸Šå‘¨è¿›è¡Œè¿‡çš„çŠ¶æ€æ£€æŸ¥
settings.protect_status_check_matched=匹é…
-settings.protect_invalid_status_check_pattern=æ— æ•ˆçš„çŠ¶æ€æ£€æŸ¥è§„则:“%sâ€ã€‚
-settings.protect_no_valid_status_check_patterns=æ²¡æœ‰æœ‰æ•ˆçš„çŠ¶æ€æ£€æŸ¥è§„则。
+settings.protect_invalid_status_check_pattern=æ— æ•ˆçš„çŠ¶æ€æ£€æŸ¥è¡¨è¾¾å¼ï¼šã€Œ%sã€ã€‚
+settings.protect_no_valid_status_check_patterns=æ²¡æœ‰æœ‰æ•ˆçš„çŠ¶æ€æ£€æŸ¥è¡¨è¾¾å¼ã€‚
settings.protect_required_approvals=所需的批准:
settings.protect_required_approvals_desc=åªå…许åˆå¹¶æœ‰è¶³å¤Ÿå®¡æ ¸çš„åˆå¹¶è¯·æ±‚ã€‚è¦æ±‚的审核必须æ¥è‡ªç™½å啿ˆ–者有æƒé™çš„用户或团队。
settings.protect_approvals_whitelist_enabled=仅列入白åå•的用户或团队æ‰å¯æ‰¹å‡†
@@ -2498,18 +2520,18 @@ settings.ignore_stale_approvals=忽略过期批准
settings.ignore_stale_approvals_desc=对旧æäº¤ï¼ˆè¿‡æœŸå®¡æ ¸ï¼‰çš„æ‰¹å‡†å°†ä¸è®¡å…¥ PR 的批准数。如果过期审查已被驳回,则与此无关。
settings.require_signed_commits=需è¦ç­¾åæäº¤
settings.require_signed_commits_desc=æ‹’ç»æŽ¨é€æœªç­¾å或无法验è¯çš„æäº¤åˆ°åˆ†æ”¯
-settings.protect_branch_name_pattern=å—ä¿æŠ¤çš„åˆ†æ”¯å称模å¼
-settings.protect_branch_name_pattern_desc=åˆ†æ”¯ä¿æŠ¤çš„å称匹é…规则。语法请å‚阅 <a href="%s">文档</a> 。如:main, release/**
-settings.protect_patterns=规则
-settings.protect_protected_file_patterns=å—ä¿æŠ¤çš„æ–‡ä»¶æ¨¡å¼(ä½¿ç”¨åˆ†å· ';' 分隔):
-settings.protect_protected_file_patterns_desc=å³ä½¿ç”¨æˆ·æœ‰æƒæ·»åŠ ã€ç¼–辑或删除此分支中的文件,也ä¸å…许直接更改å—ä¿æŠ¤çš„æ–‡ä»¶ã€‚ å¯ä»¥ä½¿ç”¨åˆ†å· (';') 分隔多个模å¼ã€‚ è§<a href='%[1]s'>%[2]s</a>文档了解模å¼è¯­æ³•。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
-settings.protect_unprotected_file_patterns=ä¸å—ä¿æŠ¤çš„æ–‡ä»¶æ¨¡å¼(ä½¿ç”¨åˆ†å· ';' 分隔):
-settings.protect_unprotected_file_patterns_desc=如果用户有写æƒé™ï¼Œåˆ™å…许直接更改的ä¸å—ä¿æŠ¤çš„æ–‡ä»¶ï¼Œä»¥ç»•è¿‡æŽ¨é€é™åˆ¶ã€‚å¯ä»¥ä½¿ç”¨åˆ†å·åˆ†éš”å¤šä¸ªæ¨¡å¼ (';')。 è§ <a href='%[1]s'>%[2]s</a> 文档了解模å¼è¯­æ³•。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
+settings.protect_branch_name_pattern=å—ä¿æŠ¤çš„åˆ†æ”¯å称表达å¼
+settings.protect_branch_name_pattern_desc=åˆ†æ”¯ä¿æŠ¤çš„å称匹é…表达å¼ã€‚语法请å‚阅 <a href="%s">文档</a> 。如:main, release/**
+settings.protect_patterns=表达å¼
+settings.protect_protected_file_patterns=å—ä¿æŠ¤çš„æ–‡ä»¶è¡¨è¾¾å¼ï¼ˆä½¿ç”¨åˆ†å·ã€Œ;ã€åˆ†éš”):
+settings.protect_protected_file_patterns_desc=å³ä½¿ç”¨æˆ·æœ‰æƒæ·»åŠ ã€ç¼–辑或删除此分支中的文件,也ä¸å…许直接更改å—ä¿æŠ¤çš„æ–‡ä»¶ã€‚ å¯ä»¥ä½¿ç”¨åˆ†å·ã€Œ;ã€åˆ†éš”多个表达å¼ã€‚ è§<a href='%[1]s'>%[2]s</a>文档了解表达å¼è¯­æ³•。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
+settings.protect_unprotected_file_patterns=ä¸å—ä¿æŠ¤çš„æ–‡ä»¶è¡¨è¾¾å¼ï¼ˆä½¿ç”¨åˆ†å·ã€Œ;ã€åˆ†éš”):
+settings.protect_unprotected_file_patterns_desc=如果用户有写æƒé™ï¼Œåˆ™å…许直接更改的ä¸å—ä¿æŠ¤çš„æ–‡ä»¶ï¼Œä»¥ç»•è¿‡æŽ¨é€é™åˆ¶ã€‚å¯ä»¥ä½¿ç”¨åˆ†å·åˆ†éš”多个表达å¼ã€Œ;ã€ã€‚ è§ <a href='%[1]s'>%[2]s</a> 文档了解表达å¼è¯­æ³•。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
settings.add_protected_branch=å¯ç”¨ä¿æŠ¤
settings.delete_protected_branch=ç¦ç”¨ä¿æŠ¤
-settings.update_protect_branch_success=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ %s æ›´æ–°æˆåŠŸã€‚
-settings.remove_protected_branch_success=ç§»é™¤åˆ†æ”¯ä¿æŠ¤è§„åˆ™"%s"æˆåŠŸã€‚
-settings.remove_protected_branch_failed=ç§»é™¤åˆ†æ”¯ä¿æŠ¤è§„åˆ™"%s"失败。
+settings.update_protect_branch_success=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ã€Œ%sã€æ›´æ–°æˆåŠŸã€‚
+settings.remove_protected_branch_success=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ã€Œ%sã€ç§»é™¤æˆåŠŸã€‚
+settings.remove_protected_branch_failed=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ã€Œ%sã€ç§»é™¤å¤±è´¥ã€‚
settings.protected_branch_deletion=åˆ é™¤åˆ†æ”¯ä¿æŠ¤
settings.protected_branch_deletion_desc=ç¦ç”¨åˆ†æ”¯ä¿æŠ¤å…许具有写入æƒé™çš„ç”¨æˆ·æŽ¨é€æäº¤åˆ°æ­¤åˆ†æ”¯ã€‚ç»§ç»­ï¼Ÿ
settings.block_rejected_reviews=æ‹’ç»å®¡æ ¸é˜»æ­¢äº†æ­¤åˆå¹¶
@@ -2530,15 +2552,15 @@ settings.protected_branch_required_rule_name=必须填写规则åç§°
settings.protected_branch_duplicate_rule_name=规则å称已存在
settings.protected_branch_required_approvals_min=所需的审批数ä¸èƒ½ä¸ºè´Ÿæ•°ã€‚
settings.tags=标签
-settings.tags.protection=Gitæ ‡ç­¾ä¿æŠ¤
-settings.tags.protection.pattern=Git标签模å¼
+settings.tags.protection=æ ‡ç­¾ä¿æŠ¤
+settings.tags.protection.pattern=标签表达å¼
settings.tags.protection.allowed=å…许列表
settings.tags.protection.allowed.users=å…许的账å·
settings.tags.protection.allowed.teams=å…许的团队
settings.tags.protection.allowed.noone=æ— 
-settings.tags.protection.create=ä¿æŠ¤Git标签
-settings.tags.protection.none=没有å—ä¿æŠ¤çš„Git标签
-settings.tags.protection.pattern.description=ä½ å¯ä»¥ä½¿ç”¨å•个å称或 glob 模å¼åŒ¹é…æˆ–æ­£åˆ™è¡¨è¾¾å¼æ¥åŒ¹é…多个标签。了解详情请访问 <a target="_blank" rel="noopener" href="%s">ä¿æŠ¤Git标签指å—</a>。
+settings.tags.protection.create=ä¿æŠ¤æ ‡ç­¾
+settings.tags.protection.none=没有å—ä¿æŠ¤çš„æ ‡ç­¾ã€‚
+settings.tags.protection.pattern.description=您å¯ä»¥ä½¿ç”¨å•个å称或 glob 表达å¼åŒ¹é…æˆ–æ­£åˆ™è¡¨è¾¾å¼æ¥åŒ¹é…多个标签。了解详情请访问 <a target="_blank" rel="noopener" href="%s">ä¿æŠ¤æ ‡ç­¾æŒ‡å—</a>。
settings.bot_token=Bot 令牌
settings.chat_id=èŠå¤© ID
settings.thread_id=线程 ID
@@ -2559,13 +2581,13 @@ settings.visibility.error=试图更改仓库å¯è§æ€§æ—¶å‡ºé”™ã€‚
settings.visibility.fork_error=无法更改派生仓库的å¯è§æ€§ã€‚
settings.archive.button=归档仓库
settings.archive.header=归档此仓库
-settings.archive.text=归档仓库将使其完全åªè¯»ã€‚它将在首页éšè—。没有人(甚至你ï¼ï¼‰èƒ½å¤Ÿè¿›è¡Œæ–°çš„æäº¤ï¼Œæˆ–打开工å•åŠåˆå¹¶è¯·æ±‚。
+settings.archive.text=归档仓库将使其完全åªè¯»ã€‚它将在首页éšè—。没有人(包括您)能够进行新的æäº¤ï¼Œæˆ–打开工å•åŠåˆå¹¶è¯·æ±‚。
settings.archive.success=仓库已æˆåŠŸå½’æ¡£ã€‚
settings.archive.error=仓库在归档时出现异常。请通过日志获å–详细信æ¯ã€‚
settings.archive.error_ismirror=请ä¸è¦å¯¹é•œåƒä»“库归档,谢谢ï¼
settings.archive.branchsettings_unavailable=已归档仓库无法进行分支设置。
-settings.archive.tagsettings_unavailable=已归档仓库的Git标签设置ä¸å¯ç”¨ã€‚
-settings.archive.mirrors_unavailable=如果仓库已被归档,镜åƒå°†ä¸å¯ç”¨ã€‚
+settings.archive.tagsettings_unavailable=已归档仓库的标签设置ä¸å¯ç”¨ã€‚
+settings.archive.mirrors_unavailable=如果仓库已归档,镜åƒå°†ä¸å¯ç”¨ã€‚
settings.unarchive.button=撤销仓库归档
settings.unarchive.header=撤销此仓库归档
settings.unarchive.text=撤销归档将æ¢å¤ä»“库接收æäº¤ã€æŽ¨é€ï¼Œä»¥åŠæ–°å·¥å•å’Œåˆå¹¶è¯·æ±‚的能力。
@@ -2619,7 +2641,7 @@ diff.whitespace_show_everything=显示所有更改
diff.whitespace_ignore_all_whitespace=比较行时忽略空白符å·
diff.whitespace_ignore_amount_changes=å¿½ç•¥ç©ºç™½ç¬¦å·æ•°é‡çš„å˜åŒ–
diff.whitespace_ignore_at_eol=忽略行末空白符å·çš„æ›´æ”¹
-diff.stats_desc=共有 <strong> %d 个文件被更改</strong>,包括 <strong>%d 次æ’å…¥</strong> å’Œ <strong>%d 次删除</strong>
+diff.stats_desc=å…±<strong>修改 %d 个文件</strong>ï¼ŒåŒ…å« <strong>%d 行新增</strong>å’Œ <strong>%d 行删除</strong>
diff.stats_desc_file=å˜æ›´ %d 行:新增 %d 行,删除 %d 行
diff.bin=二进制
diff.bin_not_shown=二进制文件未显示。
@@ -2663,22 +2685,22 @@ diff.submodule_deleted=å­æ¨¡å— %[1]s 已从 %[2]s 中删除
diff.submodule_updated=å­æ¨¡å— %[1]s 已更新:%[2]s
releases.desc=跟踪项目版本和下载。
-release.releases=版本å‘布
+release.releases=å‘布
release.detail=å‘布详情
-release.tags=Git标签
+release.tags=标签
release.new_release=å‘布新版
release.draft=è‰ç¨¿
-release.prerelease=预å‘行
+release.prerelease=预å‘布
release.stable=稳定
-release.latest=最新版本
+release.latest=最新
release.compare=比较
release.edit=编辑
release.ahead.commits=<strong>%d</strong> 次æäº¤
-release.ahead.target=在此版本å‘布åŽè¢«åŠ å…¥åˆ° %s
+release.ahead.target=在此版本å‘布åŽå·²åŠ å…¥åˆ° %s
tag.ahead.target=自此标签到 %s
release.source_code=æºä»£ç 
-release.new_subheader=版本å‘布组织项目的版本。
-release.edit_subheader=版本å‘布组织项目的版本。
+release.new_subheader=å‘布组织项目的版本。
+release.edit_subheader=å‘布组织项目的版本。
release.tag_name=标签åç§°
release.target=目标分支
release.tag_helper=选择一个存在的标签或者创建新标签。
@@ -2687,74 +2709,74 @@ release.tag_helper_existing=现有标签。
release.title=å‘布标题
release.title_empty=标题ä¸èƒ½ä¸ºç©ºã€‚
release.message=æè¿°è¿™ä¸ªç‰ˆæœ¬
-release.prerelease_desc=标记为预å‘行
+release.prerelease_desc=标记为预å‘布
release.prerelease_helper=标记此版本ä¸é€‚åˆç”Ÿäº§ä½¿ç”¨ã€‚
release.cancel=å–æ¶ˆ
release.publish=å‘布版本
release.save_draft=ä¿å­˜è‰ç¨¿
release.edit_release=ä¿å­˜æ­¤æ¬¡å‘布
release.delete_release=删除å‘布
-release.delete_tag=删除 Git标签
+release.delete_tag=删除标签
release.deletion=删除å‘布
-release.deletion_desc=删除版本å‘布åªä¼šä»Ž Gitea 中移除。这ä¸ä¼šå½±å“ Git çš„æ ‡ç­¾ä»¥åŠæ‚¨ä»“库的内容和历å²ã€‚是å¦ç»§ç»­ï¼Ÿ
-release.deletion_success=Release已被删除。
-release.deletion_tag_desc=将从仓库中删除此 Git标签。仓库内容和历å²è®°å½•ä¿æŒä¸å˜ã€‚ç»§ç»­å—?
-release.deletion_tag_success=该 Git标签 å·²ç»è¢«åˆ é™¤
-release.tag_name_already_exist=使用此标签åç§°çš„å‘布版本已ç»å­˜åœ¨ã€‚
+release.deletion_desc=删除å‘布åªä¼šä»Ž Gitea 中移除å‘布。这ä¸ä¼šå½±å“ Git çš„æ ‡ç­¾ä»¥åŠæ‚¨ä»“库的内容和历å²ã€‚是å¦ç»§ç»­ï¼Ÿ
+release.deletion_success=该å‘布已删除。
+release.deletion_tag_desc=将从仓库中删除此标签。仓库内容和历å²è®°å½•ä¿æŒä¸å˜ã€‚ç»§ç»­å—?
+release.deletion_tag_success=该标签已删除。
+release.tag_name_already_exist=使用此标签åç§°çš„å‘布已ç»å­˜åœ¨ã€‚
release.tag_name_invalid=标签å称无效。
-release.tag_name_protected=Git标签åç§°å·²å—ä¿æŠ¤ã€‚
-release.tag_already_exist=æ­¤ Git标签 å称已存在
+release.tag_name_protected=标签åå·²å—ä¿æŠ¤ã€‚
+release.tag_already_exist=此标签å已存在。
release.downloads=下载附件
release.download_count=下载:%s
release.add_tag_msg=使用å‘布的标题和内容作为标签消æ¯ã€‚
release.add_tag=仅创建标签
-release.releases_for=%s 的版本å‘布
+release.releases_for=%s çš„å‘布
release.tags_for=%s 的标签
branch.name=分支åç§°
-branch.already_exists=å为 %s 的分支已存在。
+branch.already_exists=å为「%sã€çš„分支已存在。
branch.delete_head=刪除
-branch.delete=删除分支 %s
+branch.delete=删除分支「%sã€
branch.delete_html=删除分支
branch.delete_desc=åˆ é™¤åˆ†æ”¯æ˜¯æ°¸ä¹…çš„ã€‚è™½ç„¶å·²åˆ é™¤çš„åˆ†æ”¯åœ¨å®žé™…è¢«åˆ é™¤å‰æœ‰å¯èƒ½ä¼šçŸ­æ—¶é—´å­˜åœ¨ï¼Œä½†è¿™åœ¨å¤§å¤šæ•°æƒ…况下无法撤销。是å¦ç»§ç»­ï¼Ÿ
-branch.deletion_success=分支 %s 已被删除。
-branch.deletion_failed=删除分支 %s 失败。
-branch.delete_branch_has_new_commits=因为åˆå¹¶ä¹‹åŽæœ‰æ–°çš„æäº¤ï¼Œåˆ†æ”¯ %s 无法被删除。
+branch.deletion_success=分支「%sã€åˆ é™¤æˆåŠŸã€‚
+branch.deletion_failed=分支「%sã€åˆ é™¤å¤±è´¥ã€‚
+branch.delete_branch_has_new_commits=因为åˆå¹¶ä¹‹åŽæœ‰æ–°çš„æäº¤ï¼Œåˆ†æ”¯ã€Œ%sã€æ— æ³•删除。
branch.create_branch=创建分支 %s
-branch.create_from=从 %s
-branch.create_success=分支 '%s' 已创建。
-branch.branch_already_exists=此仓库已存在å为 %s 的分支。
-branch.branch_name_conflict=分支åç§°"%s"与已存在的分支"%s"冲çªã€‚
-branch.tag_collision=分支 %s ä¸èƒ½è¢«åˆ›å»ºå› ä¸ºåŒå的标签已ç»å­˜åœ¨ã€‚
+branch.create_from=从「%sã€
+branch.create_success=分支「%sã€å·²åˆ›å»ºã€‚
+branch.branch_already_exists=此仓库已存在å为「%sã€çš„分支。
+branch.branch_name_conflict=分支å称「%sã€ä¸Žå·²å­˜åœ¨çš„分支「%sã€å†²çªã€‚
+branch.tag_collision=分支「%sã€æ— æ³•创建,因为åŒå的标签已ç»å­˜åœ¨ã€‚
branch.deleted_by=删除人:%s
-branch.restore_success=分支 "%s"已还原。
-branch.restore_failed=还原分支 "%s"失败。
-branch.protected_deletion_failed=ä¸èƒ½åˆ é™¤å—ä¿æŠ¤çš„åˆ†æ”¯ "%s"。
-branch.default_deletion_failed=ä¸èƒ½åˆ é™¤é»˜è®¤åˆ†æ”¯"%s"。
-branch.default_branch_not_exist=默认分支 %s ä¸å­˜åœ¨ã€‚
-branch.restore=`还原分支 "%s"`
-branch.download=`下载分支 "%s"`
-branch.rename=`é‡å‘½å分支 "%s"`
+branch.restore_success=分支「%sã€å·²è¿˜åŽŸã€‚
+branch.restore_failed=分支「%sã€è¿˜åŽŸå¤±è´¥ã€‚
+branch.protected_deletion_failed=ä¸èƒ½åˆ é™¤å—ä¿æŠ¤çš„åˆ†æ”¯ã€Œ%sã€ã€‚
+branch.default_deletion_failed=ä¸èƒ½åˆ é™¤é»˜è®¤åˆ†æ”¯ã€Œ%sã€ã€‚
+branch.default_branch_not_exist=默认分支「%sã€ä¸å­˜åœ¨ã€‚
+branch.restore=还原分支「%sã€
+branch.download=下载分支「%sã€
+branch.rename=é‡å‘½å分支「%sã€
branch.included_desc=此分支是默认分支的一部分
branch.included=已包å«
branch.create_new_branch=从下列分支创建分支:
branch.confirm_create_branch=创建分支
branch.warning_rename_default_branch=您正在é‡å‘½å默认分支。
-branch.rename_branch_to=é‡å‘½å %s 为:
+branch.rename_branch_to=é‡å‘½å「%sã€ä¸ºï¼š
branch.confirm_rename_branch=é‡å‘½å分支
branch.create_branch_operation=创建分支
branch.new_branch=创建新分支
-branch.new_branch_from=基于"%s"创建新分支
-branch.renamed=分支 %s 被é‡å‘½å为 %s。
+branch.new_branch_from=基于「%sã€åˆ›å»ºæ–°åˆ†æ”¯
+branch.renamed=分支 %s å·²é‡å‘½å为 %s。
branch.rename_default_or_protected_branch_error=åªæœ‰ç®¡ç†å‘˜èƒ½é‡å‘½å默认分支和å—ä¿æŠ¤çš„åˆ†æ”¯ã€‚
branch.rename_protected_branch_failed=此分支å—到 glob è¯­æ³•è§„åˆ™çš„ä¿æŠ¤ã€‚
tag.create_tag=创建标签 %s
tag.create_tag_operation=创建标签
tag.confirm_create_tag=创建标签
-tag.create_tag_from=基于"%s"创建新标签
+tag.create_tag_from=基于「%sã€åˆ›å»ºæ–°æ ‡ç­¾
-tag.create_success=标签"%s"已存在
+tag.create_success=标签「%sã€å·²å­˜åœ¨ã€‚
topic.manage_topics=管ç†ä¸»é¢˜
topic.done=ä¿å­˜
@@ -2772,7 +2794,7 @@ error.broken_git_hook=此仓库的 Git é’©å­ä¼¼ä¹Žå·²æŸå。 请按照 <a tar
[graphs]
component_loading=正在加载 %s...
component_loading_failed=无法加载 %s
-component_loading_info=è¿™å¯èƒ½éœ€è¦ä¸€ç‚¹â€¦
+component_loading_info=è¿™å¯èƒ½éœ€è¦ä¸€ç‚¹æ—¶é—´â€¦
component_failed_to_load=æ„外的错误å‘生了。
code_frequency.what=代ç é¢‘率
contributors.what=贡献
@@ -2801,14 +2823,15 @@ team_permission_desc=æƒé™
team_unit_desc=å…许访问仓库å•å…ƒ
team_unit_disabled=(å·²ç¦ç”¨)
-form.name_reserved=组织åç§° '%s' 是被ä¿ç•™çš„。
-form.name_pattern_not_allowed=仓库å称中ä¸å…许使用 "%s"。
+form.name_been_taken=组织å称「%sã€å·²ç»è¢«å ç”¨ã€‚
+form.name_reserved=组织å称「%sã€æ˜¯ä¿ç•™çš„。
+form.name_pattern_not_allowed=组织å中ä¸å…许使用「%sã€æ ¼å¼ã€‚
form.create_org_not_allowed=此账å·ç¦æ­¢åˆ›å»ºç»„织
settings=组织设置
settings.options=组织
settings.full_name=组织全å
-settings.email=è”系电å­é‚®ä»¶
+settings.email=è”系邮箱
settings.website=网站
settings.location=所在地区
settings.permission=æƒé™
@@ -2822,15 +2845,28 @@ settings.visibility.private_shortname=ç§æœ‰
settings.update_settings=更新组织设置
settings.update_setting_success=组织设置已更新。
-settings.change_orgname_prompt=注æ„:更改组织åç§°åŒæ—¶ä¼šæ›´æ”¹ç»„织的 URL 地å€å¹¶é‡Šæ”¾æ—§çš„å称。
-settings.change_orgname_redirect_prompt=在被人使用å‰ï¼Œæ—§ç”¨æˆ·å将会被é‡å®šå‘。
+
+settings.rename=修改组织åç§°
+settings.rename_desc=更改组织åç§°åŒæ—¶ä¼šæ›´æ”¹ç»„织的 URL 地å€å¹¶é‡Šæ”¾æ—§çš„å称。
+settings.rename_success=组织 %[1]s å·²æˆåŠŸé‡å‘½å为 %[2]s。
+settings.rename_no_change=组织å称没有å˜åŒ–。
+settings.rename_new_org_name=新组织åç§°
+settings.rename_failed=由于内部错误,é‡å‘½å组织失败
+settings.rename_notices_1=æ­¤æ“作 <strong>无法</strong> 被回滚。
+settings.rename_notices_2=在被人使用å‰ï¼Œæ—§å称将会被é‡å®šå‘。
+
settings.update_avatar_success=组织头åƒå·²ç»æ›´æ–°ã€‚
settings.delete=删除组织
settings.delete_account=删除当å‰ç»„织
-settings.delete_prompt=删除æ“作会永久清除该组织的信æ¯ï¼Œå¹¶ä¸” <strong>ä¸å¯æ¢å¤</strong>ï¼
+settings.delete_prompt=删除æ“作会永久清除该组织的信æ¯ï¼Œå¹¶ä¸” <strong>无法</strong> æ¢å¤ï¼
+settings.name_confirm=输入组织å称以确认:
+settings.delete_notices_1=æ­¤æ“作 <strong>无法</strong> 被回滚。
+settings.delete_notices_2=æ­¤æ“作将永久删除 <strong>%s</strong> 的所有<strong>仓库</strong>,包括 Git æ•°æ®ã€ å·¥å•ã€è¯„论ã€ç™¾ç§‘å’Œå作者的æ“作æƒé™ã€‚
+settings.delete_notices_3=æ­¤æ“作将永久删除 <strong>%s</strong> 的所有 <strong>软件包</strong>。
+settings.delete_notices_4=æ­¤æ“作将永久删除 <strong>%s</strong> 的所有 <strong>项目</strong>。
settings.confirm_delete_account=确认删除组织
-settings.delete_org_title=删除组织
-settings.delete_org_desc=此组织将会被永久删除,确认继续å—?
+settings.delete_failed=由于内部错误,删除组织失败
+settings.delete_successful=组织 <b>%s</b> å·²æˆåŠŸåˆ é™¤ã€‚
settings.hooks_desc=在此处添加的 Web é’©å­å°†ä¼šåº”用到该组织下的 <strong>所有仓库</strong>。
settings.labels_desc=添加能够被该组织下的 <strong>所有仓库</strong> 的工å•使用的标签。
@@ -2876,7 +2912,7 @@ teams.invite_team_member=邀请加入 %s
teams.invite_team_member.list=待处ç†çš„邀请
teams.delete_team_title=删除团队
teams.delete_team_desc=删除一个团队将删除团队æˆå‘˜çš„访问æƒé™ï¼Œç»§ç»­ï¼Ÿ
-teams.delete_team_success=该团队已被删除。
+teams.delete_team_success=该团队已删除。
teams.read_permission_desc=该团队拥有对所属仓库的 <strong>读å–</strong> æƒé™ï¼Œå›¢é˜Ÿæˆå‘˜å¯ä»¥è¿›è¡ŒæŸ¥çœ‹å’Œå…‹éš†ç­‰åªè¯»æ“作。
teams.write_permission_desc=该团队拥有对所属仓库的 <strong>读å–</strong> å’Œ <strong>写入</strong> çš„æƒé™ã€‚
teams.admin_permission_desc=该团队拥有一定的 <strong>管ç†</strong> æƒé™ï¼Œå›¢é˜Ÿæˆå‘˜å¯ä»¥è¯»å–ã€å…‹éš†ã€æŽ¨é€ä»¥åŠæ·»åŠ å…¶å®ƒä»“åº“å作者。
@@ -2927,7 +2963,7 @@ repositories=仓库管ç†
hooks=Web é’©å­
integrations=集æˆ
authentication=è®¤è¯æº
-emails=用户邮件
+emails=用户邮箱
config=应用é…ç½®
config_summary=摘è¦
config_settings=设置
@@ -2946,27 +2982,27 @@ dashboard.operation_name=æ“作åç§°
dashboard.operation_switch=开关
dashboard.operation_run=执行
dashboard.clean_unbind_oauth=æ¸…ç†æœªç»‘定的 OAuth 连接
-dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth 连接已被删除。
+dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth 连接已删除。
dashboard.task.started=已开始任务:%[1]s
dashboard.task.process=任务: %[1]s
dashboard.task.cancelled=任务: %[1]s 已喿¶ˆ: %[3]s
dashboard.task.error=任务中的错误: %[1]s: %[3]s
dashboard.task.finished=任务: %[2]s å¯åŠ¨çš„ %[1]s 已完æˆ
dashboard.task.unknown=未知任务: %[1]s
-dashboard.cron.started=已开始计划任务:%[1]s
+dashboard.cron.started=计划任务:%[1]s å·²å¯åЍ
dashboard.cron.process=计划任务:%[1]s
-dashboard.cron.cancelled=定时任务: %[1]s 已喿¶ˆ: %[3]s
-dashboard.cron.error=任务中的错误: %s: %[3]s
-dashboard.cron.finished=任务:%[1]s å·²ç»å®Œæˆ
+dashboard.cron.cancelled=计划任务:%[1]s 已喿¶ˆï¼š%[3]s
+dashboard.cron.error=计划任务错误: %s: %[3]s
+dashboard.cron.finished=计划任务:%[1]s 已完æˆ
dashboard.delete_inactive_accounts=åˆ é™¤æ‰€æœ‰æœªæ¿€æ´»çš„å¸æˆ·
dashboard.delete_inactive_accounts.started=删除所有未激活的账户任务已å¯åŠ¨ã€‚
-dashboard.delete_repo_archives=删除所有代ç åº“的存档 (ZIP〠TARã€GZ, ç­‰...)
+dashboard.delete_repo_archives=删除所有仓库的存档(ZIPã€TARã€GZ等)
dashboard.delete_repo_archives.started=删除所有仓库存档任务已å¯åŠ¨ã€‚
dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库
dashboard.delete_missing_repos.started=删除所有丢失 Git 文件的仓库任务已å¯åŠ¨ã€‚
dashboard.delete_generated_repository_avatars=删除生æˆçš„仓库头åƒ
-dashboard.sync_repo_branches=将缺少的分支从 git æ•°æ®åŒæ­¥åˆ°æ•°æ®åº“
-dashboard.sync_repo_tags=从 git æ•°æ®åŒæ­¥æ ‡ç­¾åˆ°æ•°æ®åº“
+dashboard.sync_repo_branches=将缺少的分支从 Git æ•°æ®åŒæ­¥åˆ°æ•°æ®åº“
+dashboard.sync_repo_tags=从 Git æ•°æ®åŒæ­¥æ ‡ç­¾åˆ°æ•°æ®åº“
dashboard.update_mirrors=更新镜åƒä»“库
dashboard.repo_health_check=å¥åº·æ£€æŸ¥æ‰€æœ‰ä»“库
dashboard.check_repo_stats=检查所有仓库统计
@@ -2981,11 +3017,11 @@ dashboard.reinit_missing_repos=釿–°åˆå§‹åŒ–所有丢失的 Git 仓库存在çš
dashboard.sync_external_users=åŒæ­¥å¤–部用户数æ®
dashboard.cleanup_hook_task_table=æ¸…ç† hook_task 表
dashboard.cleanup_packages=清ç†è¿‡æœŸçš„软件包
-dashboard.cleanup_actions=清ç†è¿‡æœŸçš„ Actions 资æº
+dashboard.cleanup_actions=清ç†è¿‡æœŸçš„工作æµèµ„æº
dashboard.server_uptime=æœåŠ¡è¿è¡Œæ—¶é—´
dashboard.current_goroutine=å½“å‰ Goroutines æ•°é‡
dashboard.current_memory_usage=当å‰å†…存使用é‡
-dashboard.total_memory_allocated=所有被分é…的内存
+dashboard.total_memory_allocated=所有已分é…的内存
dashboard.memory_obtained=内存å ç”¨é‡
dashboard.pointer_lookup_times=指针查找次数
dashboard.memory_allocate_times=å†…å­˜åˆ†é…æ¬¡æ•°
@@ -2994,36 +3030,36 @@ dashboard.current_heap_usage=å½“å‰ Heap 内存使用é‡
dashboard.heap_memory_obtained=Heap 内存å ç”¨é‡
dashboard.heap_memory_idle=Heap 内存空闲é‡
dashboard.heap_memory_in_use=正在使用的 Heap 内存
-dashboard.heap_memory_released=被释放的 Heap 内存
+dashboard.heap_memory_released=已释放的 Heap 内存
dashboard.heap_objects=Heap 对象数é‡
dashboard.bootstrap_stack_usage=å¯åЍ Stack 使用é‡
-dashboard.stack_memory_obtained=被分é…çš„ Stack 内存
+dashboard.stack_memory_obtained=已分é…çš„ Stack 内存
dashboard.mspan_structures_usage=MSpan 结构内存使用é‡
-dashboard.mspan_structures_obtained=被分é…çš„ MSpan 结构内存
+dashboard.mspan_structures_obtained=已分é…çš„ MSpan 结构内存
dashboard.mcache_structures_usage=MCache 结构内存使用é‡
-dashboard.mcache_structures_obtained=被分é…çš„ MCache 结构内存
-dashboard.profiling_bucket_hash_table_obtained=被分é…的剖æžå“ˆå¸Œè¡¨å†…å­˜
-dashboard.gc_metadata_obtained=被分é…çš„ GC 元数æ®å†…å­˜
-dashboard.other_system_allocation_obtained=其它被分é…的系统内存
+dashboard.mcache_structures_obtained=已分é…çš„ MCache 结构内存
+dashboard.profiling_bucket_hash_table_obtained=已分é…的剖æžå“ˆå¸Œè¡¨å†…å­˜
+dashboard.gc_metadata_obtained=已分é…çš„ GC 元数æ®å†…å­˜
+dashboard.other_system_allocation_obtained=其它已分é…的系统内存
dashboard.next_gc_recycle=下次 GC 内存回收é‡
dashboard.last_gc_time=è·ç¦»ä¸Šæ¬¡ GC æ—¶é—´
dashboard.total_gc_time=GC æš‚åœæ—¶é—´æ€»é‡
dashboard.total_gc_pause=GC æš‚åœæ—¶é—´æ€»é‡
dashboard.last_gc_pause=上次 GC æš‚åœæ—¶é—´
dashboard.gc_times=GC 执行次数
-dashboard.delete_old_actions=从数æ®åº“中删除所有旧æ“作记录
-dashboard.delete_old_actions.started=已开始从数æ®åº“中删除所有旧æ“作记录。
+dashboard.delete_old_actions=从数æ®åº“中删除所有旧工作æµè®°å½•
+dashboard.delete_old_actions.started=已开始从数æ®åº“中删除所有旧工作æµè®°å½•。
dashboard.update_checker=更新检查器
dashboard.delete_old_system_notices=从数æ®åº“中删除所有旧系统通知
dashboard.gc_lfs=垃圾回收 LFS 元数æ®
-dashboard.stop_zombie_tasks=åœæ­¢åƒµå°¸ä»»åŠ¡
-dashboard.stop_endless_tasks=åœæ­¢æ— æ³•åœæ­¢çš„任务
-dashboard.cancel_abandoned_jobs=å–æ¶ˆä¸¢å¼ƒçš„任务
-dashboard.start_schedule_tasks=开始Actions调度任务
+dashboard.stop_zombie_tasks=åœæ­¢åƒµå°¸å·¥ä½œæµä»»åŠ¡
+dashboard.stop_endless_tasks=åœæ­¢æ— é™å¾ªçŽ¯çš„å·¥ä½œæµä»»åŠ¡
+dashboard.cancel_abandoned_jobs=å–æ¶ˆå·²æ”¾å¼ƒçš„工作æµä»»åŠ¡
+dashboard.start_schedule_tasks=å¯åŠ¨å·¥ä½œæµè®¡åˆ’任务
dashboard.sync_branch.started=åˆ†æ”¯åŒæ­¥å·²å¼€å§‹
dashboard.sync_tag.started=æ ‡ç­¾åŒæ­¥å·²å¼€å§‹
dashboard.rebuild_issue_indexer=é‡å»ºå·¥å•索引
-dashboard.sync_repo_licenses=釿–°ä»“库许å¯è¯æŽ¢æµ‹
+dashboard.sync_repo_licenses=釿–°æŽ¢æµ‹ä»“库许å¯è¯
users.user_manage_panel=ç”¨æˆ·å¸æˆ·ç®¡ç†
users.new_account=åˆ›å»ºæ–°å¸æˆ·
@@ -3041,33 +3077,33 @@ users.created=创建时间
users.last_login=上次登录
users.never_login=从未登录
users.send_register_notify=å‘逿³¨å†Œé€šçŸ¥
-users.new_success=用户账户 '%s' 已被创建。
+users.new_success=用户账户「%sã€å·²åˆ›å»ºã€‚
users.edit=修改
users.auth_source=è®¤è¯æº
users.local=本地
users.auth_login_name=认è¯ç™»å½•åç§°
users.password_helper=ä¿æŒå¯†ç ä¸ºç©ºå°†ä¸æ›´æ”¹å¯†ç ã€‚
-users.update_profile_success=è¯¥å¸æˆ·å·²è¢«æ›´æ–°ã€‚
+users.update_profile_success=è¯¥å¸æˆ·å·²æ›´æ–°ã€‚
users.edit_account=编辑å¸å·
users.max_repo_creation=最大仓库数
users.max_repo_creation_desc=(设置为 -1 表示使用全局默认值)
-users.is_activated=该用户已被激活
+users.is_activated=该用户已激活
users.prohibit_login=ç¦ç”¨ç™»å½•
users.is_admin=是管ç†å‘˜
users.is_restricted=å—é™
users.allow_git_hook=å…许创建 Git é’©å­
-users.allow_git_hook_tooltip=Git é’©å­å°†ä¼šè¢«ä»¥æ“作系统用户è¿è¡Œï¼Œå°†ä¼šæ‹¥æœ‰åŒæ ·çš„主机访问æƒé™ã€‚因此,拥有此特殊的Git é’©å­æƒé™å°†èƒ½å¤Ÿè®¿é—®åˆä¿®æ”¹æ‰€æœ‰çš„ Gitea 仓库或者Gitea的数æ®åº“ã€‚åŒæ—¶ä¹Ÿèƒ½èŽ·å¾—Gitea的管ç†å‘˜æƒé™ã€‚
+users.allow_git_hook_tooltip=Git é’©å­å°†ä¼šä»¥æ“作系统用户è¿è¡Œï¼Œæ‹¥æœ‰åŒæ ·çš„主机访问æƒé™ã€‚因此,拥有此特殊的 Git é’©å­æƒé™å°†èƒ½å¤Ÿè®¿é—®åˆä¿®æ”¹æ‰€æœ‰çš„ Gitea 仓库或者 Gitea 的数æ®åº“ã€‚åŒæ—¶ä¹Ÿèƒ½èŽ·å¾— Gitea 的管ç†å‘˜æƒé™ã€‚
users.allow_import_local=å…许导入本地仓库
users.allow_create_organization=å…许创建组织
users.update_profile=æ›´æ–°å¸æˆ·
users.delete_account=åˆ é™¤å¸æˆ·
-users.cannot_delete_self=ä½ ä¸èƒ½åˆ é™¤è‡ªå·±
-users.still_own_repo=此用户ä»ç„¶æ‹¥æœ‰ä¸€ä¸ªæˆ–多个仓库。必须首先删除或转让这些仓库。
+users.cannot_delete_self=您ä¸èƒ½åˆ é™¤è‡ªå·±
+users.still_own_repo=此用户ä»ç„¶æ‹¥æœ‰ä¸€ä¸ªæˆ–多个仓库。必须首先删除或转移这些仓库。
users.still_has_org=此用户是组织的æˆå‘˜ã€‚必须先从组织中删除用户。
users.purge=清ç†ç”¨æˆ·
-users.purge_help=强制删除用户和用户拥有的任何仓库ã€ç»„织和软件包。所有评论也将被删除。
+users.purge_help=强制删除用户和用户拥有的任何仓库ã€ç»„织和软件包。所有评论也将删除。
users.still_own_packages=此用户ä»ç„¶æ‹¥æœ‰ä¸€ä¸ªæˆ–多个软件包,请先删除这些软件包。
-users.deletion_success=ç”¨æˆ·å¸æˆ·å·²è¢«åˆ é™¤ã€‚
+users.deletion_success=ç”¨æˆ·å¸æˆ·å·²åˆ é™¤ã€‚
users.reset_2fa=é‡ç½®ä¸¤æ­¥éªŒè¯
users.list_status_filter.menu_text=过滤
users.list_status_filter.reset=é‡ç½®
@@ -3086,19 +3122,19 @@ users.details=用户详细信æ¯
emails.email_manage_panel=邮件管ç†
emails.primary=主è¦çš„
emails.activated=已激活
-emails.filter_sort.email=电å­é‚®ä»¶
-emails.filter_sort.email_reverse=电å­é‚®ä»¶(逆åº)
+emails.filter_sort.email=邮箱
+emails.filter_sort.email_reverse=邮箱(倒åºï¼‰
emails.filter_sort.name=用户å
emails.filter_sort.name_reverse=用户å(倒åº)
-emails.updated=电å­é‚®ä»¶å·²æ›´æ–°
-emails.not_updated=无法更新请求的电å­é‚®ä»¶åœ°å€ï¼š %v
-emails.duplicate_active=此电å­é‚®ä»¶åœ°å€å·²è¢«å¦ä¸€ä¸ªç”¨æˆ·æ¿€æ´»ä½¿ç”¨ã€‚
-emails.change_email_header=更新电å­é‚®ä»¶å±žæ€§
-emails.change_email_text=æ‚¨ç¡®å®šè¦æ›´æ–°è¯¥ç”µå­é‚®ä»¶åœ°å€å—?
-emails.delete=删除电å­é‚®ä»¶
-emails.delete_desc=您确定è¦åˆ é™¤è¯¥ç”µå­é‚®ä»¶åœ°å€ï¼Ÿ
-emails.deletion_success=电å­é‚®ä»¶åœ°å€å·²è¢«åˆ é™¤ã€‚
-emails.delete_primary_email_error=您ä¸èƒ½åˆ é™¤ä¸»ç”µå­é‚®ä»¶ã€‚
+emails.updated=邮箱已更新
+emails.not_updated=无法更新请求的邮箱地å€ï¼š%v
+emails.duplicate_active=此邮箱地å€å·²è¢«å¦ä¸€ä¸ªç”¨æˆ·æ¿€æ´»ä½¿ç”¨ã€‚
+emails.change_email_header=更新邮箱属性
+emails.change_email_text=æ‚¨ç¡®å®šè¦æ›´æ–°è¯¥é‚®ç®±åœ°å€å—?
+emails.delete=删除邮箱
+emails.delete_desc=您确定è¦åˆ é™¤è¯¥é‚®ç®±åœ°å€ï¼Ÿ
+emails.deletion_success=邮箱地å€å·²åˆ é™¤ã€‚
+emails.delete_primary_email_error=您ä¸èƒ½åˆ é™¤ä¸»é‚®ç®±ã€‚
orgs.org_manage_panel=组织管ç†
orgs.name=åç§°
@@ -3118,7 +3154,7 @@ repos.lfs_size=LFS 大å°
packages.package_manage_panel=软件包管ç†
packages.total_size=总大å°ï¼š%s
-packages.unreferenced_size=未引用大å°ï¼š %s
+packages.unreferenced_size=未引用大å°ï¼š%s
packages.cleanup=清ç†è¿‡æœŸæ•°æ®
packages.cleanup.success=清ç†è¿‡æœŸæ•°æ®æˆåŠŸ
packages.owner=所有者
@@ -3130,13 +3166,13 @@ packages.repository=仓库
packages.size=大å°
packages.published=å·²å‘布
-defaulthooks=默认Webé’©å­
+defaulthooks=默认 Web é’©å­
defaulthooks.desc=当æŸäº› Gitea äº‹ä»¶è§¦å‘æ—¶ï¼ŒWeb é’©å­è‡ªåЍ呿œåС噍å‘出 HTTP POST 请求。这里定义的 Web é’©å­æ˜¯é»˜è®¤é…置,将被å¤åˆ¶åˆ°æ‰€æœ‰æ–°çš„仓库中。详情请访问 <a target="_blank" rel="noopener" href="%s">Web é’©å­æŒ‡å—</a>。
-defaulthooks.add_webhook=添加默认Web é’©å­
+defaulthooks.add_webhook=添加默认 Web é’©å­
defaulthooks.update_webhook=更新默认 Web 钩å­
systemhooks=系统 Web é’©å­
-systemhooks.desc=当æŸäº› Gitea äº‹ä»¶è§¦å‘æ—¶ï¼ŒWeb é’©å­è‡ªåЍ呿œåС噍å‘出HTTP POST请求。这里定义的 Web é’©å­å°†ä½œç”¨äºŽç³»ç»Ÿä¸Šçš„æ‰€æœ‰ä»“库,所以请考虑这å¯èƒ½å¸¦æ¥çš„任何性能影å“。了解详情请访问 <a target="_blank" rel="noopener" href="%s">Web é’©å­æŒ‡å—</a>。
+systemhooks.desc=当æŸäº› Gitea äº‹ä»¶è§¦å‘æ—¶ï¼ŒWeb é’©å­è‡ªåЍ呿œåС噍å‘出 HTTP POST 请求。这里定义的 Web é’©å­å°†ä½œç”¨äºŽç³»ç»Ÿä¸Šçš„æ‰€æœ‰ä»“库,所以请考虑这å¯èƒ½å¸¦æ¥çš„任何性能影å“。了解详情请访问 <a target="_blank" rel="noopener" href="%s">Web é’©å­æŒ‡å—</a>。
systemhooks.add_webhook=添加系统 Web é’©å­
systemhooks.update_webhook=更新系统 Web é’©å­
@@ -3158,7 +3194,7 @@ auths.bind_password=绑定密ç 
auths.user_base=用户æœç´¢åŸºå‡†
auths.user_dn=用户 DN
auths.attribute_username=用户å属性
-auths.attribute_username_placeholder=置空将使用Gitea的用户å。
+auths.attribute_username_placeholder=置空将使用 Gitea 的用户å。
auths.attribute_name=å字属性
auths.attribute_surname=å§“æ°å±žæ€§
auths.attribute_mail=电å­é‚®ç®±å±žæ€§
@@ -3192,7 +3228,7 @@ auths.helo_hostname=HELO 主机å
auths.helo_hostname_helper=用 HELO å‘é€çš„主机å。 留空å‘é€å½“å‰ä¸»æœºå。
auths.disable_helo=ç¦ç”¨ HELO
auths.pam_service_name=PAM æœåŠ¡åç§°
-auths.pam_email_domain=PAM 电å­é‚®ä»¶åŸŸ(å¯é€‰)
+auths.pam_email_domain=PAM 邮件域(å¯é€‰ï¼‰
auths.oauth2_provider=OAuth2 æä¾›ç¨‹åº
auths.oauth2_icon_url=图标 URL
auths.oauth2_clientID=客户端 ID (键)
@@ -3201,8 +3237,8 @@ auths.openIdConnectAutoDiscoveryURL=OpenID 连接自动å‘现 URL
auths.oauth2_use_custom_url=使用自定义的 URL è€Œä¸æ˜¯é»˜è®¤çš„ URL
auths.oauth2_tokenURL=令牌 URL
auths.oauth2_authURL=æŽˆæƒ URL
-auths.oauth2_profileURL=Profile URL
-auths.oauth2_emailURL=电å­é‚®ä»¶ URL
+auths.oauth2_profileURL=个人资料 URL
+auths.oauth2_emailURL=邮箱 URL
auths.skip_local_two_fa=跳过本地两步验è¯
auths.skip_local_two_fa_helper=ä¸è®¾ç½®æ„味ç€è®¾ç½®äº†ä¸¤æ­¥éªŒè¯çš„æœ¬åœ°ç”¨æˆ·ä»ç„¶éœ€è¦é€šè¿‡ä¸¤æ­¥éªŒè¯æ‰èƒ½ç™»å½•
auths.oauth2_tenant=租户
@@ -3222,31 +3258,31 @@ auths.sspi_auto_create_users_helper=å…许 SSPI 认è¯åœ¨ç”¨æˆ·ç¬¬ä¸€æ¬¡ç™»å½•æ—
auths.sspi_auto_activate_users=自动激活用户
auths.sspi_auto_activate_users_helper=å…许 SSPI 认è¯è‡ªåŠ¨æ¿€æ´»æ–°ç”¨æˆ·
auths.sspi_strip_domain_names=从用户å中删除域å部分
-auths.sspi_strip_domain_names_helper=如果选中此项,域å将从登录å中删除(例如,"DOMAIN\user"å’Œ"user@example.org"ï¼Œä¸¤è€…éƒ½å°†å˜æˆåªæ˜¯â€œç”¨æˆ·â€)。
+auths.sspi_strip_domain_names_helper=如果选中此项,域å将从登录å中删除(例如,「DOMAIN\userã€å’Œã€Œuser@example.orgã€ï¼Œä¸¤è€…éƒ½å°†å˜æˆåªæ˜¯ã€Œç”¨æˆ·ã€ï¼‰ã€‚
auths.sspi_separator_replacement=è¦ä½¿ç”¨çš„分隔符代替\, / å’Œ @
-auths.sspi_separator_replacement_helper=用于替æ¢ä¸‹çº§ç™»å½•å称分隔符的字符 (例如 "DOMAIN\user") 中的 \ 和用户主åå­—(如"user@example.org"中的 @)。
+auths.sspi_separator_replacement_helper=用于替æ¢ä¸‹çº§ç™»å½•å称分隔符的字符(例如「DOMAIN\userã€ï¼‰ä¸­çš„ \ 和用户主å字(如「user@example.orgã€ä¸­çš„ @)。
auths.sspi_default_language=默认语言
auths.sspi_default_language_helper=SSPI è®¤è¯æ–¹æ³•为用户自动创建的默认语言。如果您想è¦è‡ªåŠ¨æ£€æµ‹åˆ°è¯­è¨€ï¼Œè¯·ç•™ç©ºã€‚
auths.tips=帮助æç¤º
auths.tips.oauth2.general=OAuth2 认è¯
auths.tips.oauth2.general.tip=当注册新的 OAuth2 èº«ä»½éªŒè¯æ—¶ï¼Œå›žè°ƒ/é‡å®šå‘ URL 应该是:
auths.tip.oauth2_provider=OAuth2 æä¾›ç¨‹åº
-auths.tip.bitbucket=在 %s 注册新的 OAuth ä½¿ç”¨è€…åŒæ—¶æ·»åŠ æƒé™â€œè´¦å·â€-“读å–â€
-auths.tip.nextcloud=使用下é¢çš„èœå•“设置(Settings) -> 安全(Security) -> OAuth 2.0 clientâ€åœ¨æ‚¨çš„实例上注册一个新的 OAuth 客户端。
+auths.tip.bitbucket=在 %s 注册新的 OAuth ä½¿ç”¨è€…åŒæ—¶æ·»åŠ æƒé™ã€Œè´¦å·ã€-「读å–ã€
+auths.tip.nextcloud=使用下é¢çš„èœå•「设置 -> 安全 -> OAuth 2.0 客户端ã€åœ¨æ‚¨çš„实例上注册一个新的 OAuth 客户端
auths.tip.dropbox=在 %s 上创建一个新的应用程åº
-auths.tip.facebook=`在 %s 注册一个新的应用,并添加产å“"Facebook 登录"`
+auths.tip.facebook=在 %s 注册一个新的应用,并添加产å“「Facebook 登录ã€
auths.tip.github=在 %s 注册一个 OAuth 应用程åº
auths.tip.gitlab_new=在 %s 注册一个新的应用
auths.tip.google_plus=从谷歌 API æŽ§åˆ¶å° %s 获得 OAuth2 客户端凭æ®
auths.tip.openid_connect=使用 OpenID 连接å‘现 URL ({server}/.well-known/openid-configuration) æ¥æŒ‡å®šç»ˆç‚¹
-auths.tip.twitter=访问 %s,创建应用并确ä¿å¯ç”¨äº†"å…许此应用程åºç”¨äºŽç™»å½• Twitter"的选项。
+auths.tip.twitter=访问 %s,创建应用并确ä¿å¯ç”¨äº†ã€Œå…许此应用程åºä½¿ç”¨ Twitter 登录ã€çš„选项
auths.tip.discord=在 %s 上注册新应用程åº
auths.tip.gitea=注册一个新的 OAuth2 应用程åºã€‚å¯ä»¥è®¿é—® %s 查看帮助
-auths.tip.yandex=在 %s 上创建一个新的应用程åºã€‚在“ Yandex.Passport APIâ€è¿™éƒ¨åˆ†ä¸­é€‰æ‹©ä»¥ä¸‹æƒé™ï¼šâ€œè®¿é—®ç”µå­é‚®ä»¶åœ°å€ï¼ˆAccess to email address)â€ï¼Œâ€œè®¿é—®ç”¨æˆ·å¤´åƒï¼ˆAccess to user avatar)â€å’Œâ€œè®¿é—®ç”¨æˆ·å,å字和姓æ°ï¼Œæ€§åˆ«ï¼ˆAccess to username, first name and surname, genderAccess to username, first name and surname, gender)â€
+auths.tip.yandex=在 %s 上创建一个新的应用。在「Yandex.Passport APIã€è¿™éƒ¨åˆ†ä¸­é€‰æ‹©ä»¥ä¸‹æƒé™ï¼šã€Œè®¿é—®é‚®ç®±åœ°å€ã€ï¼Œã€Œè®¿é—®ç”¨æˆ·å¤´åƒã€å’Œã€Œè®¿é—®ç”¨æˆ·å,å字和姓æ°ï¼Œæ€§åˆ«ã€
auths.tip.mastodon=输入您想è¦è®¤è¯çš„ mastodon 实例的自定义 URL (或使用默认值)
auths.edit=ä¿®æ”¹è®¤è¯æº
auths.activated=è¯¥è®¤è¯æºå·²ç»å¯ç”¨
-auths.new_success=å·²æ·»åŠ èº«ä»½éªŒè¯ '%s'。
+auths.new_success=已添加身份验è¯ã€Œ%sã€ã€‚
auths.update_success=è®¤è¯æºå·²ç»æ›´æ–°ã€‚
auths.update=æ›´æ–°è®¤è¯æº
auths.delete=åˆ é™¤è®¤è¯æº
@@ -3254,10 +3290,10 @@ auths.delete_auth_title=åˆ é™¤èº«ä»½éªŒè¯æº
auths.delete_auth_desc=åˆ é™¤ä¸€ä¸ªè®¤è¯æºå°†é˜»æ­¢ä½¿ç”¨å®ƒè¿›è¡Œç™»å½•。确认?
auths.still_in_used=è®¤è¯æºä»åœ¨ä½¿ç”¨ã€‚è¯·å…ˆè§£é™¤æˆ–è€…åˆ é™¤ä½¿ç”¨æ­¤è®¤è¯æºçš„用户。
auths.deletion_success=è®¤è¯æºå·²ç»æ›´æ–°ã€‚
-auths.login_source_exist=è®¤è¯æº '%s' å·²ç»å­˜åœ¨ã€‚
+auths.login_source_exist=è®¤è¯æºã€Œ%sã€å·²ç»å­˜åœ¨ã€‚
auths.login_source_of_type_exist=æ­¤ç±»åž‹çš„è®¤è¯æºå·²å­˜åœ¨ã€‚
-auths.unable_to_initialize_openid=无法åˆå§‹åŒ– OpenID Connect æä¾›å•†ï¼š %s
-auths.invalid_openIdConnectAutoDiscoveryURL=无效的 Auto Discovery URL (这必须是一个以 http:// 或 https://开头的有效的 URL)
+auths.unable_to_initialize_openid=无法åˆå§‹åŒ– OpenID Connect æä¾›å•†ï¼š%s
+auths.invalid_openIdConnectAutoDiscoveryURL=无效的自动å‘现 URL(这必须是一个以 http:// 或 https:// 开头的有效的 URL)
config.server_config=æœåС噍é…ç½®
config.app_name=站点åç§°
@@ -3298,16 +3334,16 @@ config.db_type=类型
config.db_host=主机
config.db_name=æ•°æ®åº“åç§°
config.db_user=用户å
-config.db_schema=架构模å¼
+config.db_schema=æž¶æž„
config.db_ssl_mode=SSL
config.db_path=æ•°æ®åº“路径
config.service_config=æœåŠ¡é…ç½®
-config.register_email_confirm=需è¦ç”µå­é‚®ä»¶ç¡®è®¤æ³¨å†Œ
+config.register_email_confirm=需è¦é‚®ä»¶ç¡®è®¤æ³¨å†Œ
config.disable_register=ç¦æ­¢ç”¨æˆ·æ³¨å†Œ
-config.allow_only_internal_registration=åªå…许通过 Gitea 进行注册
+config.allow_only_internal_registration=ä»…å…许通过 Gitea 进行注册
config.allow_only_external_registration=ä»…å…许通过外部æœåŠ¡æ³¨å†Œ
-config.enable_openid_signup=å¯ç”¨ OpenID 自注册
+config.enable_openid_signup=å¯ç”¨ OpenID 自助注册
config.enable_openid_signin=å¯ç”¨ OpenID 登录
config.show_registration_button=显示注册按钮
config.require_sign_in_view=å¯ç”¨ç™»å½•访问é™åˆ¶
@@ -3315,12 +3351,12 @@ config.mail_notify=å¯ç”¨é‚®ä»¶é€šçŸ¥
config.enable_captcha=å¯ç”¨ç™»å½•验è¯ç 
config.active_code_lives=激活用户链接有效期
config.reset_password_code_lives=æ¢å¤è´¦æˆ·éªŒè¯ç è¿‡æœŸæ—¶é—´
-config.default_keep_email_private=默认情况下éšè—电å­é‚®ä»¶åœ°å€
+config.default_keep_email_private=默认éšè—邮箱地å€
config.default_allow_create_organization=默认情况下å…许创建组织
config.enable_timetracking=å¯ç”¨æ—¶é—´è·Ÿè¸ª
config.default_enable_timetracking=默认情况下å¯ç”¨æ—¶é—´è·Ÿè¸ª
config.default_allow_only_contributors_to_track_time=ä»…å…许æˆå‘˜è·Ÿè¸ªæ—¶é—´
-config.no_reply_address=éšè—电å­é‚®ä»¶åŸŸ
+config.no_reply_address=éšè—邮件域
config.default_visibility_organization=新组织的默认å¯è§æ€§
config.default_enable_dependencies=默认情况下å¯ç”¨å·¥å•ä¾èµ–
@@ -3331,7 +3367,7 @@ config.skip_tls_verify=跳过 TLS 验è¯
config.mailer_config=Mailer é…ç½®
config.mailer_enabled=å¯ç”¨æœåŠ¡
-config.mailer_enable_helo=å¯ç”¨HELO
+config.mailer_enable_helo=å¯ç”¨ HELO
config.mailer_name=任务åç§°
config.mailer_protocol=åè®®
config.mailer_smtp_addr=SMTP 地å€
@@ -3345,8 +3381,8 @@ config.mailer_use_dummy=Dummy
config.test_email_placeholder=电å­é‚®å€ (例如,test@example.com)
config.send_test_mail=å‘逿µ‹è¯•邮件
config.send_test_mail_submit=å‘é€
-config.test_mail_failed=å‘逿µ‹è¯•邮件至 '%s' 时失败:%v
-config.test_mail_sent=测试邮件已ç»å‘é€è‡³ '%s'。
+config.test_mail_failed=å‘逿µ‹è¯•邮件至「%sã€å¤±è´¥ï¼š%v
+config.test_mail_sent=测试邮件已ç»å‘é€è‡³ã€Œ%sã€ã€‚
config.oauth_config=OAuth é…ç½®
config.oauth_enabled=å¯ç”¨
@@ -3358,7 +3394,7 @@ config.cache_conn=Cache 连接字符串
config.cache_item_ttl=缓存项目 TTL
config.cache_test=测试缓存
config.cache_test_failed=缓存测试失败: %v。
-config.cache_test_slow=缓存测试æˆåŠŸï¼Œä½†å“应缓慢: %s。
+config.cache_test_slow=缓存测试æˆåŠŸï¼Œä½†å“应缓慢:%s。
config.cache_test_succeeded=缓存测试æˆåŠŸï¼Œåœ¨ %s 时间内得到å“应。
config.session_config=Session é…ç½®
@@ -3399,7 +3435,7 @@ config.set_setting_failed=设置 %s 失败
monitor.stats=统计
-monitor.cron=Cron 任务
+monitor.cron=计划任务
monitor.name=任务åç§°
monitor.schedule=任务安排
monitor.next=下次执行时间
@@ -3420,7 +3456,7 @@ monitor.process.cancel_desc=中止一个进程å¯èƒ½å¯¼è‡´æ•°æ®ä¸¢å¤±
monitor.process.children=å­è¿›ç¨‹
monitor.queues=队列
-monitor.queue=队列: %s
+monitor.queue=队列:%s
monitor.queue.name=åç§°
monitor.queue.type=类型
monitor.queue.exemplar=æ•°æ®ç±»åž‹
@@ -3437,7 +3473,7 @@ monitor.queue.settings.maxnumberworkers.error=最大工作者数必须是数字
monitor.queue.settings.submit=更新设置
monitor.queue.settings.changed=设置已更新
monitor.queue.settings.remove_all_items=移除全部
-monitor.queue.settings.remove_all_items_done=队列中的所有项目已被移除。
+monitor.queue.settings.remove_all_items_done=队列中的所有项目已移除。
notices.system_notice_list=系统æç¤ºç®¡ç†
notices.view_detail_header=查看æç¤ºè¯¦æƒ…
@@ -3452,16 +3488,16 @@ notices.type_1=仓库
notices.type_2=任务
notices.desc=æç¤ºæè¿°
notices.op=æ“作
-notices.delete_success=系统通知已被删除。
+notices.delete_success=系统通知已删除。
self_check.no_problem_found=尚未å‘现问题。
self_check.startup_warnings=å¯åŠ¨è­¦å‘Šï¼š
self_check.database_collation_mismatch=期望数æ®åº“使用的校验方å¼ï¼š%s
-self_check.database_collation_case_insensitive=æ•°æ®åº“正在使用一个校验 %s, è¿™æ˜¯ä¸€ä¸ªä¸æ•感的校验. 虽然Giteaå¯ä»¥ä¸Žå®ƒåˆä½œï¼Œä½†å¯èƒ½æœ‰ä¸€äº›ç½•è§çš„æƒ…况ä¸å¦‚预期的那样起作用。
-self_check.database_inconsistent_collation_columns=æ•°æ®åº“正在使用%s的排åºè§„则,但是这些列使用了ä¸åŒ¹é…的排åºè§„则。这å¯èƒ½ä¼šé€ æˆä¸€äº›æ„外问题。
-self_check.database_fix_mysql=对于MySQL/MariaDB用户,您å¯ä»¥ä½¿ç”¨â€œgitea doctor convertâ€å‘½ä»¤æ¥è§£å†³æ ¡éªŒé—®é¢˜ã€‚ 或者您也å¯ä»¥é€šè¿‡ "ALTER ... COLLATE ..." 这样的SQL æ¥æ‰‹åŠ¨è§£å†³è¿™ä¸ªé—®é¢˜ã€‚
-self_check.database_fix_mssql=对于MSSQL用户,您现在åªèƒ½é€šè¿‡"ALTER ... COLLATE ..."SQLs手动解决这个问题。
-self_check.location_origin_mismatch=å½“å‰ URL (%[1]s) 与 Gitea çš„ URL (%[2]s) ä¸åŒ¹é… 。 如果您正在使用åå‘代ç†ï¼Œè¯·ç¡®ä¿è®¾ç½®æ­£ç¡®çš„“主机â€å’Œâ€œX-转å‘-åŽŸå§‹â€æ ‡é¢˜ã€‚
+self_check.database_collation_case_insensitive=æ•°æ®åº“正在使用一个校验 %sï¼Œè¿™æ˜¯ä¸€ä¸ªä¸æ•感的校验。虽然 Gitea å¯ä»¥ä¸Žå®ƒåˆä½œï¼Œä½†å¯èƒ½æœ‰ä¸€äº›ç½•è§çš„æƒ…况ä¸å¦‚预期的那样起作用。
+self_check.database_inconsistent_collation_columns=æ•°æ®åº“正在使用 %s 的排åºè§„则,但是这些列使用了ä¸åŒ¹é…的排åºè§„则。这å¯èƒ½ä¼šé€ æˆä¸€äº›æ„外问题。
+self_check.database_fix_mysql=对于 MySQL/MariaDB 用户,您å¯ä»¥ä½¿ç”¨ã€Œgitea doctor convertã€å‘½ä»¤æ¥è§£å†³æ ¡éªŒé—®é¢˜ã€‚ 或者您也å¯ä»¥é€šè¿‡ã€ŒALTER ... COLLATE ...ã€è¿™æ ·çš„ SQL æ¥æ‰‹åŠ¨è§£å†³è¿™ä¸ªé—®é¢˜ã€‚
+self_check.database_fix_mssql=对于 MSSQL 用户,您现在åªèƒ½é€šè¿‡ã€ŒALTER ... COLLATE ...ã€SQL 手动解决这个问题。
+self_check.location_origin_mismatch=å½“å‰ URL(%[1]s)与 Gitea çš„ URL(%[2]s)ä¸åŒ¹é… 。 如果您正在使用åå‘代ç†ï¼Œè¯·ç¡®ä¿è®¾ç½®æ­£ç¡®çš„「Hostã€å’Œã€ŒX-Forwarded-Protoã€æ ‡å¤´ã€‚
[action]
create_repo=创建了仓库 <a href="%s">%s</a>
@@ -3469,10 +3505,10 @@ rename_repo=é‡å‘½å仓库 <code>%[1]s</code> 为 <a href="%[2]s">%[3]s</a>
commit_repo=推é€åˆ°äº†ä»“库 <a href="%[1]s">%[4]s</a> çš„ <a href="%[2]s">%[3]s</a> 分支
create_issue=`åˆ›å»ºäº†å·¥å• <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`å…³é—­äº†å·¥å• <a href="%[1]s">%[3]s#%[2]s</a>`
-reopen_issue=`釿–°å¼€å¯äº†å·¥å• <a href="%[1]s">%[3]s#%[2]s</a>`
+reopen_issue=`釿–°æ‰“å¼€äº†å·¥å• <a href="%[1]s">%[3]s#%[2]s</a>`
create_pull_request=`创建了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>`
close_pull_request=`关闭了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>`
-reopen_pull_request=`釿–°å¼€å¯äº†åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>`
+reopen_pull_request=`釿–°æ‰“开了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>`
comment_issue=`è¯„è®ºäº†å·¥å• <a href="%[1]s">%[3]s#%[2]s</a>`
comment_pull=`评论了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>`
merge_pull_request=`åˆå¹¶äº†åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>`
@@ -3540,7 +3576,7 @@ no_subscriptions=无订阅
default_key=使用默认密钥签å
error.extract_sign=无法æå–ç­¾å
error.generate_hash=æ— æ³•ç”Ÿæˆæäº¤çš„å“ˆå¸Œ
-error.no_committer_account=æ²¡æœ‰å¸æˆ·é“¾æŽ¥åˆ°æäº¤è€…的电å­é‚®ä»¶
+error.no_committer_account=æ²¡æœ‰å¸æˆ·é“¾æŽ¥åˆ°æäº¤è€…的邮箱
error.no_gpg_keys_found=找ä¸åˆ°æ­¤ç­¾å对应的密钥
error.not_signed_commit=未签åçš„æäº¤
error.failed_retrieval_gpg_keys=找ä¸åˆ°ä»»ä½•与该æäº¤è€…è´¦å·ç›¸å…³çš„密钥
@@ -3666,11 +3702,11 @@ rubygems.dependencies.development=å¼€å‘ä¾èµ–
rubygems.required.ruby=éœ€è¦ Ruby 版本
rubygems.required.rubygems=éœ€è¦ RubyGem 版本
swift.registry=从命令行设置此注册中心:
-swift.install=在你的 <code>Package.swift</code>文件中添加该包:
+swift.install=在您的 <code>Package.swift</code>文件中添加该包:
swift.install2=å¹¶è¿è¡Œä»¥ä¸‹å‘½ä»¤ï¼š
vagrant.install=è‹¥è¦æ·»åŠ ä¸€ä¸ª Vagrant box,请è¿è¡Œä»¥ä¸‹å‘½ä»¤ï¼š
settings.link=将此软件包链接到仓库
-settings.link.description=如果您将一个软件包与一个代ç åº“链接起æ¥ï¼Œè½¯ä»¶åŒ…将显示在代ç åº“的软件包列表中。
+settings.link.description=如果您将一个软件包与一个仓库链接起æ¥ï¼Œè½¯ä»¶åŒ…将显示在仓库的软件包列表中。
settings.link.select=选择仓库
settings.link.button=更新仓库链接
settings.link.success=仓库链接已æˆåŠŸæ›´æ–°ã€‚
@@ -3678,13 +3714,13 @@ settings.link.error=更新仓库链接失败。
settings.delete=删除软件包
settings.delete.description=删除软件包是永久性的,无法撤消。
settings.delete.notice=您将è¦åˆ é™¤ %s (%s)。此æ“作是ä¸å¯é€†çš„,您确定å—?
-settings.delete.success=软件包已被删除。
+settings.delete.success=软件包已删除。
settings.delete.error=删除软件包失败。
owner.settings.cargo.title=Cargo 注册中心索引
owner.settings.cargo.initialize=åˆå§‹åŒ–索引
-owner.settings.cargo.initialize.description=使用 Cargo 注册中心时需è¦ä¸€ä¸ªç‰¹æ®Šç´¢å¼•çš„ Git ä»“åº“ã€‚ä½¿ç”¨æ­¤é€‰é¡¹å°†ï¼ˆé‡æ–°ï¼‰åˆ›å»ºå­˜å‚¨åº“并自动é…置它。
-owner.settings.cargo.initialize.error=åˆå§‹åŒ–Cargo索引失败: %v
-owner.settings.cargo.initialize.success=Cargoç´¢å¼•å·²ç»æˆåŠŸåˆ›å»ºã€‚
+owner.settings.cargo.initialize.description=使用 Cargo 注册中心时需è¦ä¸€ä¸ªç‰¹æ®Šç´¢å¼•çš„ Git ä»“åº“ã€‚ä½¿ç”¨æ­¤é€‰é¡¹å°†ï¼ˆé‡æ–°ï¼‰åˆ›å»ºä»“库并自动é…置它。
+owner.settings.cargo.initialize.error=åˆå§‹åŒ– Cargo 索引失败: %v
+owner.settings.cargo.initialize.success=Cargo ç´¢å¼•å·²ç»æˆåŠŸåˆ›å»ºã€‚
owner.settings.cargo.rebuild=é‡å»ºç´¢å¼•
owner.settings.cargo.rebuild.description=如果索引与存储的 Cargo 包ä¸åŒæ­¥ï¼Œé‡å»ºå¯èƒ½ä¼šæœ‰ç”¨ã€‚
owner.settings.cargo.rebuild.error=无法é‡å»º Cargo 索引: %v
@@ -3697,7 +3733,7 @@ owner.settings.cleanuprules.preview=清ç†è§„则预览
owner.settings.cleanuprules.preview.overview=%d 个软件包计划被删除。
owner.settings.cleanuprules.preview.none=清ç†è§„则与任何软件包都ä¸åŒ¹é…。
owner.settings.cleanuprules.enabled=å¯ç”¨
-owner.settings.cleanuprules.pattern_full_match=应用规则到完整软件包åç§°
+owner.settings.cleanuprules.pattern_full_match=应用表达å¼åˆ°å®Œæ•´è½¯ä»¶åŒ…åç§°
owner.settings.cleanuprules.keep.title=与这些规则相匹é…的版本å³ä½¿ä¸Žä¸‹é¢çš„删除规则相匹é…,也将予以ä¿ç•™ã€‚
owner.settings.cleanuprules.keep.count=ä¿ç•™æœ€æ–°çš„
owner.settings.cleanuprules.keep.count.1=æ¯ä¸ªè½¯ä»¶åŒ…1个版本
@@ -3715,25 +3751,30 @@ owner.settings.chef.keypair.description=需è¦å¯†é’¥å¯¹æ‰èƒ½å‘ Chef 注册中å
[secrets]
secrets=密钥
-description=Secrets 将被传给特定的 Actions,其它情况将ä¸èƒ½è¯»å–
+description=密钥将被传给特定的工作æµï¼Œå…¶å®ƒæƒ…况无法读å–。
none=还没有密钥。
-creation=添加密钥
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=组织æè¿°
-creation.name_placeholder=ä¸åŒºåˆ†å¤§å°å†™ï¼Œå­—æ¯æ•°å­—或下划线ä¸èƒ½ä»¥GITEA_ 或 GITHUB_ 开头。
-creation.value_placeholder=输入任何内容,开头和结尾的空白都会被çœç•¥
-creation.description_placeholder=输入简短æè¿°(å¯é€‰)。
-creation.success=您的密钥 '%s' 添加æˆåŠŸã€‚
-creation.failed=添加密钥失败。
+creation.name_placeholder=ä¸åŒºåˆ†å¤§å°å†™ï¼Œä»…é™å­—æ¯æ•°å­—或下划线且ä¸èƒ½ä»¥ GITEA_ 或 GITHUB_ 开头
+creation.value_placeholder=输入任何内容,开头和结尾的空白将会被忽略
+creation.description_placeholder=输入简短æè¿°ï¼ˆå¯é€‰ï¼‰
+
+save_success=密钥「%sã€ä¿å­˜æˆåŠŸã€‚
+save_failed=密钥ä¿å­˜å¤±è´¥ã€‚
+
+add_secret=添加密钥
+edit_secret=编辑密钥
deletion=删除密钥
-deletion.description=删除密钥是永久性的,无法撤消。继续å—?
-deletion.success=此Secret已被删除。
+deletion.description=删除密钥是永久性的且无法撤消。继续?
+deletion.success=此密钥已删除。
deletion.failed=删除密钥失败。
management=密钥管ç†
[actions]
-actions=Actions
+actions=工作æµ
-unit.desc=管ç†Actions
+unit.desc=管ç†å·¥ä½œæµ
status.unknown=未知
status.waiting=等待中
@@ -3744,9 +3785,9 @@ status.cancelled=已喿¶ˆ
status.skipped=已忽略
status.blocked=阻塞中
-runners=Runners
-runners.runner_manage_panel=Runners管ç†
-runners.new=创建 Runner
+runners=è¿è¡Œå™¨
+runners.runner_manage_panel=è¿è¡Œå™¨ç®¡ç†
+runners.new=创建新è¿è¡Œå™¨
runners.new_notice=如何å¯åŠ¨ä¸€ä¸ªè¿è¡Œå™¨
runners.status=状æ€
runners.id=ID
@@ -3755,9 +3796,9 @@ runners.owner_type=类型
runners.description=æè¿°
runners.labels=标签
runners.last_online=上次在线时间
-runners.runner_title=Runner
-runners.task_list=最近在此runner上的任务
-runners.task_list.no_tasks=还没有任务。
+runners.runner_title=è¿è¡Œå™¨
+runners.task_list=最近在此è¿è¡Œå™¨ä¸Šçš„任务
+runners.task_list.no_tasks=ç›®å‰è¿˜æ²¡æœ‰ä»»åŠ¡ã€‚
runners.task_list.run=执行
runners.task_list.status=状æ€
runners.task_list.repository=仓库
@@ -3766,13 +3807,13 @@ runners.task_list.done_at=完æˆäºŽ
runners.edit_runner=编辑è¿è¡Œå™¨
runners.update_runner=更新更改
runners.update_runner_success=è¿è¡Œå™¨æ›´æ–°æˆåŠŸ
-runners.update_runner_failed=æ›´æ–°è¿è¡Œå™¨å¤±è´¥
-runners.delete_runner=删除è¿è¡Œå™¨
+runners.update_runner_failed=è¿è¡Œå™¨æ›´æ–°å¤±è´¥
+runners.delete_runner=删除此è¿è¡Œå™¨
runners.delete_runner_success=è¿è¡Œå™¨åˆ é™¤æˆåŠŸ
-runners.delete_runner_failed=删除è¿è¡Œå™¨å¤±è´¥
+runners.delete_runner_failed=è¿è¡Œå™¨åˆ é™¤å¤±è´¥
runners.delete_runner_header=确认è¦åˆ é™¤æ­¤è¿è¡Œå™¨
runners.delete_runner_notice=如果一个任务正在è¿è¡Œåœ¨æ­¤è¿è¡Œå™¨ä¸Šï¼Œå®ƒå°†è¢«ç»ˆæ­¢å¹¶æ ‡è®°ä¸ºå¤±è´¥ã€‚它å¯èƒ½ä¼šæ‰“断正在构建的工作æµã€‚
-runners.none=æ— å¯ç”¨çš„ Runner
+runners.none=æ— å¯ç”¨è¿è¡Œå™¨
runners.status.unspecified=未知
runners.status.idle=空闲
runners.status.active=å¯ç”¨
@@ -3786,8 +3827,8 @@ runs.all_workflows=所有工作æµ
runs.commit=æäº¤
runs.scheduled=已计划的
runs.pushed_by=推é€è€…
-runs.invalid_workflow_helper=工作æµé…置文件无效。请检查您的é…置文件: %s
-runs.no_matching_online_runner_helper=æ²¡æœ‰åŒ¹é…æ ‡ç­¾çš„在线 runner: %s
+runs.invalid_workflow_helper=工作æµé…置文件无效。请检查您的é…置文件:%s
+runs.no_matching_online_runner_helper=æ²¡æœ‰åŒ¹é… %s 标签的在线è¿è¡Œå™¨
runs.no_job_without_needs=工作æµå¿…须包å«è‡³å°‘一个没有ä¾èµ–关系的作业。
runs.no_job=工作æµå¿…须包å«è‡³å°‘一个作业
runs.actor=æ“作者
@@ -3796,23 +3837,28 @@ runs.actors_no_select=所有æ“作者
runs.status_no_select=所有状æ€
runs.no_results=没有匹é…的结果。
runs.no_workflows=ç›®å‰è¿˜æ²¡æœ‰å·¥ä½œæµã€‚
-runs.no_workflows.quick_start=ä¸çŸ¥é“如何使用 Gitea Actionså—?请查看 <a target="_blank" rel="noopener noreferrer" href="%s">快速å¯åŠ¨æŒ‡å—</a>。
-runs.no_workflows.documentation=关于Gitea Actions的更多信æ¯ï¼Œè¯·å‚阅 <a target="_blank" rel="noopener noreferrer" href="%s">文档</a>。
+runs.no_workflows.quick_start=ä¸çŸ¥é“如何使用 Gitea 工作æµå—?请查看<a target="_blank" rel="noopener noreferrer" href="%s">快速开始指å—</a>。
+runs.no_workflows.documentation=关于 Gitea 工作æµçš„æ›´å¤šä¿¡æ¯ï¼Œè¯·å‚阅<a target="_blank" rel="noopener noreferrer" href="%s">文档</a>。
runs.no_runs=工作æµå°šæœªè¿è¡Œè¿‡ã€‚
-runs.empty_commit_message=(空白的æäº¤æ¶ˆæ¯)
-runs.expire_log_message=旧的日志已被清除
+runs.empty_commit_message=(空白的æäº¤æ¶ˆæ¯ï¼‰
+runs.expire_log_message=旧的日志已清除。
+runs.delete=删除工作æµè¿è¡Œ
+runs.cancel=å–æ¶ˆå·¥ä½œæµè¿è¡Œ
+runs.delete.description=æ‚¨ç¡®å®šè¦æ°¸ä¹…删除此工作æµè¿è¡Œå—?此æ“作无法撤消。
+runs.not_done=此工作æµè¿è¡Œå°šæœªå®Œæˆã€‚
+runs.view_workflow_file=æŸ¥çœ‹å·¥ä½œæµæ–‡ä»¶
workflow.disable=ç¦ç”¨å·¥ä½œæµ
-workflow.disable_success=å·¥ä½œæµ '%s' å·²æˆåŠŸç¦ç”¨ã€‚
+workflow.disable_success=工作æµã€Œ%sã€å·²æˆåŠŸç¦ç”¨ã€‚
workflow.enable=å¯ç”¨å·¥ä½œæµ
-workflow.enable_success=å·¥ä½œæµ '%s' å·²æˆåŠŸå¯ç”¨ã€‚
+workflow.enable_success=工作æµã€Œ%sã€å·²æˆåŠŸå¯ç”¨ã€‚
workflow.disabled=工作æµå·²ç¦ç”¨ã€‚
workflow.run=è¿è¡Œå·¥ä½œæµ
-workflow.not_found=å·¥ä½œæµ %s 未找到。
-workflow.run_success=å·¥ä½œæµ %s å·²æˆåŠŸè¿è¡Œã€‚
+workflow.not_found=工作æµã€Œ%sã€æœªæ‰¾åˆ°ã€‚
+workflow.run_success=工作æµã€Œ%sã€å·²æˆåŠŸè¿è¡Œã€‚
workflow.from_ref=使用工作æµä»Ž
-workflow.has_workflow_dispatch=æ­¤ Workflow 有一个 Workflow_dispatch 事件触å‘器。
-workflow.has_no_workflow_dispatch=å·¥ä½œæµ %s 没有 workflow_dispatch 事件的触å‘器。
+workflow.has_workflow_dispatch=æ­¤å·¥ä½œæµæœ‰ä¸€ä¸ª workflow_dispatch 事件触å‘器。
+workflow.has_no_workflow_dispatch=工作æµã€Œ%sã€æ²¡æœ‰ workflow_dispatch 事件触å‘器。
need_approval_desc=该工作æµç”±æ´¾ç”Ÿä»“库的åˆå¹¶è¯·æ±‚所触å‘ï¼Œéœ€è¦æ‰¹å‡†æ–¹å¯è¿è¡Œã€‚
@@ -3822,15 +3868,15 @@ variables.creation=添加å˜é‡
variables.none=ç›®å‰è¿˜æ²¡æœ‰å˜é‡ã€‚
variables.deletion=删除å˜é‡
variables.deletion.description=删除å˜é‡æ˜¯æ°¸ä¹…性的,无法撤消。继续å—?
-variables.description=å˜é‡å°†è¢«ä¼ ç»™ç‰¹å®šçš„ Actions,其它情况将ä¸èƒ½è¯»å–
+variables.description=å˜é‡å°†è¢«ä¼ ç»™ç‰¹å®šçš„工作æµï¼Œå…¶å®ƒæƒ…况下无法读å–。
variables.id_not_exist=ID为 %d çš„å˜é‡ä¸å­˜åœ¨ã€‚
variables.edit=编辑å˜é‡
-variables.deletion.failed=删除å˜é‡å¤±è´¥ã€‚
-variables.deletion.success=å˜é‡å·²è¢«åˆ é™¤ã€‚
-variables.creation.failed=添加å˜é‡å¤±è´¥ã€‚
-variables.creation.success=å˜é‡ “%s†添加æˆåŠŸã€‚
-variables.update.failed=编辑å˜é‡å¤±è´¥ã€‚
-variables.update.success=该å˜é‡å·²è¢«ç¼–辑。
+variables.deletion.failed=å˜é‡åˆ é™¤å¤±è´¥ã€‚
+variables.deletion.success=å˜é‡å·²åˆ é™¤ã€‚
+variables.creation.failed=å˜é‡æ·»åŠ å¤±è´¥ã€‚
+variables.creation.success=å˜é‡ã€Œ%sã€æ·»åŠ æˆåŠŸã€‚
+variables.update.failed=å˜é‡ç¼–辑失败。
+variables.update.success=å˜é‡å·²ç¼–辑。
logs.always_auto_scroll=总是自动滚动日志
logs.always_expand_running=总是展开è¿è¡Œæ—¥å¿—
@@ -3840,6 +3886,8 @@ deleted.display_name=已删除项目
type-1.display_name=个人项目
type-2.display_name=仓库项目
type-3.display_name=组织项目
+enter_fullscreen=å…¨å±
+exit_fullscreen=退出全å±
[git.filemode]
changed_filemode=%[1]s -> %[2]s
diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini
index b157a44c69..77a5c0abec 100644
--- a/options/locale/locale_zh-HK.ini
+++ b/options/locale/locale_zh-HK.ini
@@ -374,6 +374,7 @@ editor.create_new_branch=建立 <strong>新的分支</strong> 為此æäº¤å’Œé–‹
editor.cancel=å–æ¶ˆ
editor.no_changes_to_show=沒有å¯ä»¥é¡¯ç¤ºçš„變更。
+
commits.commits=次程å¼ç¢¼æäº¤
commits.author=作者
commits.message=備註
@@ -656,10 +657,11 @@ settings.visibility.private_shortname=ç§æœ‰åº«
settings.update_settings=更新組織設定
settings.update_setting_success=組織設定已更新。
+
+
settings.delete=刪除組織
settings.delete_account=刪除當å‰çµ„ç¹”
settings.confirm_delete_account=確èªåˆªé™¤çµ„ç¹”
-settings.delete_org_title=刪除組織
settings.hooks_desc=新增 webhooks 將觸發在這個組織下 <strong>全部的儲存庫</strong> 。
@@ -959,8 +961,12 @@ conan.details.repository=儲存庫
owner.settings.cleanuprules.enabled=已啟用
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=組織æè¿°
+
+
[actions]
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 3b25c81be3..85c796a3e4 100644
--- a/options/locale/locale_zh-TW.ini
+++ b/options/locale/locale_zh-TW.ini
@@ -1318,7 +1318,6 @@ editor.update=æ›´æ–° %s
editor.delete=刪除 %s
editor.patch=套用 Patch
editor.patching=正在 Patch:
-editor.fail_to_apply_patch=無法套用 Patch「%sã€
editor.new_patch=新增 Patch
editor.commit_message_desc=(é¸ç”¨) 加入詳細說明...
editor.signoff_desc=在æäº¤è¨Šæ¯åº•部加入æäº¤è€…的「Signed-off-byã€è³‡è¨Šã€‚
@@ -1336,8 +1335,6 @@ editor.branch_already_exists=此儲存庫已有å為「%sã€çš„分支。
editor.directory_is_a_file=目錄å稱「%sã€å·²è¢«æ­¤å„²å­˜åº«çš„æª”案使用。
editor.file_is_a_symlink=`"%s" 是一個符號連çµã€‚符號連çµç„¡æ³•在網é ç·¨è¼¯å™¨ä¸­ç·¨è¼¯`
editor.filename_is_a_directory=檔å「%sã€å·²è¢«æ­¤å„²å­˜åº«çš„目錄å稱使用。
-editor.file_editing_no_longer_exists=æ­£è¦ç·¨è¼¯çš„æª”案「%sã€å·²ä¸å­˜åœ¨æ­¤å„²å­˜åº«ä¸­ã€‚
-editor.file_deleting_no_longer_exists=æ­£è¦åˆªé™¤çš„æª”案「%sã€å·²ä¸å­˜åœ¨æ­¤å„²å­˜åº«ä¸­ã€‚
editor.file_changed_while_editing=檔案內容在您編輯的途中已被變更。<a target="_blank" rel="noopener noreferrer" href="%s">按一下此處</a>查看更動的地方或<strong>冿¬¡æäº¤</strong>以覆蓋這些變更。
editor.file_already_exists=此儲存庫已有å為「%sã€çš„æª”案。
editor.commit_id_not_matching=æäº¤ ID 與您開始編輯時的 ID ä¸åŒ¹é…。請æäº¤åˆ°ä¸€å€‹è£œä¸åˆ†æ”¯ç„¶å¾Œåˆä½µã€‚
@@ -1345,8 +1342,6 @@ editor.push_out_of_date=推é€ä¼¼ä¹Žå·²éŽæ™‚。
editor.commit_empty_file_header=æäº¤ç©ºç™½æª”案
editor.commit_empty_file_text=你準備æäº¤çš„æª”案是空白的,是å¦ç¹¼çºŒï¼Ÿ
editor.no_changes_to_show=沒有å¯ä»¥é¡¯ç¤ºçš„變更。
-editor.fail_to_update_file=æ›´æ–°/建立檔案「%sã€å¤±æ•—。
-editor.fail_to_update_file_summary=錯誤訊æ¯:
editor.push_rejected_no_message=該變更被伺æœå™¨æ‹’絕但未æä¾›å…¶ä»–資訊。請檢查 Git Hook。
editor.push_rejected=該變更被伺æœå™¨æ‹’絕。請檢查 Git Hook。
editor.push_rejected_summary=完整的拒絕訊æ¯:
@@ -1361,6 +1356,7 @@ editor.require_signed_commit=分支僅接å—經簽署的æäº¤
editor.cherry_pick=Cherry-pick %s 到:
editor.revert=還原 %s 到:
+
commits.desc=ç€è¦½åŽŸå§‹ç¢¼ä¿®æ”¹æ­·ç¨‹ã€‚
commits.commits=次程å¼ç¢¼æäº¤
commits.no_commits=沒有共åŒçš„æäº¤ã€‚「%sã€å’Œã€Œ%sã€çš„æ­·å²å®Œå…¨ä¸åŒã€‚
@@ -1905,7 +1901,6 @@ pulls.cmd_instruction_checkout_title=檢出
pulls.cmd_instruction_checkout_desc=從您的專案儲存庫中,檢出一個新分支並測試變更。
pulls.cmd_instruction_merge_title=åˆä½µ
pulls.cmd_instruction_merge_desc=åˆä½µè®Šæ›´ä¸¦åœ¨ Gitea 上更新。
-pulls.cmd_instruction_merge_warning=警告:此æ“作無法åˆä½µåˆä½µè«‹æ±‚,因為未啟用「自動檢測手動åˆä½µã€
pulls.clear_merge_message=清除åˆä½µè¨Šæ¯
pulls.clear_merge_message_hint=清除åˆä½µè¨Šæ¯å°‡åƒ…移除æäº¤è¨Šæ¯å…§å®¹ï¼Œç•™ä¸‹ç”¢ç”Ÿçš„ git çµå°¾ï¼Œå¦‚「Co-Authored-By …ã€ã€‚
@@ -2308,7 +2303,6 @@ settings.event_repository=儲存庫
settings.event_repository_desc=建立或刪除儲存庫。
settings.event_header_issue=å•題事件
settings.event_issues=å•題
-settings.event_issues_desc=建立ã€ç·¨è¼¯ã€é—œé–‰åŠé‡æ–°é–‹æ”¾å•題。
settings.event_issue_assign=指派å•題
settings.event_issue_assign_desc=æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾å•題。
settings.event_issue_label=標籤
@@ -2319,7 +2313,6 @@ settings.event_issue_comment=å•題留言
settings.event_issue_comment_desc=已經建立ã€ç·¨è¼¯æˆ–刪除的å•題留言。
settings.event_header_pull_request=åˆä½µè«‹æ±‚事件
settings.event_pull_request=åˆä½µè«‹æ±‚
-settings.event_pull_request_desc=建立ã€ç·¨è¼¯ã€é—œé–‰åŠé‡æ–°é–‹æ”¾åˆä½µè«‹æ±‚。
settings.event_pull_request_assign=指派åˆä½µè«‹æ±‚
settings.event_pull_request_assign_desc=æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾åˆä½µè«‹æ±‚。
settings.event_pull_request_label=åˆä½µè«‹æ±‚標籤
@@ -2756,15 +2749,13 @@ settings.visibility.private_shortname=ç§æœ‰
settings.update_settings=更新設定
settings.update_setting_success=組織設定已更新。
-settings.change_orgname_prompt=注æ„:更改組織åç¨±å°‡åŒæ™‚更改組織的 URL 並釋放舊å稱。
-settings.change_orgname_redirect_prompt=舊的å稱被領用å‰ï¼Œæœƒé‡æ–°å°Žå‘æ–°å稱。
+
+
settings.update_avatar_success=已更新組織的大頭貼。
settings.delete=刪除組織
settings.delete_account=刪除這個組織
settings.delete_prompt=該組織將被永久刪除。此動作<strong>ä¸å¯</strong>還原ï¼
settings.confirm_delete_account=確èªåˆªé™¤çµ„ç¹”
-settings.delete_org_title=刪除組織
-settings.delete_org_desc=å³å°‡æ°¸ä¹…刪除這個組織,是å¦ç¹¼çºŒï¼Ÿ
settings.hooks_desc=此組織下的<strong>所有存儲庫</strong>都會觸發在此新增的 Webhook。
settings.labels_desc=在此處新增的標籤å¯ç”¨æ–¼æ­¤çµ„織下的<strong>所有儲存庫</strong>。
@@ -3635,12 +3626,13 @@ owner.settings.chef.keypair.description=é©—è­‰ Chef 註冊中心需è¦ä¸€å€‹å¯†é
secrets=Secret
description=Secret 會被傳給特定的 Action,其他情æ³ç„¡æ³•讀å–。
none=還沒有 Secret。
-creation=加入 Secret
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=æè¿°
creation.name_placeholder=ä¸å€åˆ†å¤§å°å¯«ï¼Œåªèƒ½åŒ…å«è‹±æ–‡å­—æ¯ã€æ•¸å­—ã€åº•ç·š ('_'),ä¸èƒ½ä»¥ GITEA_ 或 GITHUB_ 開頭。
creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。
-creation.success=已新增 Secret「%sã€ã€‚
-creation.failed=加入 Secret 失敗。
+
+
deletion=移除 Secret
deletion.description=移除 Secret 是永久的且ä¸å¯é‚„原,是å¦ç¹¼çºŒï¼Ÿ
deletion.success=已移除此 Secret。
diff --git a/package-lock.json b/package-lock.json
index 4c5963d0c8..21e29a2081 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,43 +10,43 @@
"@citation-js/plugin-csl": "0.7.18",
"@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.3",
- "@github/relative-time-element": "4.4.7",
- "@github/text-expander-element": "2.9.1",
+ "@github/relative-time-element": "4.4.8",
+ "@github/text-expander-element": "2.9.2",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
- "@primer/octicons": "19.15.1",
+ "@primer/octicons": "19.15.3",
"@silverwind/vue3-calendar-heatmap": "2.0.6",
"add-asset-webpack-plugin": "3.0.0",
- "ansi_up": "6.0.5",
- "asciinema-player": "3.9.0",
- "chart.js": "4.4.9",
+ "ansi_up": "6.0.6",
+ "asciinema-player": "3.10.0",
+ "chart.js": "4.5.0",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
- "clippie": "4.1.6",
+ "clippie": "4.1.7",
"cropperjs": "1.6.2",
"css-loader": "7.1.2",
"dayjs": "1.11.13",
"dropzone": "6.0.0-beta.2",
"easymde": "2.20.0",
"esbuild-loader": "4.3.0",
- "escape-goat": "4.0.0",
"fast-glob": "3.3.3",
- "htmx.org": "2.0.4",
+ "htmx.org": "2.0.6",
"idiomorph": "0.7.3",
"jquery": "3.7.1",
"katex": "0.16.22",
"license-checker-webpack-plugin": "0.2.1",
- "mermaid": "11.6.0",
+ "mermaid": "11.8.0",
"mini-css-extract-plugin": "2.9.2",
- "minimatch": "10.0.1",
+ "minimatch": "10.0.3",
"monaco-editor": "0.52.2",
"monaco-editor-webpack-plugin": "7.1.0",
+ "online-3d-viewer": "0.16.0",
"pdfobject": "2.3.1",
"perfect-debounce": "1.0.0",
- "postcss": "8.5.3",
+ "postcss": "8.5.6",
"postcss-loader": "8.1.1",
- "postcss-nesting": "13.0.1",
+ "postcss-nesting": "13.0.2",
"sortablejs": "1.15.6",
- "swagger-ui-dist": "5.21.0",
+ "swagger-ui-dist": "5.26.0",
"tailwindcss": "3.4.17",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
@@ -54,70 +54,70 @@
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"typescript": "5.8.3",
- "uint8-to-base64": "0.2.0",
+ "uint8-to-base64": "0.2.1",
"vanilla-colorful": "0.7.2",
- "vue": "3.5.13",
+ "vue": "3.5.17",
"vue-bar-graph": "2.2.0",
"vue-chartjs": "5.3.2",
"vue-loader": "17.4.2",
- "webpack": "5.99.8",
+ "webpack": "5.99.9",
"webpack-cli": "6.0.1",
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
- "@playwright/test": "1.52.0",
+ "@playwright/test": "1.53.2",
"@stoplight/spectral-cli": "6.15.0",
"@stylistic/eslint-plugin-js": "3.1.0",
- "@stylistic/stylelint-plugin": "3.1.2",
+ "@stylistic/stylelint-plugin": "3.1.3",
"@types/dropzone": "5.7.9",
"@types/jquery": "3.5.32",
"@types/katex": "0.16.7",
- "@types/license-checker-webpack-plugin": "0.2.4",
+ "@types/license-checker-webpack-plugin": "0.2.5",
"@types/pdfobject": "2.2.5",
"@types/sortablejs": "1.15.8",
- "@types/swagger-ui-dist": "3.30.5",
+ "@types/swagger-ui-dist": "3.30.6",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/toastify-js": "1.12.4",
- "@typescript-eslint/eslint-plugin": "8.32.0",
- "@typescript-eslint/parser": "8.32.0",
- "@vitejs/plugin-vue": "5.2.3",
- "@vitest/eslint-plugin": "1.1.44",
+ "@typescript-eslint/eslint-plugin": "8.35.1",
+ "@typescript-eslint/parser": "8.35.1",
+ "@vitejs/plugin-vue": "6.0.0",
+ "@vitest/eslint-plugin": "1.3.4",
"eslint": "8.57.0",
- "eslint-import-resolver-typescript": "4.3.4",
+ "eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "5.0.2",
- "eslint-plugin-import-x": "4.11.0",
+ "eslint-plugin-import-x": "4.16.1",
"eslint-plugin-no-jquery": "3.1.1",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-playwright": "2.2.0",
- "eslint-plugin-regexp": "2.7.0",
- "eslint-plugin-sonarjs": "3.0.2",
+ "eslint-plugin-regexp": "2.9.0",
+ "eslint-plugin-sonarjs": "3.0.4",
"eslint-plugin-unicorn": "56.0.1",
- "eslint-plugin-vue": "10.1.0",
- "eslint-plugin-vue-scoped-css": "2.9.0",
+ "eslint-plugin-vue": "10.3.0",
+ "eslint-plugin-vue-scoped-css": "2.11.0",
"eslint-plugin-wc": "3.0.1",
- "happy-dom": "17.4.6",
- "markdownlint-cli": "0.44.0",
- "material-icon-theme": "5.22.0",
+ "happy-dom": "18.0.1",
+ "markdownlint-cli": "0.45.0",
+ "material-icon-theme": "5.24.0",
"nolyfill": "1.0.44",
"postcss-html": "1.8.0",
- "stylelint": "16.19.1",
+ "stylelint": "16.21.1",
"stylelint-config-recommended": "16.0.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.11",
- "stylelint-define-config": "16.19.0",
+ "stylelint-define-config": "16.21.0",
"stylelint-value-no-unknown-custom-properties": "6.0.1",
- "svgo": "3.3.2",
+ "svgo": "4.0.0",
"type-fest": "4.41.0",
"updates": "16.4.2",
"vite-string-plugin": "1.4.4",
- "vitest": "3.1.3",
- "vue-tsc": "2.2.10"
+ "vitest": "3.2.4",
+ "vue-tsc": "3.0.1"
},
"engines": {
- "node": ">= 18.0.0"
+ "node": ">= 20.0.0"
}
},
"node_modules/@alloc/quick-lru": {
@@ -203,12 +203,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
- "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.27.1"
+ "@babel/types": "^7.28.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -218,18 +218,18 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
- "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
+ "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
- "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz",
+ "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -431,9 +431,9 @@
}
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
- "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"dev": true,
"funding": [
{
@@ -450,13 +450,13 @@
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-tokenizer": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
- "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"dev": true,
"funding": [
{
@@ -552,9 +552,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
- "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
+ "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"cpu": [
"ppc64"
],
@@ -568,9 +568,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
- "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
+ "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
@@ -584,9 +584,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
- "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
+ "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"cpu": [
"arm64"
],
@@ -600,9 +600,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
- "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
+ "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"cpu": [
"x64"
],
@@ -616,9 +616,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
- "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
+ "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
@@ -632,9 +632,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
- "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
+ "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"cpu": [
"x64"
],
@@ -648,9 +648,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
- "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"cpu": [
"arm64"
],
@@ -664,9 +664,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
- "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
+ "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"cpu": [
"x64"
],
@@ -680,9 +680,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
- "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
+ "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
@@ -696,9 +696,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
- "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
+ "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"cpu": [
"arm64"
],
@@ -712,9 +712,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
- "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
+ "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"cpu": [
"ia32"
],
@@ -728,9 +728,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
- "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
+ "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
@@ -744,9 +744,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
- "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
+ "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"cpu": [
"mips64el"
],
@@ -760,9 +760,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
- "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
+ "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"cpu": [
"ppc64"
],
@@ -776,9 +776,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
- "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
+ "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"cpu": [
"riscv64"
],
@@ -792,9 +792,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
- "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
+ "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
@@ -808,9 +808,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
- "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
+ "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
"cpu": [
"x64"
],
@@ -824,9 +824,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
- "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"cpu": [
"arm64"
],
@@ -840,9 +840,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
- "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"cpu": [
"x64"
],
@@ -856,9 +856,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
- "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"cpu": [
"arm64"
],
@@ -872,9 +872,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
- "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"cpu": [
"x64"
],
@@ -888,9 +888,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
- "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
+ "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"cpu": [
"x64"
],
@@ -904,9 +904,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
- "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
+ "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"cpu": [
"arm64"
],
@@ -920,9 +920,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
- "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
+ "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
@@ -936,9 +936,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
- "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
+ "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"cpu": [
"x64"
],
@@ -1054,17 +1054,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
@@ -1146,19 +1135,19 @@
"license": "MIT"
},
"node_modules/@github/relative-time-element": {
- "version": "4.4.7",
- "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.7.tgz",
- "integrity": "sha512-NZCePEFYtV7qAUI/pHYuqZ8vRhcsfH/dziUZTY9YR5+JwzDCWtEokYSDbDLZjrRl+SAFr02YHUK+UdtP6hPcbQ==",
+ "version": "4.4.8",
+ "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.8.tgz",
+ "integrity": "sha512-FSLYm6F3TSQnqHE1EMQUVVgi2XjbCvsESwwXfugHFpBnhyF1uhJOtu0Psp/BB/qqazfdkk7f5fVcu7WuXl3t8Q==",
"license": "MIT"
},
"node_modules/@github/text-expander-element": {
- "version": "2.9.1",
- "resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.9.1.tgz",
- "integrity": "sha512-T/pCDjB/diMaarmcdc01hP026v0b9lidluyZD5z/EPOExXRdNDqb11kOXevoMZY42WiI3Yhoqsj3nbM+HthLgQ==",
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.9.2.tgz",
+ "integrity": "sha512-XY8EUMqM4GAloNxXNA1Py1ny+engWwYntbgsnpstQN4piaTI9rIlfYldyd0nnPXhxjGCVqHPmP6yg17Q0/n9Vg==",
"license": "MIT",
"dependencies": {
"@github/combobox-nav": "^2.0.2",
- "dom-input-range": "^1.2.0"
+ "dom-input-range": "^2.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
@@ -1177,17 +1166,6 @@
"node": ">=10.10.0"
}
},
- "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -1257,6 +1235,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@isaacs/balanced-match": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
+ "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/brace-expansion": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
+ "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@isaacs/balanced-match": "^4.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -1348,17 +1347,13 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -1370,19 +1365,10 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/source-map": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
- "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
+ "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1390,15 +1376,15 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1537,24 +1523,24 @@
}
},
"node_modules/@mermaid-js/parser": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz",
- "integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.0.tgz",
+ "integrity": "sha512-7DNESgpyZ5WG1SIkrYafVBhWmImtmQuoxOO1lawI3gQYWxBX3v1FW3IyuuRfKJAO06XrZR71W0Kif5VEGGd4VA==",
"license": "MIT",
"dependencies": {
"langium": "3.3.1"
}
},
"node_modules/@napi-rs/wasm-runtime": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz",
- "integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==",
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz",
+ "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.4.0",
- "@emnapi/runtime": "^1.4.0",
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.9.0"
}
},
@@ -1611,9 +1597,9 @@
}
},
"node_modules/@pkgr/core": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz",
- "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
+ "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1624,13 +1610,13 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz",
- "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==",
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz",
+ "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright": "1.52.0"
+ "playwright": "1.53.2"
},
"bin": {
"playwright": "cli.js"
@@ -1650,14 +1636,21 @@
}
},
"node_modules/@primer/octicons": {
- "version": "19.15.1",
- "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.15.1.tgz",
- "integrity": "sha512-2cFLIrfbNvfeAPxmJTr/k7/b5QvIxqM7MeroGrXojDodA2yB1nXyt91Xhu9+2e0CL5ZAe3Vd2/fEf9N9EUiX9A==",
+ "version": "19.15.3",
+ "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.15.3.tgz",
+ "integrity": "sha512-SVD0JbTzabLqLx4d5Cl3cyY+i0u3j5/lI6P3FyNkkJb9Z4VAQ+mBvl7WgK0ZFEXBsLc2gxnoYXvC3TkNSEz1NQ==",
"license": "MIT",
"dependencies": {
"object-assign": "^4.1.1"
}
},
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.19",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
+ "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@rollup/plugin-commonjs": {
"version": "22.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz",
@@ -1706,9 +1699,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz",
- "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.2.tgz",
+ "integrity": "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==",
"cpu": [
"arm"
],
@@ -1720,9 +1713,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz",
- "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.2.tgz",
+ "integrity": "sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==",
"cpu": [
"arm64"
],
@@ -1734,9 +1727,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz",
- "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.2.tgz",
+ "integrity": "sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==",
"cpu": [
"arm64"
],
@@ -1748,9 +1741,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz",
- "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.2.tgz",
+ "integrity": "sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==",
"cpu": [
"x64"
],
@@ -1762,9 +1755,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz",
- "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.2.tgz",
+ "integrity": "sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==",
"cpu": [
"arm64"
],
@@ -1776,9 +1769,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz",
- "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.2.tgz",
+ "integrity": "sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==",
"cpu": [
"x64"
],
@@ -1790,9 +1783,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz",
- "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.2.tgz",
+ "integrity": "sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==",
"cpu": [
"arm"
],
@@ -1804,9 +1797,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz",
- "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.2.tgz",
+ "integrity": "sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==",
"cpu": [
"arm"
],
@@ -1818,9 +1811,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz",
- "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.2.tgz",
+ "integrity": "sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==",
"cpu": [
"arm64"
],
@@ -1832,9 +1825,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz",
- "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.2.tgz",
+ "integrity": "sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==",
"cpu": [
"arm64"
],
@@ -1846,9 +1839,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz",
- "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.2.tgz",
+ "integrity": "sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==",
"cpu": [
"loong64"
],
@@ -1860,9 +1853,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz",
- "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.2.tgz",
+ "integrity": "sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==",
"cpu": [
"ppc64"
],
@@ -1874,9 +1867,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz",
- "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.2.tgz",
+ "integrity": "sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==",
"cpu": [
"riscv64"
],
@@ -1888,9 +1881,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz",
- "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.2.tgz",
+ "integrity": "sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==",
"cpu": [
"riscv64"
],
@@ -1902,9 +1895,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz",
- "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.2.tgz",
+ "integrity": "sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==",
"cpu": [
"s390x"
],
@@ -1916,9 +1909,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz",
- "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.2.tgz",
+ "integrity": "sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==",
"cpu": [
"x64"
],
@@ -1930,9 +1923,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz",
- "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.2.tgz",
+ "integrity": "sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==",
"cpu": [
"x64"
],
@@ -1944,9 +1937,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz",
- "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.2.tgz",
+ "integrity": "sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==",
"cpu": [
"arm64"
],
@@ -1958,9 +1951,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz",
- "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.2.tgz",
+ "integrity": "sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==",
"cpu": [
"ia32"
],
@@ -1972,9 +1965,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz",
- "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.2.tgz",
+ "integrity": "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==",
"cpu": [
"x64"
],
@@ -2012,6 +2005,16 @@
"vue": "^3.2.29"
}
},
+ "node_modules/@simonwep/pickr": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.9.0.tgz",
+ "integrity": "sha512-oEYvv15PyfZzjoAzvXYt3UyNGwzsrpFxLaZKzkOSd0WYBVwLd19iJerePDONxC1iF6+DpcswPdLIM2KzCJuYFg==",
+ "license": "MIT",
+ "dependencies": {
+ "core-js": "3.32.2",
+ "nanopop": "2.3.0"
+ }
+ },
"node_modules/@stoplight/better-ajv-errors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz",
@@ -2221,17 +2224,6 @@
"node": "^12.20 || >=14.13"
}
},
- "node_modules/@stoplight/spectral-core/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/@stoplight/spectral-core/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2548,9 +2540,9 @@
}
},
"node_modules/@stylistic/stylelint-plugin": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.2.tgz",
- "integrity": "sha512-tylFJGMQo62alGazK74MNxFjMagYOHmBZiePZFOJK2n13JZta0uVkB3Bh5qodUmOLtRH+uxH297EibK14UKm8g==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.3.tgz",
+ "integrity": "sha512-85fsmzgsIVmyG3/GFrjuYj6Cz8rAM7IZiPiXCMiSMfoDOC1lOrzrXPDk24WqviAghnPqGpx8b0caK2PuewWGFg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2558,10 +2550,10 @@
"@csstools/css-tokenizer": "^3.0.1",
"@csstools/media-query-list-parser": "^3.0.1",
"is-plain-object": "^5.0.0",
+ "postcss": "^8.4.41",
"postcss-selector-parser": "^6.1.2",
"postcss-value-parser": "^4.2.0",
- "style-search": "^0.1.0",
- "stylelint": "^16.8.2"
+ "style-search": "^0.1.0"
},
"engines": {
"node": "^18.12 || >=20.9"
@@ -2576,16 +2568,6 @@
"integrity": "sha512-wpCQMhf5p5GhNg2MmGKXzUNwxe7zRiCsmqYsamez2beP7mKPCSiu+BjZcdN95yYSzO857kr0VfQewmGpS77nqA==",
"license": "MIT"
},
- "node_modules/@trysound/sax": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
- "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/@tybys/wasm-util": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
@@ -2597,10 +2579,20 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
+ "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*"
+ }
+ },
"node_modules/@types/codemirror": {
- "version": "5.60.15",
- "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.15.tgz",
- "integrity": "sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==",
+ "version": "5.60.16",
+ "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.16.tgz",
+ "integrity": "sha512-V/yHdamffSS075jit+fDxaOAmdP2liok8NSNJnAZfDJErzOheuygHZEhAJrfmk5TEyM32MhkZjwo/idX791yxw==",
"license": "MIT",
"dependencies": {
"@types/tern": "*"
@@ -2869,6 +2861,13 @@
"@types/ms": "*"
}
},
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/dropzone": {
"version": "5.7.9",
"resolved": "https://registry.npmjs.org/@types/dropzone/-/dropzone-5.7.9.tgz",
@@ -2958,9 +2957,9 @@
"license": "MIT"
},
"node_modules/@types/license-checker-webpack-plugin": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@types/license-checker-webpack-plugin/-/license-checker-webpack-plugin-0.2.4.tgz",
- "integrity": "sha512-QTWqHJ5T9lgm3vPwWSZnBwAB+15zl4QBfGoNDcjnthHQEP8VTV87fYfp1HVeCtrDip73xWMtasQeA4QHQ0nFLw==",
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@types/license-checker-webpack-plugin/-/license-checker-webpack-plugin-0.2.5.tgz",
+ "integrity": "sha512-raj3YPZxjkDlRrJJUq6So+C9i/fqYxUtVZzlXxm6bwwXONOnTHvGCfYmbQhzsHJneiJdlR6mMH76/bvWbZZN8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2988,12 +2987,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.15.14",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.14.tgz",
- "integrity": "sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==",
+ "version": "24.0.10",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
+ "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"license": "MIT",
"dependencies": {
- "undici-types": "~6.21.0"
+ "undici-types": "~7.8.0"
}
},
"node_modules/@types/normalize-package-data": {
@@ -3039,9 +3038,9 @@
"license": "MIT"
},
"node_modules/@types/swagger-ui-dist": {
- "version": "3.30.5",
- "resolved": "https://registry.npmjs.org/@types/swagger-ui-dist/-/swagger-ui-dist-3.30.5.tgz",
- "integrity": "sha512-SrXhD9L8qeIxJzN+o1kmf3wXeVf/+Km3jIdRM1+Yq3I5b/dlF5TcGr5WCVM7I/cBYpgf43/gCPIucQ13AhICiw==",
+ "version": "3.30.6",
+ "resolved": "https://registry.npmjs.org/@types/swagger-ui-dist/-/swagger-ui-dist-3.30.6.tgz",
+ "integrity": "sha512-FVxN7wjLYRtJsZBscOcOcf8oR++m38vbUFjT33Mr9HBuasX9bRDrJsp7iwixcOtKSHEEa2B7o2+4wEiXqC+Ebw==",
"dev": true,
"license": "MIT"
},
@@ -3150,20 +3149,27 @@
"node": ">= 8"
}
},
+ "node_modules/@types/whatwg-mimetype": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
+ "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz",
- "integrity": "sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
+ "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.32.0",
- "@typescript-eslint/type-utils": "8.32.0",
- "@typescript-eslint/utils": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/type-utils": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"graphemer": "^1.4.0",
- "ignore": "^5.3.1",
+ "ignore": "^7.0.0",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0"
},
@@ -3175,22 +3181,32 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+ "@typescript-eslint/parser": "^8.35.1",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/@typescript-eslint/parser": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.0.tgz",
- "integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
+ "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.32.0",
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/typescript-estree": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0",
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4"
},
"engines": {
@@ -3205,33 +3221,72 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
+ "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.35.1",
+ "@typescript-eslint/types": "^8.35.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.0.tgz",
- "integrity": "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
+ "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0"
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
+ "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
+ "dev": true,
+ "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.0.tgz",
- "integrity": "sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
+ "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.32.0",
- "@typescript-eslint/utils": "8.32.0",
+ "@typescript-eslint/typescript-estree": "8.35.1",
+ "@typescript-eslint/utils": "8.35.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -3248,9 +3303,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.0.tgz",
- "integrity": "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz",
+ "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3262,14 +3317,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.0.tgz",
- "integrity": "sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
+ "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/visitor-keys": "8.32.0",
+ "@typescript-eslint/project-service": "8.35.1",
+ "@typescript-eslint/tsconfig-utils": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/visitor-keys": "8.35.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -3288,6 +3345,23 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -3305,16 +3379,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.0.tgz",
- "integrity": "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz",
+ "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.32.0",
- "@typescript-eslint/types": "8.32.0",
- "@typescript-eslint/typescript-estree": "8.32.0"
+ "@typescript-eslint/scope-manager": "8.35.1",
+ "@typescript-eslint/types": "8.35.1",
+ "@typescript-eslint/typescript-estree": "8.35.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3329,14 +3403,14 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.0.tgz",
- "integrity": "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==",
+ "version": "8.35.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
+ "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.32.0",
- "eslint-visitor-keys": "^4.2.0"
+ "@typescript-eslint/types": "8.35.1",
+ "eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3353,10 +3427,38 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.10.1.tgz",
+ "integrity": "sha512-zohDKXT1Ok0yhbVGff4YAg9HUs5ietG5GpvJBPFSApZnGe7uf2cd26DRhKZbn0Be6xHUZrSzP+RAgMmzyc71EA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.10.1.tgz",
+ "integrity": "sha512-tAN6k5UrTd4nicpA7s2PbjR/jagpDzAmvXFjbpTazUe5FRsFxVcBlS1F5Lzp5jtWU6bdiqRhSvd4X8rdpCffeA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
"node_modules/@unrs/resolver-binding-darwin-arm64": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz",
- "integrity": "sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.10.1.tgz",
+ "integrity": "sha512-+FCsag8WkauI4dQ50XumCXdfvDCZEpMUnvZDsKMxfOisnEklpDFXc6ThY0WqybBYZbiwR5tWcFaZmI0G6b4vrg==",
"cpu": [
"arm64"
],
@@ -3368,9 +3470,9 @@
]
},
"node_modules/@unrs/resolver-binding-darwin-x64": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz",
- "integrity": "sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.10.1.tgz",
+ "integrity": "sha512-qYKGGm5wk71ONcXTMZ0+J11qQeOAPz3nw6VtqrBUUELRyXFyvK8cHhHsLBFR4GHnilc2pgY1HTB2TvdW9wO26Q==",
"cpu": [
"x64"
],
@@ -3382,9 +3484,9 @@
]
},
"node_modules/@unrs/resolver-binding-freebsd-x64": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz",
- "integrity": "sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.10.1.tgz",
+ "integrity": "sha512-hOHMAhbvIQ63gkpgeNsXcWPSyvXH7ZEyeg254hY0Lp/hX8NdW+FsUWq73g9946Pc/BrcVI/I3C1cmZ4RCX9bNw==",
"cpu": [
"x64"
],
@@ -3396,9 +3498,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz",
- "integrity": "sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.10.1.tgz",
+ "integrity": "sha512-6ds7+zzHJgTDmpe0gmFcOTvSUhG5oZukkt+cCsSb3k4Uiz2yEQB4iCRITX2hBwSW+p8gAieAfecITjgqCkswXw==",
"cpu": [
"arm"
],
@@ -3410,9 +3512,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz",
- "integrity": "sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.10.1.tgz",
+ "integrity": "sha512-P7A0G2/jW00diNJyFeq4W9/nxovD62Ay8CMP4UK9OymC7qO7rG1a8Upad68/bdfpIOn7KSp7Aj/6lEW3yyznAA==",
"cpu": [
"arm"
],
@@ -3424,9 +3526,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz",
- "integrity": "sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.10.1.tgz",
+ "integrity": "sha512-Cg6xzdkrpltcTPO4At+A79zkC7gPDQIgosJmVV8M104ImB6KZi1MrNXgDYIAfkhUYjPzjNooEDFRAwwPadS7ZA==",
"cpu": [
"arm64"
],
@@ -3438,9 +3540,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-arm64-musl": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz",
- "integrity": "sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.10.1.tgz",
+ "integrity": "sha512-aNeg99bVkXa4lt+oZbjNRPC8ZpjJTKxijg/wILrJdzNyAymO2UC/HUK1UfDjt6T7U5p/mK24T3CYOi3/+YEQSA==",
"cpu": [
"arm64"
],
@@ -3452,9 +3554,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz",
- "integrity": "sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.10.1.tgz",
+ "integrity": "sha512-ylz5ojeXrkPrtnzVhpCO+YegG63/aKhkoTlY8PfMfBfLaUG8v6m6iqrL7sBUKdVBgOB4kSTUPt9efQdA/Y3Z/w==",
"cpu": [
"ppc64"
],
@@ -3466,9 +3568,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz",
- "integrity": "sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.10.1.tgz",
+ "integrity": "sha512-xcWyhmJfXXOxK7lvE4+rLwBq+on83svlc0AIypfe6x4sMJR+S4oD7n9OynaQShfj2SufPw2KJAotnsNb+4nN2g==",
"cpu": [
"riscv64"
],
@@ -3480,9 +3582,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz",
- "integrity": "sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.10.1.tgz",
+ "integrity": "sha512-mW9JZAdOCyorgi1eLJr4gX7xS67WNG9XNPYj5P8VuttK72XNsmdw9yhOO4tDANMgiLXFiSFaiL1gEpoNtRPw/A==",
"cpu": [
"riscv64"
],
@@ -3494,9 +3596,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz",
- "integrity": "sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.10.1.tgz",
+ "integrity": "sha512-NZGKhBy6xkJ0k09cWNZz4DnhBcGlhDd3W+j7EYoNvf5TSwj2K6kbmfqTWITEgkvjsMUjm1wsrc4IJaH6VtjyHQ==",
"cpu": [
"s390x"
],
@@ -3508,9 +3610,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-x64-gnu": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz",
- "integrity": "sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.10.1.tgz",
+ "integrity": "sha512-VsjgckJ0gNMw7p0d8In6uPYr+s0p16yrT2rvG4v2jUpEMYkpnfnCiALa9SWshbvlGjKQ98Q2x19agm3iFk8w8Q==",
"cpu": [
"x64"
],
@@ -3522,9 +3624,9 @@
]
},
"node_modules/@unrs/resolver-binding-linux-x64-musl": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz",
- "integrity": "sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.10.1.tgz",
+ "integrity": "sha512-idMnajMeejnaFi0Mx9UTLSYFDAOTfAEP7VjXNgxKApso3Eu2Njs0p2V95nNIyFi4oQVGFmIuCkoznAXtF/Zbmw==",
"cpu": [
"x64"
],
@@ -3536,9 +3638,9 @@
]
},
"node_modules/@unrs/resolver-binding-wasm32-wasi": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz",
- "integrity": "sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.10.1.tgz",
+ "integrity": "sha512-7jyhjIRNFjzlr8x5pth6Oi9hv3a7ubcVYm2GBFinkBQKcFhw4nIs5BtauSNtDW1dPIGrxF0ciynCZqzxMrYMsg==",
"cpu": [
"wasm32"
],
@@ -3546,16 +3648,16 @@
"license": "MIT",
"optional": true,
"dependencies": {
- "@napi-rs/wasm-runtime": "^0.2.9"
+ "@napi-rs/wasm-runtime": "^0.2.11"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz",
- "integrity": "sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.10.1.tgz",
+ "integrity": "sha512-TY79+N+Gkoo7E99K+zmsKNeiuNJYlclZJtKqsHSls8We2iGhgxtletVsiBYie93MSTDRDMI8pkBZJlIJSZPrdA==",
"cpu": [
"arm64"
],
@@ -3567,9 +3669,9 @@
]
},
"node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz",
- "integrity": "sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.10.1.tgz",
+ "integrity": "sha512-BAJN5PEPlEV+1m8+PCtFoKm3LQ1P57B4Z+0+efU0NzmCaGk7pUaOxuPgl+m3eufVeeNBKiPDltG0sSB9qEfCxw==",
"cpu": [
"ia32"
],
@@ -3581,9 +3683,9 @@
]
},
"node_modules/@unrs/resolver-binding-win32-x64-msvc": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz",
- "integrity": "sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.10.1.tgz",
+ "integrity": "sha512-2v3erKKmmCyIVvvhI2nF15qEbdBpISTq44m9pyd5gfIJB1PN94oePTLWEd82XUbIbvKhv76xTSeUQSCOGesLeg==",
"cpu": [
"x64"
],
@@ -3595,27 +3697,32 @@
]
},
"node_modules/@vitejs/plugin-vue": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz",
- "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.0.tgz",
+ "integrity": "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-beta.19"
+ },
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
- "vite": "^5.0.0 || ^6.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vitest/eslint-plugin": {
- "version": "1.1.44",
- "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.44.tgz",
- "integrity": "sha512-m4XeohMT+Dj2RZfxnbiFR+Cv5dEC0H7C6TlxRQT7GK2556solm99kxgzJp/trKrZvanZcOFyw7aABykUTfWyrg==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.3.4.tgz",
+ "integrity": "sha512-EOg8d0jn3BAiKnR55WkFxmxfWA3nmzrbIIuOXyTe6A72duryNgyU+bdBEauA97Aab3ho9kLmAwgPX63Ckj4QEg==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/utils": "^8.24.1"
+ },
"peerDependencies": {
- "@typescript-eslint/utils": ">= 8.24.0",
"eslint": ">= 8.57.0",
"typescript": ">= 5.0.0",
"vitest": "*"
@@ -3630,14 +3737,15 @@
}
},
"node_modules/@vitest/expect": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz",
- "integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.1.3",
- "@vitest/utils": "3.1.3",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
"chai": "^5.2.0",
"tinyrainbow": "^2.0.0"
},
@@ -3646,13 +3754,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz",
- "integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.1.3",
+ "@vitest/spy": "3.2.4",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
@@ -3661,7 +3769,7 @@
},
"peerDependencies": {
"msw": "^2.4.9",
- "vite": "^5.0.0 || ^6.0.0"
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
},
"peerDependenciesMeta": {
"msw": {
@@ -3673,9 +3781,9 @@
}
},
"node_modules/@vitest/mocker/node_modules/@types/estree": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
@@ -3700,9 +3808,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz",
- "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3713,27 +3821,28 @@
}
},
"node_modules/@vitest/runner": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz",
- "integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "3.1.3",
- "pathe": "^2.0.3"
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz",
- "integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.1.3",
+ "@vitest/pretty-format": "3.2.4",
"magic-string": "^0.30.17",
"pathe": "^2.0.3"
},
@@ -3752,27 +3861,27 @@
}
},
"node_modules/@vitest/spy": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz",
- "integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tinyspy": "^3.0.2"
+ "tinyspy": "^4.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz",
- "integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.1.3",
- "loupe": "^3.1.3",
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
"tinyrainbow": "^2.0.0"
},
"funding": {
@@ -3780,72 +3889,72 @@
}
},
"node_modules/@volar/language-core": {
- "version": "2.4.13",
- "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.13.tgz",
- "integrity": "sha512-MnQJ7eKchJx5Oz+YdbqyFUk8BN6jasdJv31n/7r6/WwlOOv7qzvot6B66887l2ST3bUW4Mewml54euzpJWA6bg==",
+ "version": "2.4.17",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.17.tgz",
+ "integrity": "sha512-chmRZMbKmcGpKMoO7Reb70uiLrzo0KWC2CkFttKUuKvrE+VYgi+fL9vWMJ07Fv5ulX0V1TAyyacN9q3nc5/ecA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/source-map": "2.4.13"
+ "@volar/source-map": "2.4.17"
}
},
"node_modules/@volar/source-map": {
- "version": "2.4.13",
- "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.13.tgz",
- "integrity": "sha512-l/EBcc2FkvHgz2ZxV+OZK3kMSroMr7nN3sZLF2/f6kWW66q8+tEL4giiYyFjt0BcubqJhBt6soYIrAPhg/Yr+Q==",
+ "version": "2.4.17",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.17.tgz",
+ "integrity": "sha512-QDybtQyO3Ms/NjFqNHTC5tbDN2oK5VH7ZaKrcubtfHBDj63n2pizHC3wlMQ+iT55kQXZUUAbmBX5L1C8CHFeBw==",
"dev": true,
"license": "MIT"
},
"node_modules/@volar/typescript": {
- "version": "2.4.13",
- "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.13.tgz",
- "integrity": "sha512-Ukz4xv84swJPupZeoFsQoeJEOm7U9pqsEnaGGgt5ni3SCTa22m8oJP5Nng3Wed7Uw5RBELdLxxORX8YhJPyOgQ==",
+ "version": "2.4.17",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.17.tgz",
+ "integrity": "sha512-3paEFNh4P5DkgNUB2YkTRrfUekN4brAXxd3Ow1syMqdIPtCZHbUy4AW99S5RO/7mzyTWPMdDSo3mqTpB/LPObQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/language-core": "2.4.13",
+ "@volar/language-core": "2.4.17",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
- "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz",
+ "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.25.3",
- "@vue/shared": "3.5.13",
+ "@babel/parser": "^7.27.5",
+ "@vue/shared": "3.5.17",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
- "source-map-js": "^1.2.0"
+ "source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
- "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz",
+ "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.13",
- "@vue/shared": "3.5.13"
+ "@vue/compiler-core": "3.5.17",
+ "@vue/shared": "3.5.17"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
- "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz",
+ "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.25.3",
- "@vue/compiler-core": "3.5.13",
- "@vue/compiler-dom": "3.5.13",
- "@vue/compiler-ssr": "3.5.13",
- "@vue/shared": "3.5.13",
+ "@babel/parser": "^7.27.5",
+ "@vue/compiler-core": "3.5.17",
+ "@vue/compiler-dom": "3.5.17",
+ "@vue/compiler-ssr": "3.5.17",
+ "@vue/shared": "3.5.17",
"estree-walker": "^2.0.2",
- "magic-string": "^0.30.11",
- "postcss": "^8.4.48",
- "source-map-js": "^1.2.0"
+ "magic-string": "^0.30.17",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-sfc/node_modules/magic-string": {
@@ -3858,13 +3967,13 @@
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
- "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz",
+ "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.13",
- "@vue/shared": "3.5.13"
+ "@vue/compiler-dom": "3.5.17",
+ "@vue/shared": "3.5.17"
}
},
"node_modules/@vue/compiler-vue2": {
@@ -3879,18 +3988,18 @@
}
},
"node_modules/@vue/language-core": {
- "version": "2.2.10",
- "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.10.tgz",
- "integrity": "sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.0.1.tgz",
+ "integrity": "sha512-sq+/Mc1IqIexWEQ+Q2XPiDb5SxSvY5JPqHnMOl/PlF5BekslzduX8dglSkpC17VeiAQB6dpS+4aiwNLJRduCNw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/language-core": "~2.4.11",
+ "@volar/language-core": "2.4.17",
"@vue/compiler-dom": "^3.5.0",
"@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0",
- "alien-signals": "^1.0.3",
- "minimatch": "^9.0.3",
+ "alien-signals": "^2.0.5",
+ "minimatch": "^10.0.1",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1"
},
@@ -3903,70 +4012,54 @@
}
}
},
- "node_modules/@vue/language-core/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/@vue/reactivity": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
- "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz",
+ "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.13"
+ "@vue/shared": "3.5.17"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
- "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz",
+ "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.13",
- "@vue/shared": "3.5.13"
+ "@vue/reactivity": "3.5.17",
+ "@vue/shared": "3.5.17"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
- "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz",
+ "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.13",
- "@vue/runtime-core": "3.5.13",
- "@vue/shared": "3.5.13",
+ "@vue/reactivity": "3.5.17",
+ "@vue/runtime-core": "3.5.17",
+ "@vue/shared": "3.5.17",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
- "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz",
+ "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.13",
- "@vue/shared": "3.5.13"
+ "@vue/compiler-ssr": "3.5.17",
+ "@vue/shared": "3.5.17"
},
"peerDependencies": {
- "vue": "3.5.13"
+ "vue": "3.5.17"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
- "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz",
+ "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==",
"license": "MIT"
},
"node_modules/@webassemblyjs/ast": {
@@ -4185,9 +4278,9 @@
}
},
"node_modules/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -4292,16 +4385,16 @@
}
},
"node_modules/alien-signals": {
- "version": "1.0.13",
- "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
- "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.5.tgz",
+ "integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==",
"dev": true,
"license": "MIT"
},
"node_modules/ansi_up": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.5.tgz",
- "integrity": "sha512-bo4K8S5usgFivfgvgQozTC2EfusPf76o7w0LUVdAOkpISvVmQqtwCdF5c6okokrgIN13KhFIVB/0BhnNXueQeA==",
+ "version": "6.0.6",
+ "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.6.tgz",
+ "integrity": "sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA==",
"license": "MIT",
"engines": {
"node": "*"
@@ -4458,9 +4551,9 @@
}
},
"node_modules/asciinema-player": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.9.0.tgz",
- "integrity": "sha512-SXVFImVzeNr8ZUdNIHABGuzlbnGWTKy245AquAjODsAnv+Lp6vxjYGN0LfA8ns30tnx/ag/bMrTbLq13TpHE6w==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.10.0.tgz",
+ "integrity": "sha512-shoOK6F606nDKZxDVM7JuGSCAyWLePoGRFNlV+FqiP5Sqvyn0BlE7wlbjZyd2X4P1iRhv/HKfVNtnQIxmgphRA==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.21.0",
@@ -4551,9 +4644,10 @@
}
},
"node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
+ "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
@@ -4605,14 +4699,21 @@
"license": "ISC"
},
"node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
}
},
+ "node_modules/brace-expansion/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
@@ -4626,9 +4727,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.24.5",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
- "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"funding": [
{
"type": "opencollective",
@@ -4645,8 +4746,8 @@
],
"license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001716",
- "electron-to-chromium": "^1.5.149",
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
@@ -4728,20 +4829,20 @@
}
},
"node_modules/cacheable": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz",
- "integrity": "sha512-8D5htMCxPDUULux9gFzv30f04Xo3wCnik0oOxKoRTPIBoqA7HtOcJ87uBhQTs3jCfZZTrUBGsYIZOgE0ZRgMAg==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.1.tgz",
+ "integrity": "sha512-Fa2BZY0CS9F0PFc/6aVA6tgpOdw+hmv9dkZOlHXII5v5Hw+meJBIWDcPrG9q/dXxGcNbym5t77fzmawrBQfTmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "hookified": "^1.8.2",
- "keyv": "^5.3.3"
+ "hookified": "^1.10.0",
+ "keyv": "^5.3.4"
}
},
"node_modules/cacheable/node_modules/keyv": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz",
- "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==",
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.4.tgz",
+ "integrity": "sha512-ypEvQvInNpUe+u+w8BIcPkQvEqXquyyibWE/1NB5T2BTzIpS5cGEV1LZskDzPSTvNAaT4+5FutvzlvnkxOSKlw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4767,9 +4868,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001717",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz",
- "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==",
+ "version": "1.0.30001726",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz",
+ "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==",
"funding": [
{
"type": "opencollective",
@@ -4853,9 +4954,9 @@
}
},
"node_modules/chart.js": {
- "version": "4.4.9",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
- "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
+ "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
@@ -5024,9 +5125,9 @@
}
},
"node_modules/clippie": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.6.tgz",
- "integrity": "sha512-1M3xZRNWcVwRkh3i2XcVYFjVtfC6FCLmIpk5s54uT3+jkBa25KRE3dB0Fgkt/YLoeR7AABWkVTlb0WziQYGgaw==",
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.7.tgz",
+ "integrity": "sha512-l8BmUmWqOt4mpxeSflcfODJJOU+DsE5atWj82k1zsxd2X82haz2+g12PJPMOt6e1Cs4/ZnOWdRAV+nY9n2o1Rg==",
"license": "BSD-2-Clause"
},
"node_modules/cliui": {
@@ -5169,14 +5270,25 @@
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"license": "MIT"
},
+ "node_modules/core-js": {
+ "version": "3.32.2",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.2.tgz",
+ "integrity": "sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
"node_modules/core-js-compat": {
- "version": "3.42.0",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz",
- "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==",
+ "version": "3.43.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz",
+ "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "browserslist": "^4.24.4"
+ "browserslist": "^4.25.0"
},
"funding": {
"type": "opencollective",
@@ -5296,9 +5408,9 @@
}
},
"node_modules/css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -5327,9 +5439,9 @@
}
},
"node_modules/css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -5929,9 +6041,9 @@
"license": "MIT"
},
"node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -5946,9 +6058,9 @@
}
},
"node_modules/decode-named-character-reference": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
- "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
+ "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6105,9 +6217,9 @@
}
},
"node_modules/dom-input-range": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/dom-input-range/-/dom-input-range-1.2.0.tgz",
- "integrity": "sha512-8HVA5Oy5Vt872S7IXsjjp6/5Hqsm5YZLhurxwwQXp80T9qVsj8/mEUH3sQlFujLLUoWfxiaThHHuJ3/q1MHVuA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/dom-input-range/-/dom-input-range-2.0.1.tgz",
+ "integrity": "sha512-UMGnuQlEepIPCju5NrPe+8y52B+pRvSjIaSPW92KpukoDGSEVkI2iaCR4HxK7K6C7zmVsWE8PEjCYZaJMr3vpg==",
"license": "MIT",
"workspaces": [
"demos"
@@ -6158,9 +6270,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz",
- "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
+ "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -6211,9 +6323,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.150",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.150.tgz",
- "integrity": "sha512-rOOkP2ZUMx1yL4fCxXQKDHQ8ZXwisb2OycOQVKHgvB3ZI4CvehOd4y2tfnnLDieJ3Zs1RL1Dlp3cMkyIn7nnXA==",
+ "version": "1.5.179",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz",
+ "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -6232,9 +6344,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.18.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
- "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
+ "version": "5.18.2",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
+ "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -6307,9 +6419,9 @@
"license": "MIT"
},
"node_modules/esbuild": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
- "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
+ "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -6319,31 +6431,31 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.4",
- "@esbuild/android-arm": "0.25.4",
- "@esbuild/android-arm64": "0.25.4",
- "@esbuild/android-x64": "0.25.4",
- "@esbuild/darwin-arm64": "0.25.4",
- "@esbuild/darwin-x64": "0.25.4",
- "@esbuild/freebsd-arm64": "0.25.4",
- "@esbuild/freebsd-x64": "0.25.4",
- "@esbuild/linux-arm": "0.25.4",
- "@esbuild/linux-arm64": "0.25.4",
- "@esbuild/linux-ia32": "0.25.4",
- "@esbuild/linux-loong64": "0.25.4",
- "@esbuild/linux-mips64el": "0.25.4",
- "@esbuild/linux-ppc64": "0.25.4",
- "@esbuild/linux-riscv64": "0.25.4",
- "@esbuild/linux-s390x": "0.25.4",
- "@esbuild/linux-x64": "0.25.4",
- "@esbuild/netbsd-arm64": "0.25.4",
- "@esbuild/netbsd-x64": "0.25.4",
- "@esbuild/openbsd-arm64": "0.25.4",
- "@esbuild/openbsd-x64": "0.25.4",
- "@esbuild/sunos-x64": "0.25.4",
- "@esbuild/win32-arm64": "0.25.4",
- "@esbuild/win32-ia32": "0.25.4",
- "@esbuild/win32-x64": "0.25.4"
+ "@esbuild/aix-ppc64": "0.25.5",
+ "@esbuild/android-arm": "0.25.5",
+ "@esbuild/android-arm64": "0.25.5",
+ "@esbuild/android-x64": "0.25.5",
+ "@esbuild/darwin-arm64": "0.25.5",
+ "@esbuild/darwin-x64": "0.25.5",
+ "@esbuild/freebsd-arm64": "0.25.5",
+ "@esbuild/freebsd-x64": "0.25.5",
+ "@esbuild/linux-arm": "0.25.5",
+ "@esbuild/linux-arm64": "0.25.5",
+ "@esbuild/linux-ia32": "0.25.5",
+ "@esbuild/linux-loong64": "0.25.5",
+ "@esbuild/linux-mips64el": "0.25.5",
+ "@esbuild/linux-ppc64": "0.25.5",
+ "@esbuild/linux-riscv64": "0.25.5",
+ "@esbuild/linux-s390x": "0.25.5",
+ "@esbuild/linux-x64": "0.25.5",
+ "@esbuild/netbsd-arm64": "0.25.5",
+ "@esbuild/netbsd-x64": "0.25.5",
+ "@esbuild/openbsd-arm64": "0.25.5",
+ "@esbuild/openbsd-x64": "0.25.5",
+ "@esbuild/sunos-x64": "0.25.5",
+ "@esbuild/win32-arm64": "0.25.5",
+ "@esbuild/win32-ia32": "0.25.5",
+ "@esbuild/win32-x64": "0.25.5"
}
},
"node_modules/esbuild-loader": {
@@ -6373,18 +6485,6 @@
"node": ">=6"
}
},
- "node_modules/escape-goat": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz",
- "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -6472,18 +6572,46 @@
}
},
"node_modules/eslint-config-prettier": {
- "version": "10.1.3",
- "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.3.tgz",
- "integrity": "sha512-vDo4d9yQE+cS2tdIT4J02H/16veRvkHgiLDRpej+WL67oCfbOb97itZXn8wMPJ/GsiEBVjrjs//AVNw2Cp1EcA==",
+ "version": "10.1.5",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
+ "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
+ "node_modules/eslint-import-context": {
+ "version": "0.1.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz",
+ "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-tsconfig": "^4.10.1",
+ "stable-hash-x": "^0.2.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-import-context"
+ },
+ "peerDependencies": {
+ "unrs-resolver": "^1.0.0"
+ },
+ "peerDependenciesMeta": {
+ "unrs-resolver": {
+ "optional": true
+ }
+ }
+ },
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
@@ -6507,18 +6635,19 @@
}
},
"node_modules/eslint-import-resolver-typescript": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.4.tgz",
- "integrity": "sha512-buzw5z5VtiQMysYLH9iW9BV04YyZebsw+gPi+c4FCjfS9i6COYOrEWw9t3m3wA9PFBfqcBCqWf32qrXLbwafDw==",
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz",
+ "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==",
"dev": true,
"license": "ISC",
"dependencies": {
- "debug": "^4.4.0",
- "get-tsconfig": "^4.10.0",
+ "debug": "^4.4.1",
+ "eslint-import-context": "^0.1.8",
+ "get-tsconfig": "^4.10.1",
"is-bun-module": "^2.0.0",
- "stable-hash": "^0.0.5",
- "tinyglobby": "^0.2.13",
- "unrs-resolver": "^1.6.3"
+ "stable-hash-x": "^0.2.0",
+ "tinyglobby": "^0.2.14",
+ "unrs-resolver": "^1.7.11"
},
"engines": {
"node": "^16.17.0 || >=18.6.0"
@@ -6541,9 +6670,9 @@
}
},
"node_modules/eslint-module-utils": {
- "version": "2.12.0",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
- "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
+ "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6683,30 +6812,30 @@
}
},
"node_modules/eslint-plugin-import": {
- "version": "2.31.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
- "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
+ "version": "2.32.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
+ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rtsao/scc": "^1.1.0",
- "array-includes": "^3.1.8",
- "array.prototype.findlastindex": "^1.2.5",
- "array.prototype.flat": "^1.3.2",
- "array.prototype.flatmap": "^1.3.2",
+ "array-includes": "^3.1.9",
+ "array.prototype.findlastindex": "^1.2.6",
+ "array.prototype.flat": "^1.3.3",
+ "array.prototype.flatmap": "^1.3.3",
"debug": "^3.2.7",
"doctrine": "^2.1.0",
"eslint-import-resolver-node": "^0.3.9",
- "eslint-module-utils": "^2.12.0",
+ "eslint-module-utils": "^2.12.1",
"hasown": "^2.0.2",
- "is-core-module": "^2.15.1",
+ "is-core-module": "^2.16.1",
"is-glob": "^4.0.3",
"minimatch": "^3.1.2",
"object.fromentries": "^2.0.8",
"object.groupby": "^1.0.3",
- "object.values": "^1.2.0",
+ "object.values": "^1.2.1",
"semver": "^6.3.1",
- "string.prototype.trimend": "^1.0.8",
+ "string.prototype.trimend": "^1.0.9",
"tsconfig-paths": "^3.15.0"
},
"engines": {
@@ -6717,23 +6846,21 @@
}
},
"node_modules/eslint-plugin-import-x": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.11.0.tgz",
- "integrity": "sha512-NAaYY49342gj09QGvwnFFl5KcD5aLzjAz97Lo+upnN8MzjEGSIlmL5sxCYGqtIeMjw8fSRDFZIp2xjRLT+yl4Q==",
+ "version": "4.16.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz",
+ "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/utils": "^8.31.0",
+ "@typescript-eslint/types": "^8.35.0",
"comment-parser": "^1.4.1",
- "debug": "^4.4.0",
- "eslint-import-resolver-node": "^0.3.9",
- "get-tsconfig": "^4.10.0",
+ "debug": "^4.4.1",
+ "eslint-import-context": "^0.1.9",
"is-glob": "^4.0.3",
"minimatch": "^9.0.3 || ^10.0.1",
- "semver": "^7.7.1",
- "stable-hash": "^0.0.5",
- "tslib": "^2.8.1",
- "unrs-resolver": "^1.7.0"
+ "semver": "^7.7.2",
+ "stable-hash-x": "^0.2.0",
+ "unrs-resolver": "^1.9.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6742,18 +6869,17 @@
"url": "https://opencollective.com/eslint-plugin-import-x"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "@typescript-eslint/utils": "^8.0.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "eslint-import-resolver-node": "*"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/utils": {
+ "optional": true
+ },
+ "eslint-import-resolver-node": {
+ "optional": true
+ }
}
},
"node_modules/eslint-plugin-import/node_modules/debug": {
@@ -6832,17 +6958,6 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
}
},
- "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -6912,14 +7027,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz",
- "integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==",
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
+ "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.11.0"
+ "synckit": "^0.11.7"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -6943,9 +7058,9 @@
}
},
"node_modules/eslint-plugin-regexp": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.7.0.tgz",
- "integrity": "sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==",
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.9.0.tgz",
+ "integrity": "sha512-9WqJMnOq8VlE/cK+YAo9C9YHhkOtcEtEk9d12a+H7OSZFwlpI6stiHmYPGa2VE0QhTzodJyhlyprUaXDZLgHBw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6965,9 +7080,9 @@
}
},
"node_modules/eslint-plugin-sonarjs": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.2.tgz",
- "integrity": "sha512-LxjbfwI7ypENeTmGyKmDyNux3COSkMi7H/6Cal5StSLQ6edf0naP45SZR43OclaNR7WfhVTZdhOn63q3/Y6puQ==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.4.tgz",
+ "integrity": "sha512-ftQcP811kRJNXapqpQXHErEoVOdTPfYPPYd7n3AExIPwv4qWKKHf4slFvXmodiOnfgy1Tl3waPZZLD7lcvJOtw==",
"dev": true,
"license": "LGPL-3.0-only",
"dependencies": {
@@ -6976,15 +7091,33 @@
"bytes": "3.1.2",
"functional-red-black-tree": "1.0.1",
"jsx-ast-utils": "3.3.5",
+ "lodash.merge": "4.6.2",
"minimatch": "9.0.5",
"scslre": "0.3.0",
- "semver": "7.7.1",
- "typescript": "^5"
+ "semver": "7.7.2",
+ "typescript": ">=5"
},
"peerDependencies": {
"eslint": "^8.0.0 || ^9.0.0"
}
},
+ "node_modules/eslint-plugin-sonarjs/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
"node_modules/eslint-plugin-sonarjs/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -7049,9 +7182,9 @@
}
},
"node_modules/eslint-plugin-vue": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.1.0.tgz",
- "integrity": "sha512-/VTiJ1eSfNLw6lvG9ENySbGmcVvz6wZ9nA7ZqXlLBY2RkaF15iViYKxglWiIch12KiLAj0j1iXPYU6W4wTROFA==",
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.3.0.tgz",
+ "integrity": "sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7066,24 +7199,30 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
+ "@typescript-eslint/parser": "^7.0.0 || ^8.0.0",
"eslint": "^8.57.0 || ^9.0.0",
"vue-eslint-parser": "^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/parser": {
+ "optional": true
+ }
}
},
"node_modules/eslint-plugin-vue-scoped-css": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.9.0.tgz",
- "integrity": "sha512-zXeKtEUpfk3PlsgKnr9/2U8K2xcsCV1M9hXWRhKbl3wipVowGXfHrhqUzHFVWNAHzEQv0DCDXGFWrmsGFqhGGA==",
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue-scoped-css/-/eslint-plugin-vue-scoped-css-2.11.0.tgz",
+ "integrity": "sha512-rrJgLY8iroTIUMSyxhyhJzFcRxABbk3gFrOLkl41F9G1VBqNNpDShyf6PmDoBEWDk07/bJlnqYlvnQ3giUrRYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
- "eslint-compat-utils": "^0.6.0",
+ "eslint-compat-utils": "^0.6.5",
"lodash": "^4.17.21",
"postcss": "^8.4.31",
"postcss-safe-parser": "^6.0.0",
"postcss-scss": "^4.0.3",
- "postcss-selector-parser": "^6.0.9",
+ "postcss-selector-parser": "^7.0.0",
"postcss-styl": "^0.12.0"
},
"engines": {
@@ -7097,6 +7236,20 @@
"vue-eslint-parser": ">=7.1.0"
}
},
+ "node_modules/eslint-plugin-vue-scoped-css/node_modules/postcss-selector-parser": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
+ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/eslint-plugin-wc": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-3.0.1.tgz",
@@ -7139,9 +7292,9 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -7168,17 +7321,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/eslint/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
@@ -7231,15 +7373,15 @@
}
},
"node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.14.0",
+ "acorn": "^8.15.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
+ "eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -7335,9 +7477,9 @@
}
},
"node_modules/exsolve": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz",
- "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
+ "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
@@ -7465,6 +7607,12 @@
}
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -7640,9 +7788,9 @@
}
},
"node_modules/get-tsconfig": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
- "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
+ "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
@@ -7690,16 +7838,6 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"license": "BSD-2-Clause"
},
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -7846,19 +7984,37 @@
}
},
"node_modules/happy-dom": {
- "version": "17.4.6",
- "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.4.6.tgz",
- "integrity": "sha512-OEV1hDe9i2rFr66+WZNiwy1S8rAJy6bRXmXql68YJDjdfHBRbN76om+qVh68vQACf6y5Bcr90e/oK53RQxsDdg==",
+ "version": "18.0.1",
+ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz",
+ "integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "webidl-conversions": "^7.0.0",
+ "@types/node": "^20.0.0",
+ "@types/whatwg-mimetype": "^3.0.2",
"whatwg-mimetype": "^3.0.0"
},
"engines": {
- "node": ">=18.0.0"
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/happy-dom/node_modules/@types/node": {
+ "version": "20.19.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.4.tgz",
+ "integrity": "sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
}
},
+ "node_modules/happy-dom/node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -7896,9 +8052,9 @@
}
},
"node_modules/hookified": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.9.0.tgz",
- "integrity": "sha512-2yEEGqphImtKIe1NXWEhu6yD3hlFR4Mxk4Mtp3XEyScpSt4pQ4ymmXA1zzxZpj99QkFK+nN0nzjeb2+RUi/6CQ==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.10.0.tgz",
+ "integrity": "sha512-dJw0492Iddsj56U1JsSTm9E/0B/29a1AuoSLRAte8vQg/kaTGF3IgjEWT8c8yG4cC10+HisE1x5QAwR0Xwc+DA==",
"dev": true,
"license": "MIT"
},
@@ -7953,9 +8109,9 @@
}
},
"node_modules/htmx.org": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.4.tgz",
- "integrity": "sha512-HLxMCdfXDOJirs3vBZl/ZLoY+c7PfM4Ahr2Ad4YXh6d22T5ltbTXFFkpx9Tgb2vvmWFMbIc3LqN2ToNkZJvyYQ==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.6.tgz",
+ "integrity": "sha512-7ythjYneGSk3yCHgtCnQeaoF+D+o7U2LF37WU3O0JYv3gTZSicdEFiI/Ai/NJyC5ZpYJWMpUb11OC5Lr6AfAqA==",
"license": "0BSD"
},
"node_modules/iconv-lite": {
@@ -8383,18 +8539,19 @@
}
},
"node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
+ "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
+ "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
+ "engines": {
+ "node": "20 || >=22"
+ },
"funding": {
"url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jest-worker": {
@@ -8660,9 +8817,9 @@
}
},
"node_modules/known-css-properties": {
- "version": "0.36.0",
- "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz",
- "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==",
+ "version": "0.37.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
+ "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
"dev": true,
"license": "MIT"
},
@@ -8758,16 +8915,6 @@
"webpack": "^4.4.0 || ^5.4.0"
}
},
- "node_modules/license-checker-webpack-plugin/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/license-checker-webpack-plugin/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -8983,9 +9130,9 @@
"license": "MIT"
},
"node_modules/loupe": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
- "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz",
+ "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==",
"dev": true,
"license": "MIT"
},
@@ -9000,10 +9147,14 @@
}
},
"node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "license": "ISC"
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
+ "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
},
"node_modules/magic-string": {
"version": "0.25.9",
@@ -9041,52 +9192,52 @@
}
},
"node_modules/markdownlint": {
- "version": "0.37.4",
- "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.37.4.tgz",
- "integrity": "sha512-u00joA/syf3VhWh6/ybVFkib5Zpj2e5KB/cfCei8fkSRuums6nyisTWGqjTWIOFoFwuXoTBQQiqlB4qFKp8ncQ==",
+ "version": "0.38.0",
+ "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz",
+ "integrity": "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "markdown-it": "14.1.0",
- "micromark": "4.0.1",
- "micromark-core-commonmark": "2.0.2",
- "micromark-extension-directive": "3.0.2",
+ "micromark": "4.0.2",
+ "micromark-core-commonmark": "2.0.3",
+ "micromark-extension-directive": "4.0.0",
"micromark-extension-gfm-autolink-literal": "2.1.0",
"micromark-extension-gfm-footnote": "2.1.0",
- "micromark-extension-gfm-table": "2.1.0",
+ "micromark-extension-gfm-table": "2.1.1",
"micromark-extension-math": "3.1.0",
- "micromark-util-types": "2.0.1"
+ "micromark-util-types": "2.0.2"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/DavidAnson"
}
},
"node_modules/markdownlint-cli": {
- "version": "0.44.0",
- "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.44.0.tgz",
- "integrity": "sha512-ZJTAONlvF9NkrIBltCdW15DxN9UTbPiKMEqAh2EU2gwIFlrCMavyCEPPO121cqfYOrLUJWW8/XKWongstmmTeQ==",
+ "version": "0.45.0",
+ "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.45.0.tgz",
+ "integrity": "sha512-GiWr7GfJLVfcopL3t3pLumXCYs8sgWppjIA1F/Cc3zIMgD3tmkpyZ1xkm1Tej8mw53B93JsDjgA3KOftuYcfOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "~13.1.0",
- "glob": "~10.4.5",
- "ignore": "~7.0.3",
+ "glob": "~11.0.2",
+ "ignore": "~7.0.4",
"js-yaml": "~4.1.0",
"jsonc-parser": "~3.3.1",
"jsonpointer": "~5.0.1",
- "markdownlint": "~0.37.4",
- "minimatch": "~9.0.5",
+ "markdown-it": "~14.1.0",
+ "markdownlint": "~0.38.0",
+ "minimatch": "~10.0.1",
"run-con": "~1.3.2",
- "smol-toml": "~1.3.1"
+ "smol-toml": "~1.3.4"
},
"bin": {
"markdownlint": "markdownlint.js"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/markdownlint-cli/node_modules/commander": {
@@ -9100,30 +9251,33 @@
}
},
"node_modules/markdownlint-cli/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
+ "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"dev": true,
"license": "ISC",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
+ "foreground-child": "^3.3.1",
+ "jackspeak": "^4.1.1",
+ "minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
+ "engines": {
+ "node": "20 || >=22"
+ },
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/markdownlint-cli/node_modules/ignore": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
- "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9137,22 +9291,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/markdownlint-cli/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
@@ -9166,9 +9304,9 @@
}
},
"node_modules/material-icon-theme": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.22.0.tgz",
- "integrity": "sha512-hJyUSrb1ak8pJHcgw83ItXyLcj2dRH5lb3H1QWekkqTEQ4xPqUBSHWnBLioT5/sf5GFrut83p9FenC0KoYnjZw==",
+ "version": "5.24.0",
+ "resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.24.0.tgz",
+ "integrity": "sha512-fKCtRSOZwSMipgfsisDtlERXndg7hu4Ykhw8Vr+92NFVhYAi3izRHyKg6YooTw6yIB8fMF6Gq8IeNhbRxRY2SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9238,14 +9376,14 @@
}
},
"node_modules/mermaid": {
- "version": "11.6.0",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz",
- "integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==",
+ "version": "11.8.0",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.8.0.tgz",
+ "integrity": "sha512-uAZUwnBiqREZcUrFw3G5iQ5Pj3hTYUP95EZc3ec/nGBzHddJZydzYGE09tGZDBS1VoSoDn0symZ85FmypSTo5g==",
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.0.4",
"@iconify/utils": "^2.1.33",
- "@mermaid-js/parser": "^0.4.0",
+ "@mermaid-js/parser": "^0.6.0",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
@@ -9254,7 +9392,7 @@
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.13",
- "dompurify": "^3.2.4",
+ "dompurify": "^3.2.5",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
@@ -9266,9 +9404,9 @@
}
},
"node_modules/mermaid/node_modules/marked": {
- "version": "15.0.11",
- "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.11.tgz",
- "integrity": "sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==",
+ "version": "15.0.12",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
+ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
@@ -9278,9 +9416,9 @@
}
},
"node_modules/micromark": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz",
- "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
"dev": true,
"funding": [
{
@@ -9314,9 +9452,9 @@
}
},
"node_modules/micromark-core-commonmark": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz",
- "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
"dev": true,
"funding": [
{
@@ -9349,9 +9487,9 @@
}
},
"node_modules/micromark-extension-directive": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz",
- "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz",
+ "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9407,9 +9545,9 @@
}
},
"node_modules/micromark-extension-gfm-table": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
- "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9797,9 +9935,9 @@
"license": "MIT"
},
"node_modules/micromark-util-types": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz",
- "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
"dev": true,
"funding": [
{
@@ -9878,12 +10016,12 @@
}
},
"node_modules/minimatch": {
- "version": "10.0.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
- "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
+ "version": "10.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
+ "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
@@ -10007,10 +10145,16 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/nanopop": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.3.0.tgz",
+ "integrity": "sha512-fzN+T2K7/Ah25XU02MJkPZ5q4Tj5FpjmIYq4rvoHX4yb16HzFdCO6JxFFn5Y/oBhQ8no8fUZavnyIv9/+xkBBw==",
+ "license": "MIT"
+ },
"node_modules/napi-postinstall": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.3.tgz",
- "integrity": "sha512-Mi7JISo/4Ij2tDZ2xBE2WH+/KvVlkhA6juEjpEeRAVPNCpN3nxJo/5FhDNKgBcdmcmhaH6JjgST4xY/23ZYK0w==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.0.tgz",
+ "integrity": "sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==",
"dev": true,
"license": "MIT",
"bin": {
@@ -10247,6 +10391,17 @@
"wrappy": "1"
}
},
+ "node_modules/online-3d-viewer": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/online-3d-viewer/-/online-3d-viewer-0.16.0.tgz",
+ "integrity": "sha512-Mcmo41TM3K+svlMDRH8ySKSY2e8s7Sssdb5U9LV3gkFKVWGGuS304Vk5gqxopAJbE72DpsC67Ve3YNtcAuROwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@simonwep/pickr": "1.9.0",
+ "fflate": "0.8.2",
+ "three": "0.176.0"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -10415,16 +10570,17 @@
"license": "MIT"
},
"node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
+ "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
+ "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
},
"engines": {
- "node": ">=16 || 14 >=14.18"
+ "node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -10447,9 +10603,9 @@
"license": "MIT"
},
"node_modules/pathval": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
- "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -10569,24 +10725,24 @@
}
},
"node_modules/pkg-types": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz",
- "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz",
+ "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==",
"license": "MIT",
"dependencies": {
- "confbox": "^0.2.1",
- "exsolve": "^1.0.1",
+ "confbox": "^0.2.2",
+ "exsolve": "^1.0.7",
"pathe": "^2.0.3"
}
},
"node_modules/playwright": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
- "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz",
+ "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.52.0"
+ "playwright-core": "1.53.2"
},
"bin": {
"playwright": "cli.js"
@@ -10599,9 +10755,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
- "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
+ "version": "1.53.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz",
+ "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -10648,9 +10804,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.3",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
- "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@@ -10667,7 +10823,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.8",
+ "nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -10869,9 +11025,9 @@
}
},
"node_modules/postcss-nesting": {
- "version": "13.0.1",
- "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz",
- "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==",
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz",
+ "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==",
"funding": [
{
"type": "github",
@@ -10884,7 +11040,7 @@
],
"license": "MIT-0",
"dependencies": {
- "@csstools/selector-resolve-nested": "^3.0.0",
+ "@csstools/selector-resolve-nested": "^3.1.0",
"@csstools/selector-specificity": "^5.0.0",
"postcss-selector-parser": "^7.0.0"
},
@@ -10896,9 +11052,9 @@
}
},
"node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz",
- "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz",
+ "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==",
"funding": [
{
"type": "github",
@@ -11053,9 +11209,9 @@
}
},
"node_modules/prettier": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
- "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -11648,9 +11804,9 @@
}
},
"node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -11669,18 +11825,18 @@
}
},
"node_modules/seroval": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.0.tgz",
- "integrity": "sha512-4tYQDy3HVM0JjJ1CfDK3K8FhBKIDDri27oc2AyabuuHfQw6/yTDPp2Abt1h2cNtf1R0T+7AQYAzPhUgqXztaXw==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
+ "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/seroval-plugins": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.0.tgz",
- "integrity": "sha512-FFu/UE3uA8L1vj0CXXZo2Nlh10MtYoOs0G//ptwlQMjfPFSeIVYUNy0zewfV8iM0CrOebAfHEG6J3xA9c+lsaQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.2.tgz",
+ "integrity": "sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==",
"license": "MIT",
"engines": {
"node": ">=10"
@@ -11796,14 +11952,14 @@
}
},
"node_modules/solid-js": {
- "version": "1.9.6",
- "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.6.tgz",
- "integrity": "sha512-PoasAJvLk60hRtOTe9ulvALOdLjjqxuxcGZRolBQqxOnXrBXHGzqMT4ijNhGsDAYdOgEa8ZYaAE94PSldrFSkA==",
+ "version": "1.9.7",
+ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.7.tgz",
+ "integrity": "sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.0",
- "seroval": "^1.1.0",
- "seroval-plugins": "^1.1.0"
+ "seroval": "~1.3.0",
+ "seroval-plugins": "~1.3.0"
}
},
"node_modules/sortablejs": {
@@ -11936,12 +12092,15 @@
"spdx-ranges": "^2.0.0"
}
},
- "node_modules/stable-hash": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
- "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
+ "node_modules/stable-hash-x": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz",
+ "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ }
},
"node_modules/stackback": {
"version": "0.0.2",
@@ -12098,6 +12257,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/style-search": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
@@ -12106,9 +12278,9 @@
"license": "ISC"
},
"node_modules/stylelint": {
- "version": "16.19.1",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.19.1.tgz",
- "integrity": "sha512-C1SlPZNMKl+d/C867ZdCRthrS+6KuZ3AoGW113RZCOL0M8xOGpgx7G70wq7lFvqvm4dcfdGFVLB/mNaLFChRKw==",
+ "version": "16.21.1",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.21.1.tgz",
+ "integrity": "sha512-WCXdXnYK2tpCbebgMF0Bme3YZH/Rh/UXerj75twYo4uLULlcrLwFVdZTvTEF8idFnAcW21YUDJFyKOfaf6xJRw==",
"dev": true,
"funding": [
{
@@ -12122,9 +12294,9 @@
],
"license": "MIT",
"dependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/media-query-list-parser": "^4.0.2",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/media-query-list-parser": "^4.0.3",
"@csstools/selector-specificity": "^5.0.0",
"@dual-bundle/import-meta-resolve": "^4.1.0",
"balanced-match": "^2.0.0",
@@ -12132,24 +12304,24 @@
"cosmiconfig": "^9.0.0",
"css-functions-list": "^3.2.3",
"css-tree": "^3.1.0",
- "debug": "^4.3.7",
+ "debug": "^4.4.1",
"fast-glob": "^3.3.3",
"fastest-levenshtein": "^1.0.16",
- "file-entry-cache": "^10.0.8",
+ "file-entry-cache": "^10.1.1",
"global-modules": "^2.0.0",
"globby": "^11.1.0",
"globjoin": "^0.1.4",
"html-tags": "^3.3.1",
- "ignore": "^7.0.3",
+ "ignore": "^7.0.5",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
- "known-css-properties": "^0.36.0",
+ "known-css-properties": "^0.37.0",
"mathml-tag-names": "^2.1.3",
"meow": "^13.2.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"picocolors": "^1.1.1",
- "postcss": "^8.5.3",
+ "postcss": "^8.5.6",
"postcss-resolve-nested-selector": "^0.1.6",
"postcss-safe-parser": "^7.0.1",
"postcss-selector-parser": "^7.1.0",
@@ -12218,9 +12390,9 @@
}
},
"node_modules/stylelint-define-config": {
- "version": "16.19.0",
- "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.19.0.tgz",
- "integrity": "sha512-J+VluCron5JwUS/sjtTwzMjpvjqvTe5Rs0SB4mjZDTonPFINEhS9/r0yIA9olfjdf9enV8H/oLMm22SF5yykig==",
+ "version": "16.21.0",
+ "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.21.0.tgz",
+ "integrity": "sha512-DNbScaWa4MvOrEUgpUacIEBI57iV4drbIl9D5GXXlYtcv1z9xrdatDagewTq9uwXADodkwc3QJcraVAL/pWrWA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12253,9 +12425,9 @@
}
},
"node_modules/stylelint/node_modules/@csstools/media-query-list-parser": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz",
- "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz",
+ "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==",
"dev": true,
"funding": [
{
@@ -12272,8 +12444,8 @@
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/stylelint/node_modules/@csstools/selector-specificity": {
@@ -12299,39 +12471,32 @@
"postcss-selector-parser": "^7.0.0"
}
},
- "node_modules/stylelint/node_modules/balanced-match": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
- "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/stylelint/node_modules/file-entry-cache": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.0.tgz",
- "integrity": "sha512-Et/ex6smi3wOOB+n5mek+Grf7P2AxZR5ueqRUvAAn4qkyatXi3cUC1cuQXVkX0VlzBVsN4BkWJFmY/fYiRTdww==",
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.1.tgz",
+ "integrity": "sha512-zcmsHjg2B2zjuBgjdnB+9q0+cWcgWfykIcsDkWDB4GTPtl1eXUA+gTI6sO0u01AqK3cliHryTU55/b2Ow1hfZg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flat-cache": "^6.1.9"
+ "flat-cache": "^6.1.10"
}
},
"node_modules/stylelint/node_modules/flat-cache": {
- "version": "6.1.9",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.9.tgz",
- "integrity": "sha512-DUqiKkTlAfhtl7g78IuwqYM+YqvT+as0mY+EVk6mfimy19U79pJCzDZQsnqk3Ou/T6hFXWLGbwbADzD/c8Tydg==",
+ "version": "6.1.11",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.11.tgz",
+ "integrity": "sha512-zfOAns94mp7bHG/vCn9Ru2eDCmIxVQ5dELUHKjHfDEOJmHNzE+uGa6208kfkgmtym4a0FFjEuFksCXFacbVhSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cacheable": "^1.9.0",
+ "cacheable": "^1.10.1",
"flatted": "^3.3.3",
- "hookified": "^1.8.2"
+ "hookified": "^1.10.0"
}
},
"node_modules/stylelint/node_modules/ignore": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
- "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -12448,6 +12613,21 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/sucrase/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/sucrase/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
"node_modules/sucrase/node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -12477,6 +12657,27 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/sucrase/node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/sucrase/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
"node_modules/sucrase/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -12492,6 +12693,22 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/sucrase/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/superstruct": {
"version": "0.10.13",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.10.13.tgz",
@@ -12557,25 +12774,25 @@
"dev": true
},
"node_modules/svgo": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
- "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
+ "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@trysound/sax": "0.2.0",
- "commander": "^7.2.0",
+ "commander": "^11.1.0",
"css-select": "^5.1.0",
- "css-tree": "^2.3.1",
+ "css-tree": "^3.0.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1",
+ "sax": "^1.4.1"
},
"bin": {
- "svgo": "bin/svgo"
+ "svgo": "bin/svgo.js"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=16"
},
"funding": {
"type": "opencollective",
@@ -12583,35 +12800,21 @@
}
},
"node_modules/svgo/node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/svgo/node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "mdn-data": "2.0.30",
- "source-map-js": "^1.0.1"
- },
"engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ "node": ">=16"
}
},
- "node_modules/svgo/node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "node_modules/svgo/node_modules/sax": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"dev": true,
- "license": "CC0-1.0"
+ "license": "ISC"
},
"node_modules/svgson": {
"version": "5.3.1",
@@ -12625,9 +12828,9 @@
}
},
"node_modules/swagger-ui-dist": {
- "version": "5.21.0",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.21.0.tgz",
- "integrity": "sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg==",
+ "version": "5.26.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.26.0.tgz",
+ "integrity": "sha512-U8m1LruHrk33gIIT5qDKhXMygT4FonRGBE92zMbxP4i9ULolPlKISy5Pd3RCES8pWdbGzXhvm/Q6jdA/HsrClg==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -12647,14 +12850,13 @@
}
},
"node_modules/synckit": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz",
- "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==",
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
+ "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@pkgr/core": "^0.2.3",
- "tslib": "^2.8.1"
+ "@pkgr/core": "^0.2.4"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -12753,22 +12955,22 @@
}
},
"node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
+ "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/terser": {
- "version": "5.39.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
- "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
+ "version": "5.43.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
+ "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
+ "acorn": "^8.14.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
@@ -12847,6 +13049,12 @@
"node": ">=0.8"
}
},
+ "node_modules/three": {
+ "version": "0.176.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.176.0.tgz",
+ "integrity": "sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==",
+ "license": "MIT"
+ },
"node_modules/throttle-debounce": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
@@ -12876,9 +13084,9 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
- "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12893,9 +13101,9 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.4",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
- "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -12921,9 +13129,9 @@
}
},
"node_modules/tinypool": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
- "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -12941,9 +13149,9 @@
}
},
"node_modules/tinyspy": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
- "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
+ "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13109,15 +13317,15 @@
"license": "MIT"
},
"node_modules/uint8-to-base64": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.0.tgz",
- "integrity": "sha512-r13jrghEYZAN99GeYpEjM107DOxqB65enskpwce8rRHVAGEtaWmsF5GqoGdPMf8DIXc9XyAJTdvlvRZi4LsszA==",
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.1.tgz",
+ "integrity": "sha512-uO/84GaoDUfiAxpa8EksjVLE77A9Kc7ZTziN4zRpq4de9yLaLcZn3jx1/sVjyupsywcVX6RKWbqLe7gUNyzH+Q==",
"license": "ISC"
},
"node_modules/undici-types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"license": "MIT"
},
"node_modules/universalify": {
@@ -13131,36 +13339,38 @@
}
},
"node_modules/unrs-resolver": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz",
- "integrity": "sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==",
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.10.1.tgz",
+ "integrity": "sha512-EFrL7Hw4kmhZdwWO3dwwFJo6hO3FXuQ6Bg8BK/faHZ9m1YxqBS31BNSTxklIQkxK/4LlV8zTYnPsIRLBzTzjCA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
- "napi-postinstall": "^0.2.2"
+ "napi-postinstall": "^0.3.0"
},
"funding": {
- "url": "https://github.com/sponsors/JounQin"
+ "url": "https://opencollective.com/unrs-resolver"
},
"optionalDependencies": {
- "@unrs/resolver-binding-darwin-arm64": "1.7.2",
- "@unrs/resolver-binding-darwin-x64": "1.7.2",
- "@unrs/resolver-binding-freebsd-x64": "1.7.2",
- "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.2",
- "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.2",
- "@unrs/resolver-binding-linux-arm64-gnu": "1.7.2",
- "@unrs/resolver-binding-linux-arm64-musl": "1.7.2",
- "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.2",
- "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.2",
- "@unrs/resolver-binding-linux-riscv64-musl": "1.7.2",
- "@unrs/resolver-binding-linux-s390x-gnu": "1.7.2",
- "@unrs/resolver-binding-linux-x64-gnu": "1.7.2",
- "@unrs/resolver-binding-linux-x64-musl": "1.7.2",
- "@unrs/resolver-binding-wasm32-wasi": "1.7.2",
- "@unrs/resolver-binding-win32-arm64-msvc": "1.7.2",
- "@unrs/resolver-binding-win32-ia32-msvc": "1.7.2",
- "@unrs/resolver-binding-win32-x64-msvc": "1.7.2"
+ "@unrs/resolver-binding-android-arm-eabi": "1.10.1",
+ "@unrs/resolver-binding-android-arm64": "1.10.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.10.1",
+ "@unrs/resolver-binding-darwin-x64": "1.10.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.10.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.10.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.10.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.10.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.10.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.10.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.10.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.10.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.10.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.10.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.10.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.10.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.10.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.10.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.10.1"
}
},
"node_modules/update-browserslist-db": {
@@ -13280,24 +13490,24 @@
"license": "MIT"
},
"node_modules/vite": {
- "version": "6.3.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
- "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz",
+ "integrity": "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
- "fdir": "^6.4.4",
+ "fdir": "^6.4.6",
"picomatch": "^4.0.2",
- "postcss": "^8.5.3",
- "rollup": "^4.34.9",
- "tinyglobby": "^0.2.13"
+ "postcss": "^8.5.6",
+ "rollup": "^4.40.0",
+ "tinyglobby": "^0.2.14"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -13306,14 +13516,14 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
- "less": "*",
+ "less": "^4.0.0",
"lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
@@ -13355,17 +13565,17 @@
}
},
"node_modules/vite-node": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz",
- "integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cac": "^6.7.14",
- "debug": "^4.4.0",
+ "debug": "^4.4.1",
"es-module-lexer": "^1.7.0",
"pathe": "^2.0.3",
- "vite": "^5.0.0 || ^6.0.0"
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
},
"bin": {
"vite-node": "vite-node.mjs"
@@ -13385,16 +13595,16 @@
"license": "BSD-2-Clause"
},
"node_modules/vite/node_modules/@types/estree": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
"node_modules/vite/node_modules/fdir": {
- "version": "6.4.4",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
- "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -13435,13 +13645,13 @@
}
},
"node_modules/vite/node_modules/rollup": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz",
- "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==",
+ "version": "4.44.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.2.tgz",
+ "integrity": "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.7"
+ "@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -13451,56 +13661,58 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.40.2",
- "@rollup/rollup-android-arm64": "4.40.2",
- "@rollup/rollup-darwin-arm64": "4.40.2",
- "@rollup/rollup-darwin-x64": "4.40.2",
- "@rollup/rollup-freebsd-arm64": "4.40.2",
- "@rollup/rollup-freebsd-x64": "4.40.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.40.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.40.2",
- "@rollup/rollup-linux-arm64-gnu": "4.40.2",
- "@rollup/rollup-linux-arm64-musl": "4.40.2",
- "@rollup/rollup-linux-loongarch64-gnu": "4.40.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-musl": "4.40.2",
- "@rollup/rollup-linux-s390x-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-musl": "4.40.2",
- "@rollup/rollup-win32-arm64-msvc": "4.40.2",
- "@rollup/rollup-win32-ia32-msvc": "4.40.2",
- "@rollup/rollup-win32-x64-msvc": "4.40.2",
+ "@rollup/rollup-android-arm-eabi": "4.44.2",
+ "@rollup/rollup-android-arm64": "4.44.2",
+ "@rollup/rollup-darwin-arm64": "4.44.2",
+ "@rollup/rollup-darwin-x64": "4.44.2",
+ "@rollup/rollup-freebsd-arm64": "4.44.2",
+ "@rollup/rollup-freebsd-x64": "4.44.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.44.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.44.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.44.2",
+ "@rollup/rollup-linux-arm64-musl": "4.44.2",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.44.2",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.44.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.44.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.44.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.44.2",
+ "@rollup/rollup-linux-x64-gnu": "4.44.2",
+ "@rollup/rollup-linux-x64-musl": "4.44.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.44.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.44.2",
+ "@rollup/rollup-win32-x64-msvc": "4.44.2",
"fsevents": "~2.3.2"
}
},
"node_modules/vitest": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz",
- "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "3.1.3",
- "@vitest/mocker": "3.1.3",
- "@vitest/pretty-format": "^3.1.3",
- "@vitest/runner": "3.1.3",
- "@vitest/snapshot": "3.1.3",
- "@vitest/spy": "3.1.3",
- "@vitest/utils": "3.1.3",
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
"chai": "^5.2.0",
- "debug": "^4.4.0",
+ "debug": "^4.4.1",
"expect-type": "^1.2.1",
"magic-string": "^0.30.17",
"pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
"std-env": "^3.9.0",
"tinybench": "^2.9.0",
"tinyexec": "^0.3.2",
- "tinyglobby": "^0.2.13",
- "tinypool": "^1.0.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
"tinyrainbow": "^2.0.0",
- "vite": "^5.0.0 || ^6.0.0",
- "vite-node": "3.1.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
"why-is-node-running": "^2.3.0"
},
"bin": {
@@ -13516,8 +13728,8 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "3.1.3",
- "@vitest/ui": "3.1.3",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
"happy-dom": "*",
"jsdom": "*"
},
@@ -13555,6 +13767,19 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/vitest/node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
@@ -13612,16 +13837,16 @@
"license": "MIT"
},
"node_modules/vue": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
- "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz",
+ "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.13",
- "@vue/compiler-sfc": "3.5.13",
- "@vue/runtime-dom": "3.5.13",
- "@vue/server-renderer": "3.5.13",
- "@vue/shared": "3.5.13"
+ "@vue/compiler-dom": "3.5.17",
+ "@vue/compiler-sfc": "3.5.17",
+ "@vue/runtime-dom": "3.5.17",
+ "@vue/server-renderer": "3.5.17",
+ "@vue/shared": "3.5.17"
},
"peerDependencies": {
"typescript": "*"
@@ -13652,9 +13877,9 @@
}
},
"node_modules/vue-eslint-parser": {
- "version": "10.1.3",
- "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz",
- "integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz",
+ "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -13664,7 +13889,6 @@
"eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0",
"esquery": "^1.6.0",
- "lodash": "^4.17.21",
"semver": "^7.6.3"
},
"engines": {
@@ -13678,9 +13902,9 @@
}
},
"node_modules/vue-eslint-parser/node_modules/eslint-scope": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
- "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
@@ -13718,14 +13942,14 @@
}
},
"node_modules/vue-tsc": {
- "version": "2.2.10",
- "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.10.tgz",
- "integrity": "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.0.1.tgz",
+ "integrity": "sha512-UvMLQD0hAGL1g/NfEQelnSVB4H5gtf/gz2lJKjMMwWNOUmSNyWkejwJagAxEbSjtV5CPPJYslOtoSuqJ63mhdg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/typescript": "~2.4.11",
- "@vue/language-core": "2.2.10"
+ "@volar/typescript": "2.4.17",
+ "@vue/language-core": "3.0.1"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
@@ -13735,9 +13959,9 @@
}
},
"node_modules/watchpack": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
- "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
+ "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
"license": "MIT",
"dependencies": {
"glob-to-regexp": "^0.4.1",
@@ -13748,19 +13972,15 @@
}
},
"node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=12"
- }
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
},
"node_modules/webpack": {
- "version": "5.99.8",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz",
- "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==",
+ "version": "5.99.9",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz",
+ "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==",
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.7",
@@ -13880,9 +14100,9 @@
}
},
"node_modules/webpack/node_modules/@types/estree": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
"node_modules/webpack/node_modules/eslint-scope": {
@@ -13908,9 +14128,9 @@
}
},
"node_modules/webpack/node_modules/webpack-sources": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
- "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
+ "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
@@ -13936,12 +14156,6 @@
"webidl-conversions": "^3.0.0"
}
},
- "node_modules/whatwg-url/node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "license": "BSD-2-Clause"
- },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -14149,15 +14363,15 @@
}
},
"node_modules/yaml": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
- "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
+ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
- "node": ">= 14"
+ "node": ">= 14.6"
}
},
"node_modules/yargs": {
diff --git a/package.json b/package.json
index 0202b92ff4..4b4f22351b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"type": "module",
"engines": {
- "node": ">= 18.0.0"
+ "node": ">= 20.0.0"
},
"dependencies": {
"@citation-js/core": "0.7.18",
@@ -9,43 +9,43 @@
"@citation-js/plugin-csl": "0.7.18",
"@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.3",
- "@github/relative-time-element": "4.4.7",
- "@github/text-expander-element": "2.9.1",
+ "@github/relative-time-element": "4.4.8",
+ "@github/text-expander-element": "2.9.2",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
- "@primer/octicons": "19.15.1",
+ "@primer/octicons": "19.15.3",
"@silverwind/vue3-calendar-heatmap": "2.0.6",
"add-asset-webpack-plugin": "3.0.0",
- "ansi_up": "6.0.5",
- "asciinema-player": "3.9.0",
- "chart.js": "4.4.9",
+ "ansi_up": "6.0.6",
+ "asciinema-player": "3.10.0",
+ "chart.js": "4.5.0",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
- "clippie": "4.1.6",
+ "clippie": "4.1.7",
"cropperjs": "1.6.2",
"css-loader": "7.1.2",
"dayjs": "1.11.13",
"dropzone": "6.0.0-beta.2",
"easymde": "2.20.0",
"esbuild-loader": "4.3.0",
- "escape-goat": "4.0.0",
"fast-glob": "3.3.3",
- "htmx.org": "2.0.4",
+ "htmx.org": "2.0.6",
"idiomorph": "0.7.3",
"jquery": "3.7.1",
"katex": "0.16.22",
"license-checker-webpack-plugin": "0.2.1",
- "mermaid": "11.6.0",
+ "mermaid": "11.8.0",
"mini-css-extract-plugin": "2.9.2",
- "minimatch": "10.0.1",
+ "minimatch": "10.0.3",
"monaco-editor": "0.52.2",
"monaco-editor-webpack-plugin": "7.1.0",
+ "online-3d-viewer": "0.16.0",
"pdfobject": "2.3.1",
"perfect-debounce": "1.0.0",
- "postcss": "8.5.3",
+ "postcss": "8.5.6",
"postcss-loader": "8.1.1",
- "postcss-nesting": "13.0.1",
+ "postcss-nesting": "13.0.2",
"sortablejs": "1.15.6",
- "swagger-ui-dist": "5.21.0",
+ "swagger-ui-dist": "5.26.0",
"tailwindcss": "3.4.17",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
@@ -53,67 +53,67 @@
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"typescript": "5.8.3",
- "uint8-to-base64": "0.2.0",
+ "uint8-to-base64": "0.2.1",
"vanilla-colorful": "0.7.2",
- "vue": "3.5.13",
+ "vue": "3.5.17",
"vue-bar-graph": "2.2.0",
"vue-chartjs": "5.3.2",
"vue-loader": "17.4.2",
- "webpack": "5.99.8",
+ "webpack": "5.99.9",
"webpack-cli": "6.0.1",
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
- "@playwright/test": "1.52.0",
+ "@playwright/test": "1.53.2",
"@stoplight/spectral-cli": "6.15.0",
"@stylistic/eslint-plugin-js": "3.1.0",
- "@stylistic/stylelint-plugin": "3.1.2",
+ "@stylistic/stylelint-plugin": "3.1.3",
"@types/dropzone": "5.7.9",
"@types/jquery": "3.5.32",
"@types/katex": "0.16.7",
- "@types/license-checker-webpack-plugin": "0.2.4",
+ "@types/license-checker-webpack-plugin": "0.2.5",
"@types/pdfobject": "2.2.5",
"@types/sortablejs": "1.15.8",
- "@types/swagger-ui-dist": "3.30.5",
+ "@types/swagger-ui-dist": "3.30.6",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/toastify-js": "1.12.4",
- "@typescript-eslint/eslint-plugin": "8.32.0",
- "@typescript-eslint/parser": "8.32.0",
- "@vitejs/plugin-vue": "5.2.3",
- "@vitest/eslint-plugin": "1.1.44",
+ "@typescript-eslint/eslint-plugin": "8.35.1",
+ "@typescript-eslint/parser": "8.35.1",
+ "@vitejs/plugin-vue": "6.0.0",
+ "@vitest/eslint-plugin": "1.3.4",
"eslint": "8.57.0",
- "eslint-import-resolver-typescript": "4.3.4",
+ "eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "5.0.2",
- "eslint-plugin-import-x": "4.11.0",
+ "eslint-plugin-import-x": "4.16.1",
"eslint-plugin-no-jquery": "3.1.1",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-playwright": "2.2.0",
- "eslint-plugin-regexp": "2.7.0",
- "eslint-plugin-sonarjs": "3.0.2",
+ "eslint-plugin-regexp": "2.9.0",
+ "eslint-plugin-sonarjs": "3.0.4",
"eslint-plugin-unicorn": "56.0.1",
- "eslint-plugin-vue": "10.1.0",
- "eslint-plugin-vue-scoped-css": "2.9.0",
+ "eslint-plugin-vue": "10.3.0",
+ "eslint-plugin-vue-scoped-css": "2.11.0",
"eslint-plugin-wc": "3.0.1",
- "happy-dom": "17.4.6",
- "markdownlint-cli": "0.44.0",
- "material-icon-theme": "5.22.0",
+ "happy-dom": "18.0.1",
+ "markdownlint-cli": "0.45.0",
+ "material-icon-theme": "5.24.0",
"nolyfill": "1.0.44",
"postcss-html": "1.8.0",
- "stylelint": "16.19.1",
+ "stylelint": "16.21.1",
"stylelint-config-recommended": "16.0.0",
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.11",
- "stylelint-define-config": "16.19.0",
+ "stylelint-define-config": "16.21.0",
"stylelint-value-no-unknown-custom-properties": "6.0.1",
- "svgo": "3.3.2",
+ "svgo": "4.0.0",
"type-fest": "4.41.0",
"updates": "16.4.2",
"vite-string-plugin": "1.4.4",
- "vitest": "3.1.3",
- "vue-tsc": "2.2.10"
+ "vitest": "3.2.4",
+ "vue-tsc": "3.0.1"
},
"browserslist": [
"defaults"
diff --git a/public/assets/img/svg/gitea-chef.svg b/public/assets/img/svg/gitea-chef.svg
index c5e8a721cc..8fd8ed325d 100644
--- a/public/assets/img/svg/gitea-chef.svg
+++ b/public/assets/img/svg/gitea-chef.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="svg gitea-chef" width="16" height="16" aria-hidden="true"><g fill="none" fill-rule="evenodd"><path fill="#435363" d="M18 25.8c-4.3 0-7.7-3.6-7.7-8s3.4-7.9 7.7-7.9c3.5 0 6.4 2.4 7.3 5.7h3c-1-5-5.2-8.7-10.3-8.7-5.9 0-10.6 4.9-10.6 10.9 0 6.1 4.7 11 10.6 11 5.1 0 9.3-3.7 10.3-8.7h-3c-.9 3.3-3.8 5.7-7.3 5.7"/><path fill="#435363" d="M12.8 23.2c1.3 1.4 3.1 2.3 5.2 2.3v-3.2c-1.2 0-2.3-.5-3.1-1.3z"/><path fill="#F38B00" d="M10.6 17.8c0 1.1.3 2.2.6 3.1l2.9-1.3c-.3-.5-.4-1.1-.4-1.8 0-2.4 1.9-4.4 4.3-4.4v-3.2c-4.1 0-7.4 3.4-7.4 7.6"/><path fill="#435363" d="m20.6 10.7-1.1 3c.9.4 1.7 1.1 2.2 1.9H25c-.7-2.2-2.3-4-4.4-4.9"/><path fill="#F38B00" d="m19.5 22 1.1 2.9c2.1-.8 3.7-2.6 4.4-4.8h-3.3c-.5.8-1.3 1.5-2.2 1.9"/><path fill="#435363" d="M4.4 22.1c-.1-.2-.1-.3-.1-.5-.1-.2-.1-.3-.2-.5V21c0-.1 0-.3-.1-.4v-.5c-.1-.1-.1-.2-.1-.3-.1-.6-.1-1.3-.1-2H.9c0 .8 0 1.5.1 2.2 0 .2.1.4.1.6v.1c0 .2.1.4.1.5s0 .2.1.3v.3c.1.1.1.2.1.4 0 0 .1.1.1.2 0 .2 0 .3.1.4v.2c.2.7.5 1.3.7 2L5 23.8c-.2-.6-.4-1.1-.6-1.7"/><path fill="#F38B00" d="M18 32.6c-3.9 0-7.5-1.7-10.1-4.4l-2 2.2c3.1 3.2 7.3 5.2 12.1 5.2 8.7 0 15.8-6.8 16.9-15.5H32c-1.1 7-7 12.5-14 12.5M18 3.1c3.1 0 6.1 1.1 8.4 2.9l1.8-2.4C25.3 1.4 21.8.1 18 .1 10.7.1 4.5 4.8 2.1 11.4l2.7 1.1C6.8 7 12 3.1 18 3.1"/><path fill="#435363" d="M32 15.6h2.9c-.3-2.6-1.2-5-2.5-7.2L30 10c1 1.7 1.7 3.6 2 5.6"/><path fill="#F38B00" d="M28.7 15.6h2.9c-.8-5.1-4.1-9.3-8.6-11.1l-1.1 2.8c3.5 1.3 6 4.5 6.8 8.3"/><path fill="#435363" d="M18 6.5v-3c-5.9 0-10.9 3.8-12.9 9.1l2.7 1.1C9.4 9.5 13.3 6.5 18 6.5"/><path fill="#F38B00" d="M7 17.8H4.1c0 6.1 3.6 11.2 8.7 13.4l1.1-2.8C9.9 26.7 7 22.6 7 17.8"/><path fill="#435363" d="M18 29.2v3c6.9 0 12.6-5.3 13.6-12.1h-2.9c-1 5.2-5.4 9.1-10.7 9.1"/></g></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="svg gitea-chef" width="16" height="16" aria-hidden="true"><g fill="none" fill-rule="evenodd"><path fill="#435363" d="M18 25.8c-4.3 0-7.7-3.6-7.7-8s3.4-7.9 7.7-7.9c3.5 0 6.4 2.4 7.3 5.7h3c-1-5-5.2-8.7-10.3-8.7-5.9 0-10.6 4.9-10.6 10.9 0 6.1 4.7 11 10.6 11 5.1 0 9.3-3.7 10.3-8.7h-3c-.9 3.3-3.8 5.7-7.3 5.7"/><path fill="#435363" d="M12.8 23.2c1.3 1.4 3.1 2.3 5.2 2.3v-3.2c-1.2 0-2.3-.5-3.1-1.3z"/><path fill="#f38b00" d="M10.6 17.8c0 1.1.3 2.2.6 3.1l2.9-1.3c-.3-.5-.4-1.1-.4-1.8 0-2.4 1.9-4.4 4.3-4.4v-3.2c-4.1 0-7.4 3.4-7.4 7.6"/><path fill="#435363" d="m20.6 10.7-1.1 3c.9.4 1.7 1.1 2.2 1.9H25c-.7-2.2-2.3-4-4.4-4.9"/><path fill="#f38b00" d="m19.5 22 1.1 2.9c2.1-.8 3.7-2.6 4.4-4.8h-3.3c-.5.8-1.3 1.5-2.2 1.9"/><path fill="#435363" d="M4.4 22.1c-.1-.2-.1-.3-.1-.5-.1-.2-.1-.3-.2-.5V21c0-.1 0-.3-.1-.4v-.5c-.1-.1-.1-.2-.1-.3-.1-.6-.1-1.3-.1-2H.9c0 .8 0 1.5.1 2.2 0 .2.1.4.1.6v.1c0 .2.1.4.1.5s0 .2.1.3v.3c.1.1.1.2.1.4 0 0 .1.1.1.2 0 .2 0 .3.1.4v.2c.2.7.5 1.3.7 2L5 23.8c-.2-.6-.4-1.1-.6-1.7"/><path fill="#f38b00" d="M18 32.6c-3.9 0-7.5-1.7-10.1-4.4l-2 2.2c3.1 3.2 7.3 5.2 12.1 5.2 8.7 0 15.8-6.8 16.9-15.5H32c-1.1 7-7 12.5-14 12.5M18 3.1c3.1 0 6.1 1.1 8.4 2.9l1.8-2.4C25.3 1.4 21.8.1 18 .1 10.7.1 4.5 4.8 2.1 11.4l2.7 1.1C6.8 7 12 3.1 18 3.1"/><path fill="#435363" d="M32 15.6h2.9c-.3-2.6-1.2-5-2.5-7.2L30 10c1 1.7 1.7 3.6 2 5.6"/><path fill="#f38b00" d="M28.7 15.6h2.9c-.8-5.1-4.1-9.3-8.6-11.1l-1.1 2.8c3.5 1.3 6 4.5 6.8 8.3"/><path fill="#435363" d="M18 6.5v-3c-5.9 0-10.9 3.8-12.9 9.1l2.7 1.1C9.4 9.5 13.3 6.5 18 6.5"/><path fill="#f38b00" d="M7 17.8H4.1c0 6.1 3.6 11.2 8.7 13.4l1.1-2.8C9.9 26.7 7 22.6 7 17.8"/><path fill="#435363" d="M18 29.2v3c6.9 0 12.6-5.3 13.6-12.1h-2.9c-1 5.2-5.4 9.1-10.7 9.1"/></g></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-codecommit.svg b/public/assets/img/svg/gitea-codecommit.svg
index b44847d9e1..35d0b39c03 100644
--- a/public/assets/img/svg/gitea-codecommit.svg
+++ b/public/assets/img/svg/gitea-codecommit.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 100 100" class="svg gitea-codecommit" width="16" height="16" aria-hidden="true"><g fill="none" fill-rule="evenodd"><path fill="#C925D1" d="M0 0h80v80H0z"/><path fill="#FFF" d="M26.628 28.105h-2.017v-6.982c0-.558.36-.99.926-.99l7.144-.007v1.994H27.95l4.862 4.819-1.445 1.434-4.806-4.728zm28.07 10.867 1.869.827-6.541 14.446-1.868-.827zm1.311 10.493 4.003-2.89-3.526-3.535 1.458-1.422 4.36 4.373a1.002 1.002 0 0 1-.126 1.527l-4.963 3.58zm-9.043-8.802 1.205 1.633-4.061 2.932 3.538 3.536-1.454 1.424-4.374-4.373a1 1 0 0 1 .124-1.528zM69 24.13v42.858c0 .56-.458 1.012-1.024 1.012h-31.26c-.272 0-.53-.107-.723-.297a.96.96 0 0 1-.285-.7V55.034h2.018v10.971h29.256V25.113H37.726v-1.995h30.25c.566 0 1.024.453 1.024 1.012M33.182 34.588c0-1.927 1.585-3.495 3.535-3.495s3.535 1.568 3.535 3.495-1.585 3.495-3.535 3.495-3.535-1.568-3.535-3.495M17.549 66.009c-1.95 0-3.535-1.568-3.535-3.495s1.585-3.494 3.535-3.494 3.535 1.567 3.535 3.494-1.585 3.495-3.535 3.495m-3.535-23.442c0-1.927 1.585-3.495 3.535-3.495 1.982 0 3.535 1.535 3.535 3.495 0 1.927-1.585 3.495-3.535 3.495s-3.535-1.568-3.535-3.495m.004-25.081c0-1.925 1.584-3.491 3.53-3.491 1.948 0 3.532 1.566 3.532 3.49s-1.584 3.491-3.531 3.491-3.531-1.566-3.531-3.49m23.708 29.762v-7.276c2.57-.477 4.535-2.708 4.535-5.384 0-3.022-2.487-5.482-5.544-5.482s-5.545 2.46-5.545 5.482c0 2.676 1.966 4.907 4.536 5.384v7.276c0 1.163-.786 2.218-1.98 2.686l-10.451 4.1c-1.673.657-2.903 1.948-3.434 3.496-.433-.195-.801-.336-1.285-.416v-9.146c2.623-.433 4.535-2.687 4.535-5.401 0-2.764-1.878-4.972-4.535-5.393V22.889c2.626-.431 4.54-2.688 4.54-5.403 0-3.025-2.49-5.486-5.55-5.486S12 14.46 12 17.486c0 2.64 2.022 4.85 4.54 5.369v14.347c-2.515.518-4.536 2.727-4.536 5.365s2.02 4.846 4.536 5.365v9.217c-2.515.52-4.536 2.727-4.536 5.365 0 3.022 2.488 5.482 5.545 5.482s5.544-2.46 5.544-5.482a5.43 5.43 0 0 0-1.458-3.693c.167-1.27 1.066-2.384 2.397-2.905l10.45-4.1c1.98-.777 3.244-2.57 3.244-4.568"/></g></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 100 100" class="svg gitea-codecommit" width="16" height="16" aria-hidden="true"><g fill="none" fill-rule="evenodd"><path fill="#c925d1" d="M0 0h80v80H0z"/><path fill="#fff" d="M26.628 28.105h-2.017v-6.982c0-.558.36-.99.926-.99l7.144-.007v1.994H27.95l4.862 4.819-1.445 1.434-4.806-4.728zm28.07 10.867 1.869.827-6.541 14.446-1.868-.827zm1.311 10.493 4.003-2.89-3.526-3.535 1.458-1.422 4.36 4.373a1.002 1.002 0 0 1-.126 1.527l-4.963 3.58zm-9.043-8.802 1.205 1.633-4.061 2.932 3.538 3.536-1.454 1.424-4.374-4.373a1 1 0 0 1 .124-1.528zM69 24.13v42.858c0 .56-.458 1.012-1.024 1.012h-31.26c-.272 0-.53-.107-.723-.297a.96.96 0 0 1-.285-.7V55.034h2.018v10.971h29.256V25.113H37.726v-1.995h30.25c.566 0 1.024.453 1.024 1.012M33.182 34.588c0-1.927 1.585-3.495 3.535-3.495s3.535 1.568 3.535 3.495-1.585 3.495-3.535 3.495-3.535-1.568-3.535-3.495M17.549 66.009c-1.95 0-3.535-1.568-3.535-3.495s1.585-3.494 3.535-3.494 3.535 1.567 3.535 3.494-1.585 3.495-3.535 3.495m-3.535-23.442c0-1.927 1.585-3.495 3.535-3.495 1.982 0 3.535 1.535 3.535 3.495 0 1.927-1.585 3.495-3.535 3.495s-3.535-1.568-3.535-3.495m.004-25.081c0-1.925 1.584-3.491 3.53-3.491 1.948 0 3.532 1.566 3.532 3.49s-1.584 3.491-3.531 3.491-3.531-1.566-3.531-3.49m23.708 29.762v-7.276c2.57-.477 4.535-2.708 4.535-5.384 0-3.022-2.487-5.482-5.544-5.482s-5.545 2.46-5.545 5.482c0 2.676 1.966 4.907 4.536 5.384v7.276c0 1.163-.786 2.218-1.98 2.686l-10.451 4.1c-1.673.657-2.903 1.948-3.434 3.496-.433-.195-.801-.336-1.285-.416v-9.146c2.623-.433 4.535-2.687 4.535-5.401 0-2.764-1.878-4.972-4.535-5.393V22.889c2.626-.431 4.54-2.688 4.54-5.403 0-3.025-2.49-5.486-5.55-5.486S12 14.46 12 17.486c0 2.64 2.022 4.85 4.54 5.369v14.347c-2.515.518-4.536 2.727-4.536 5.365s2.02 4.846 4.536 5.365v9.217c-2.515.52-4.536 2.727-4.536 5.365 0 3.022 2.488 5.482 5.545 5.482s5.544-2.46 5.544-5.482a5.43 5.43 0 0 0-1.458-3.693c.167-1.27 1.066-2.384 2.397-2.905l10.45-4.1c1.98-.777 3.244-2.57 3.244-4.568"/></g></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-debian.svg b/public/assets/img/svg/gitea-debian.svg
index fa2f2f49dc..e92d0b6937 100644
--- a/public/assets/img/svg/gitea-debian.svg
+++ b/public/assets/img/svg/gitea-debian.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 260" class="svg gitea-debian" width="16" height="16" aria-hidden="true"><g fill="#D70751"><path d="M124.525 137.053c-4.125.058.78 2.125 6.165 2.954a55 55 0 0 0 4.04-3.479c-3.354.821-6.765.838-10.205.525m22.14-5.52c2.457-3.389 4.246-7.102 4.878-10.939-.551 2.736-2.035 5.099-3.435 7.592-7.711 4.854-.726-2.883-.004-5.824-8.29 10.436-1.138 6.257-1.439 9.171m8.174-21.265c.497-7.428-1.462-5.08-2.121-2.245.766.4 1.377 5.237 2.121 2.245M108.883 8.736c2.201.395 4.757.698 4.398 1.224 2.407-.528 2.954-1.015-4.398-1.224M113.281 9.96l-1.556.32 1.448-.127z"/><path d="M181.93 113.085c.247 6.671-1.95 9.907-3.932 15.637l-3.564 1.781c-2.919 5.666.282 3.598-1.807 8.105-4.556 4.049-13.823 12.67-16.789 13.457-2.163-.047 1.469-2.554 1.943-3.537-6.097 4.188-4.894 6.285-14.217 8.83l-.273-.607c-23.001 10.818-54.947-10.622-54.526-39.876-.246 1.857-.698 1.393-1.208 2.144-1.186-15.052 6.952-30.17 20.675-36.343 13.427-6.646 29.163-3.918 38.78 5.044-5.282-6.92-15.795-14.254-28.255-13.568-12.208.193-23.625 7.95-27.436 16.369-6.253 3.938-6.979 15.177-9.704 17.233-3.665 26.943 6.896 38.583 24.762 52.275 2.812 1.896.792 2.184 1.173 3.627-5.936-2.779-11.372-6.976-15.841-12.114 2.372 3.473 4.931 6.847 8.239 9.499-5.596-1.897-13.074-13.563-15.256-14.038 9.647 17.274 39.142 30.295 54.587 23.836-7.146.263-16.226.146-24.256-2.822-3.371-1.734-7.958-5.331-7.14-6.003 21.079 7.875 42.854 5.965 61.09-8.655 4.641-3.614 9.709-9.761 11.173-9.846-2.206 3.317.377 1.596-1.318 4.523 4.625-7.456-2.008-3.035 4.779-12.877l2.507 3.453c-.931-6.188 7.687-13.704 6.813-23.492 1.975-2.994 2.206 3.22.107 10.107 2.912-7.64.767-8.867 1.516-15.171.81 2.118 1.867 4.37 2.412 6.606-1.895-7.382 1.948-12.433 2.898-16.724-.937-.415-2.928 3.264-3.383-5.457.065-3.788 1.054-1.985 1.435-2.917-.744-.427-2.694-3.33-3.88-8.9.86-1.308 2.3 3.393 3.47 3.586-.753-4.429-2.049-7.805-2.103-11.202-3.421-7.149-1.211.953-3.985-3.069-3.641-11.357 3.021-2.637 3.47-7.796 5.52 7.995 8.667 20.387 10.11 25.519-1.103-6.258-2.883-12.32-5.058-18.185 1.677.705-2.699-12.875 2.18-3.882-5.21-19.172-22.302-37.087-38.025-45.493 1.924 1.76 4.354 3.971 3.481 4.317-7.819-4.656-6.444-5.018-7.565-6.985-6.369-2.591-6.788.208-11.007.004-12.005-6.368-14.318-5.69-25.368-9.681l.502 2.349c-7.953-2.649-9.265 1.005-17.862.009-.523-.409 2.753-1.479 5.452-1.871-7.69 1.015-7.329-1.515-14.854.279 1.855-1.301 3.815-2.162 5.793-3.269-6.271.381-14.971 3.649-12.286.677-10.235 4.569-28.403 10.976-38.597 20.535l-.321-2.142c-4.672 5.608-20.371 16.748-21.622 24.011l-1.249.291c-2.431 4.116-4.004 8.781-5.932 13.016-3.18 5.417-4.661 2.085-4.208 2.934-6.253 12.679-9.359 23.332-12.043 32.069 1.912 2.858.046 17.206.769 28.688-3.141 56.709 39.8 111.77 86.737 124.48 6.88 2.459 17.11 2.364 25.813 2.618-10.268-2.937-11.595-1.556-21.595-5.044-7.215-3.398-8.797-7.277-13.907-11.711l2.022 3.573c-10.021-3.547-5.829-4.39-13.982-6.972l2.16-2.82c-3.249-.246-8.604-5.475-10.069-8.371l-3.553.14c-4.27-5.269-6.545-9.063-6.379-12.005l-1.148 2.047c-1.301-2.235-15.709-19.759-8.234-15.679-1.389-1.271-3.235-2.067-5.237-5.703l1.522-1.739c-3.597-4.627-6.621-10.562-6.391-12.536 1.919 2.592 3.25 3.075 4.568 3.52-9.083-22.539-9.593-1.242-16.474-22.942l1.456-.116c-1.116-1.682-1.793-3.506-2.69-5.298l.633-6.313c-6.541-7.562-1.829-32.151-.887-45.637.655-5.485 5.459-11.322 9.114-20.477l-2.227-.384c4.256-7.423 24.301-29.814 33.583-28.662 4.499-5.649-.892-.02-1.772-1.443 9.878-10.223 12.984-7.222 19.65-9.061 7.19-4.268-6.17 1.664-2.761-1.628 12.427-3.174 8.808-7.216 25.021-8.828 1.71.973-3.969 1.503-5.395 2.766 10.354-5.066 32.769-3.914 47.326 2.811 16.895 7.896 35.873 31.232 36.622 53.189l.852.229c-.431 8.729 1.336 18.822-1.727 28.094l2.1-4.385"/><path d="m79.5 142.715-.578 2.893c2.71 3.683 4.861 7.673 8.323 10.552-2.49-4.863-4.341-6.872-7.745-13.445m6.409-.251c-1.435-1.587-2.284-3.497-3.235-5.4.909 3.345 2.771 6.219 4.504 9.143zm113.411-24.65-.605 1.52c-1.111 7.892-3.511 15.701-7.189 22.941a72.1 72.1 0 0 0 7.79-24.461M109.698 6.757c2.789-1.022 6.855-.56 9.814-1.233-3.855.324-7.693.517-11.484 1.005zM11.781 58.824c.642 5.951-4.477 8.26 1.134 4.337 3.007-6.773-1.175-1.87-1.134-4.337M5.188 86.362c1.292-3.967 1.526-6.349 2.02-8.645-3.571 4.566-1.643 5.539-2.02 8.645"/></g></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 260" class="svg gitea-debian" width="16" height="16" aria-hidden="true"><g fill="#d70751"><path d="M124.525 137.053c-4.125.058.78 2.125 6.165 2.954a55 55 0 0 0 4.04-3.479c-3.354.821-6.765.838-10.205.525m22.14-5.52c2.457-3.389 4.246-7.102 4.878-10.939-.551 2.736-2.035 5.099-3.435 7.592-7.711 4.854-.726-2.883-.004-5.824-8.29 10.436-1.138 6.257-1.439 9.171m8.174-21.265c.497-7.428-1.462-5.08-2.121-2.245.766.4 1.377 5.237 2.121 2.245M108.883 8.736c2.201.395 4.757.698 4.398 1.224 2.407-.528 2.954-1.015-4.398-1.224M113.281 9.96l-1.556.32 1.448-.127z"/><path d="M181.93 113.085c.247 6.671-1.95 9.907-3.932 15.637l-3.564 1.781c-2.919 5.666.282 3.598-1.807 8.105-4.556 4.049-13.823 12.67-16.789 13.457-2.163-.047 1.469-2.554 1.943-3.537-6.097 4.188-4.894 6.285-14.217 8.83l-.273-.607c-23.001 10.818-54.947-10.622-54.526-39.876-.246 1.857-.698 1.393-1.208 2.144-1.186-15.052 6.952-30.17 20.675-36.343 13.427-6.646 29.163-3.918 38.78 5.044-5.282-6.92-15.795-14.254-28.255-13.568-12.208.193-23.625 7.95-27.436 16.369-6.253 3.938-6.979 15.177-9.704 17.233-3.665 26.943 6.896 38.583 24.762 52.275 2.812 1.896.792 2.184 1.173 3.627-5.936-2.779-11.372-6.976-15.841-12.114 2.372 3.473 4.931 6.847 8.239 9.499-5.596-1.897-13.074-13.563-15.256-14.038 9.647 17.274 39.142 30.295 54.587 23.836-7.146.263-16.226.146-24.256-2.822-3.371-1.734-7.958-5.331-7.14-6.003 21.079 7.875 42.854 5.965 61.09-8.655 4.641-3.614 9.709-9.761 11.173-9.846-2.206 3.317.377 1.596-1.318 4.523 4.625-7.456-2.008-3.035 4.779-12.877l2.507 3.453c-.931-6.188 7.687-13.704 6.813-23.492 1.975-2.994 2.206 3.22.107 10.107 2.912-7.64.767-8.867 1.516-15.171.81 2.118 1.867 4.37 2.412 6.606-1.895-7.382 1.948-12.433 2.898-16.724-.937-.415-2.928 3.264-3.383-5.457.065-3.788 1.054-1.985 1.435-2.917-.744-.427-2.694-3.33-3.88-8.9.86-1.308 2.3 3.393 3.47 3.586-.753-4.429-2.049-7.805-2.103-11.202-3.421-7.149-1.211.953-3.985-3.069-3.641-11.357 3.021-2.637 3.47-7.796 5.52 7.995 8.667 20.387 10.11 25.519-1.103-6.258-2.883-12.32-5.058-18.185 1.677.705-2.699-12.875 2.18-3.882-5.21-19.172-22.302-37.087-38.025-45.493 1.924 1.76 4.354 3.971 3.481 4.317-7.819-4.656-6.444-5.018-7.565-6.985-6.369-2.591-6.788.208-11.007.004-12.005-6.368-14.318-5.69-25.368-9.681l.502 2.349c-7.953-2.649-9.265 1.005-17.862.009-.523-.409 2.753-1.479 5.452-1.871-7.69 1.015-7.329-1.515-14.854.279 1.855-1.301 3.815-2.162 5.793-3.269-6.271.381-14.971 3.649-12.286.677-10.235 4.569-28.403 10.976-38.597 20.535l-.321-2.142c-4.672 5.608-20.371 16.748-21.622 24.011l-1.249.291c-2.431 4.116-4.004 8.781-5.932 13.016-3.18 5.417-4.661 2.085-4.208 2.934-6.253 12.679-9.359 23.332-12.043 32.069 1.912 2.858.046 17.206.769 28.688-3.141 56.709 39.8 111.77 86.737 124.48 6.88 2.459 17.11 2.364 25.813 2.618-10.268-2.937-11.595-1.556-21.595-5.044-7.215-3.398-8.797-7.277-13.907-11.711l2.022 3.573c-10.021-3.547-5.829-4.39-13.982-6.972l2.16-2.82c-3.249-.246-8.604-5.475-10.069-8.371l-3.553.14c-4.27-5.269-6.545-9.063-6.379-12.005l-1.148 2.047c-1.301-2.235-15.709-19.759-8.234-15.679-1.389-1.271-3.235-2.067-5.237-5.703l1.522-1.739c-3.597-4.627-6.621-10.562-6.391-12.536 1.919 2.592 3.25 3.075 4.568 3.52-9.083-22.539-9.593-1.242-16.474-22.942l1.456-.116c-1.116-1.682-1.793-3.506-2.69-5.298l.633-6.313c-6.541-7.562-1.829-32.151-.887-45.637.655-5.485 5.459-11.322 9.114-20.477l-2.227-.384c4.256-7.423 24.301-29.814 33.583-28.662 4.499-5.649-.892-.02-1.772-1.443 9.878-10.223 12.984-7.222 19.65-9.061 7.19-4.268-6.17 1.664-2.761-1.628 12.427-3.174 8.808-7.216 25.021-8.828 1.71.973-3.969 1.503-5.395 2.766 10.354-5.066 32.769-3.914 47.326 2.811 16.895 7.896 35.873 31.232 36.622 53.189l.852.229c-.431 8.729 1.336 18.822-1.727 28.094l2.1-4.385"/><path d="m79.5 142.715-.578 2.893c2.71 3.683 4.861 7.673 8.323 10.552-2.49-4.863-4.341-6.872-7.745-13.445m6.409-.251c-1.435-1.587-2.284-3.497-3.235-5.4.909 3.345 2.771 6.219 4.504 9.143zm113.411-24.65-.605 1.52c-1.111 7.892-3.511 15.701-7.189 22.941a72.1 72.1 0 0 0 7.79-24.461M109.698 6.757c2.789-1.022 6.855-.56 9.814-1.233-3.855.324-7.693.517-11.484 1.005zM11.781 58.824c.642 5.951-4.477 8.26 1.134 4.337 3.007-6.773-1.175-1.87-1.134-4.337M5.188 86.362c1.292-3.967 1.526-6.349 2.02-8.645-3.571 4.566-1.643 5.539-2.02 8.645"/></g></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-gitbucket.svg b/public/assets/img/svg/gitea-gitbucket.svg
index 62f603484e..b9e99724b2 100644
--- a/public/assets/img/svg/gitea-gitbucket.svg
+++ b/public/assets/img/svg/gitea-gitbucket.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 316 329" class="svg gitea-gitbucket" width="16" height="16" aria-hidden="true"><path d="M123 21.1c-44.8 2.8-84 12.8-97.1 24.6-5 4.5-5 7.1 0 11.6C41.7 71.5 96.6 82.9 150 83h10.6l5.4-5.6c11.8-12.1 21.3-12.4 32.6-1.2l5.2 5 13.3-1.7c33.8-4.2 61.5-12.7 71.8-22 5.3-4.8 5.3-7.2 0-12-10.1-9.1-39.1-18.1-70.4-21.9-28.3-3.4-65.6-4.4-95.5-2.5M23.2 80.6c.4 1.6 7 42.9 14.8 91.9 7.9 49 14.7 89.5 15.2 90.2 1.7 2.1 25.8 11.4 41.6 15.9 13 3.7 35.1 8.4 40 8.4.6 0 1.2-.6 1.2-1.3 0-.6-17.4-18.5-38.6-39.7C57.9 206.6 55 203.2 55 196s3-10.7 38.3-45.9c30.1-30 34.8-34.3 36.6-33.5 1.1.5 8.7 7.4 17 15.2l15.1 14.4v5.9c0 7.3 2.4 12.4 7.7 16.7l3.8 3v61.8l-3.8 3.4c-10.2 8.9-10.2 22.9-.1 30.7 3.1 2.3 4.9 2.8 10.8 3.1 8.2.4 11.5-1.1 16.2-7.3 2.2-3 2.9-5.1 3.2-10 .4-6.5-.2-8.3-5.3-15.4l-2.5-3.4v-26.6c0-26.7.3-31.1 2.3-31.1.5 0 5.4 4.4 10.9 9.7 9.6 9.5 9.9 10 10.8 15.7 1.7 10.3 8.9 16.6 19 16.6 7.6 0 13.5-3.9 17.4-11.7 3.2-6.4 1.6-14.3-4.3-20.6-4.1-4.4-7.3-5.7-14.9-5.7h-6.8l-12.7-12.1c-10.7-10.1-12.7-12.6-13.2-15.7-1.2-7.2-1.6-8.2-4.7-11.7-3.9-4.5-7.7-6.5-12.2-6.5-1.9 0-4.5-.4-5.8-.9s-9.9-8.2-19.3-17l-17-16-7.1-.1c-10.6 0-36-2.7-52.4-5.5-22.8-4-38.5-8.6-57.9-17.2-1.1-.5-1.3 0-.9 2.3M278.5 83.6c-8.6 3.6-28 8.8-42.5 11.4-6.9 1.2-12.9 2.6-13.5 3.1-.6.6 9.3 11.2 27.5 29.4 15.6 15.6 28.5 28.3 28.7 28.1s1.9-15.8 3.8-34.7 3.7-35.6 4-37.2c.6-3.4-.2-3.4-8-.1M255.2 259.3c-7.8 7.8-14.2 14.6-14.2 15s.7.7 1.6.7c2.2 0 23-8.9 24.2-10.3.9-1.1 3.5-18.7 2.9-19.3-.2-.2-6.7 6.1-14.5 13.9M56 283.5c0 3.4 4 9.5 8.4 12.9 6.1 4.6 19.7 10.4 31.7 13.5 16.9 4.3 32.1 6.2 53.4 6.8l19 .5-7-7.1c-6.8-6.9-7.1-7.1-12-7.1-18.9 0-55.1-7.9-80.6-17.6C62.5 283 57 281 56.6 281c-.3 0-.6 1.1-.6 2.5M262 283.4c-5.3 2.8-25 9.7-36 12.6l-11.4 2.9-7.8 7.8c-4.2 4.2-7.6 7.9-7.4 8.1.9.8 24.4-4.1 33.4-6.9 16.4-5.3 26.7-11.4 30.8-18.5 2.4-4 3.1-7.4 1.7-7.4-.5.1-1.9.7-3.3 1.4"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 316 329" class="svg gitea-gitbucket" width="16" height="16" aria-hidden="true"><path d="M123 21.1c-44.8 2.8-84 12.8-97.1 24.6-5 4.5-5 7.1 0 11.6C41.7 71.5 96.6 82.9 150 83h10.6l5.4-5.6c11.8-12.1 21.3-12.4 32.6-1.2l5.2 5 13.3-1.7c33.8-4.2 61.5-12.7 71.8-22 5.3-4.8 5.3-7.2 0-12-10.1-9.1-39.1-18.1-70.4-21.9-28.3-3.4-65.6-4.4-95.5-2.5M23.2 80.6c.4 1.6 7 42.9 14.8 91.9 7.9 49 14.7 89.5 15.2 90.2 1.7 2.1 25.8 11.4 41.6 15.9 13 3.7 35.1 8.4 40 8.4.6 0 1.2-.6 1.2-1.3 0-.6-17.4-18.5-38.6-39.7C57.9 206.6 55 203.2 55 196s3-10.7 38.3-45.9c30.1-30 34.8-34.3 36.6-33.5 1.1.5 8.7 7.4 17 15.2l15.1 14.4v5.9c0 7.3 2.4 12.4 7.7 16.7l3.8 3v61.8l-3.8 3.4c-10.2 8.9-10.2 22.9-.1 30.7 3.1 2.3 4.9 2.8 10.8 3.1 8.2.4 11.5-1.1 16.2-7.3 2.2-3 2.9-5.1 3.2-10 .4-6.5-.2-8.3-5.3-15.4l-2.5-3.4v-26.6c0-26.7.3-31.1 2.3-31.1.5 0 5.4 4.4 10.9 9.7 9.6 9.5 9.9 10 10.8 15.7 1.7 10.3 8.9 16.6 19 16.6 7.6 0 13.5-3.9 17.4-11.7 3.2-6.4 1.6-14.3-4.3-20.6-4.1-4.4-7.3-5.7-14.9-5.7h-6.8l-12.7-12.1c-10.7-10.1-12.7-12.6-13.2-15.7-1.2-7.2-1.6-8.2-4.7-11.7-3.9-4.5-7.7-6.5-12.2-6.5-1.9 0-4.5-.4-5.8-.9s-9.9-8.2-19.3-17l-17-16-7.1-.1c-10.6 0-36-2.7-52.4-5.5-22.8-4-38.5-8.6-57.9-17.2-1.1-.5-1.3 0-.9 2.3M278.5 83.6c-8.6 3.6-28 8.8-42.5 11.4-6.9 1.2-12.9 2.6-13.5 3.1-.6.6 9.3 11.2 27.5 29.4 15.6 15.6 28.5 28.3 28.7 28.1s1.9-15.8 3.8-34.7 3.7-35.6 4-37.2c.6-3.4-.2-3.4-8-.1M255.2 259.3c-7.8 7.8-14.2 14.6-14.2 15s.7.7 1.6.7c2.2 0 23-8.9 24.2-10.3.9-1.1 3.5-18.7 2.9-19.3-.2-.2-6.7 6.1-14.5 13.9M56 283.5c0 3.4 4 9.5 8.4 12.9 6.1 4.6 19.7 10.4 31.7 13.5 16.9 4.3 32.1 6.2 53.4 6.8l19 .5-7-7.1c-6.8-6.9-7.1-7.1-12-7.1-18.9 0-55.1-7.9-80.6-17.6C62.5 283 57 281 56.6 281c-.3 0-.6 1.1-.6 2.5M262 283.4c-5.3 2.8-25 9.7-36 12.6l-11.4 2.9-7.8 7.8c-4.2 4.2-7.6 7.9-7.4 8.1.9.8 24.4-4.1 33.4-6.9 16.4-5.3 26.7-11.4 30.8-18.5 2.4-4 3.1-7.4 1.7-7.4-.5.1-1.9.7-3.3 1.4"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-gitlab.svg b/public/assets/img/svg/gitea-gitlab.svg
index 03fcb0b87e..e2d708e7be 100644
--- a/public/assets/img/svg/gitea-gitlab.svg
+++ b/public/assets/img/svg/gitea-gitlab.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="svg gitea-gitlab" width="16" height="16" aria-hidden="true"><path fill="#E24329" d="m31.462 12.779-.045-.115-4.35-11.35a1.14 1.14 0 0 0-.447-.541 1.16 1.16 0 0 0-1.343.071c-.187.15-.322.356-.386.587l-2.94 9.001h-11.9l-2.941-9a1.14 1.14 0 0 0-1.045-.84 1.15 1.15 0 0 0-1.13.72L.579 12.68l-.045.113a8.09 8.09 0 0 0 2.68 9.34l.016.012.038.03 6.635 4.967 3.28 2.484 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484 6.673-4.997.018-.013a8.09 8.09 0 0 0 2.69-9.352Z"/><path fill="#FC6D26" d="m31.462 12.779-.045-.115a14.75 14.75 0 0 0-5.856 2.634l-9.553 7.24L22.1 27.14l6.673-4.997.019-.013a8.09 8.09 0 0 0 2.67-9.352Z"/><path fill="#FCA326" d="m9.908 27.14 3.275 2.485 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484s-2.835-2.14-6.092-4.603z"/><path fill="#FC6D26" d="M6.435 15.305A14.7 14.7 0 0 0 .58 12.672l-.045.113a8.09 8.09 0 0 0 2.68 9.347l.016.012.038.03 6.635 4.967 6.105-4.603-9.573-7.233Z"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="svg gitea-gitlab" width="16" height="16" aria-hidden="true"><path fill="#e24329" d="m31.462 12.779-.045-.115-4.35-11.35a1.14 1.14 0 0 0-.447-.541 1.16 1.16 0 0 0-1.343.071c-.187.15-.322.356-.386.587l-2.94 9.001h-11.9l-2.941-9a1.14 1.14 0 0 0-1.045-.84 1.15 1.15 0 0 0-1.13.72L.579 12.68l-.045.113a8.09 8.09 0 0 0 2.68 9.34l.016.012.038.03 6.635 4.967 3.28 2.484 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484 6.673-4.997.018-.013a8.09 8.09 0 0 0 2.69-9.352Z"/><path fill="#fc6d26" d="m31.462 12.779-.045-.115a14.75 14.75 0 0 0-5.856 2.634l-9.553 7.24L22.1 27.14l6.673-4.997.019-.013a8.09 8.09 0 0 0 2.67-9.352Z"/><path fill="#fca326" d="m9.908 27.14 3.275 2.485 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484s-2.835-2.14-6.092-4.603z"/><path fill="#fc6d26" d="M6.435 15.305A14.7 14.7 0 0 0 .58 12.672l-.045.113a8.09 8.09 0 0 0 2.68 9.347l.016.012.038.03 6.635 4.967 6.105-4.603-9.573-7.233Z"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-google.svg b/public/assets/img/svg/gitea-google.svg
index 7dd2622df6..26ee04cb64 100644
--- a/public/assets/img/svg/gitea-google.svg
+++ b/public/assets/img/svg/gitea-google.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="gitea-google__svg gitea-google__gitea-google svg gitea-google" viewBox="0 0 24 24" width="16" height="16"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53"/><path fill="none" d="M1 1h22v22H1z"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="gitea-google__svg gitea-google__gitea-google svg gitea-google" viewBox="0 0 24 24" width="16" height="16"><path fill="#4285f4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09"/><path fill="#34a853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23"/><path fill="#fbbc05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22z"/><path fill="#ea4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53"/><path fill="none" d="M1 1h22v22H1z"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-maven.svg b/public/assets/img/svg/gitea-maven.svg
index 320d01a234..f6ece7dc28 100644
--- a/public/assets/img/svg/gitea-maven.svg
+++ b/public/assets/img/svg/gitea-maven.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2392.5 4226.6" class="svg gitea-maven" width="16" height="16" aria-hidden="true"><linearGradient id="gitea-maven__a" x1="-5167.1" x2="-4570.1" y1="697.55" y2="1395.6" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#F69923"/><stop offset=".312" stop-color="#F79A23"/><stop offset=".838" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__a)" d="M1798.9 20.1C1732.6 59.2 1622.5 170 1491 330.5l120.8 228c84.8-121.3 170.9-230.4 257.8-323.6 6.7-7.4 10.2-10.9 10.2-10.9-3.4 3.6-6.8 7.3-10.2 10.9-28.1 31-113.4 130.5-242.1 328.1 123.9-6.2 314.3-31.5 469.6-58.1 46.2-258.8-45.3-377.3-45.3-377.3S1935.5-60.6 1798.9 20.1"/><path fill="none" d="M1594.4 1320.7c.9-.2 1.8-.3 2.7-.5l-17.4 1.9c-1.1.5-2 1-3.1 1.4 6-.9 11.9-1.9 17.8-2.8m-123.3 408.4c-9.9 2.2-20 3.9-30.2 5.4 10.2-1.5 20.3-3.3 30.2-5.4m-838 916.1c1.3-3.4 2.6-6.8 3.8-10.2 26.6-70.2 52.9-138.4 79-204.9 29.3-74.6 58.2-146.8 86.8-216.8 30.1-73.8 59.8-145.1 89.1-214 30.7-72.3 61-141.9 90.7-208.9 24.2-54.5 48-107.3 71.5-158.4 7.8-17 15.6-33.9 23.4-50.6 15.4-33.1 30.7-65.6 45.7-97.3 13.9-29.3 27.7-57.9 41.4-86 4.5-9.4 9.1-18.6 13.6-27.9.7-1.5 1.5-3 2.2-4.5l-14.8 1.6-11.8-23.2c-1.1 2.3-2.3 4.5-3.5 6.8q-31.8 63.15-63 127.5c-12 24.8-24 49.7-35.9 74.7-33 69.3-65.5 139.2-97.4 209.6-32.3 71.1-63.9 142.6-94.9 214.2-30.5 70.3-60.3 140.7-89.6 210.9-29.2 70.1-57.7 140-85.6 209.4-29.1 72.5-57.4 144.3-84.8 215.3-6.2 16-12.4 32-18.5 48-22 57.3-43.4 113.8-64.3 169.6l18.6 36.7 16.6-1.8c.6-1.7 1.2-3.4 1.8-5 26.9-73.5 53.5-145.1 79.9-214.8m800.1-909.5c.1 0 .1-.1.2-.1 0 0-.1 0-.2.1"/><path fill="#BE202E" d="M1393.2 1934.8c-15.4 2.8-31.3 5.5-47.6 8.3-.1 0-.2.1-.3.1 8.2-1.2 16.3-2.4 24.3-3.8s15.8-2.9 23.6-4.6"/><path fill="#BE202E" d="M1393.2 1934.8c-15.4 2.8-31.3 5.5-47.6 8.3-.1 0-.2.1-.3.1 8.2-1.2 16.3-2.4 24.3-3.8s15.8-2.9 23.6-4.6" opacity=".35"/><path fill="#BE202E" d="M1433.6 1735.5s-.1 0-.1.1c-.1 0-.1.1-.2.1 2.6-.3 5.1-.8 7.6-1.1 10.3-1.5 20.4-3.3 30.2-5.4-12.3 2-24.8 4.2-37.5 6.3"/><path fill="#BE202E" d="M1433.6 1735.5s-.1 0-.1.1c-.1 0-.1.1-.2.1 2.6-.3 5.1-.8 7.6-1.1 10.3-1.5 20.4-3.3 30.2-5.4-12.3 2-24.8 4.2-37.5 6.3" opacity=".35"/><linearGradient id="gitea-maven__b" x1="-9585.3" x2="-5326.2" y1="620.5" y2="620.5" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9E2064"/><stop offset=".63" stop-color="#C92037"/><stop offset=".751" stop-color="#CD2335"/><stop offset="1" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__b)" d="M1255.7 1147.6c36.7-68.6 73.9-135.7 111.5-201 39-67.8 78.5-133.6 118.4-197 2.3-3.7 4.7-7.5 7-11.3 39.4-62.4 79.2-122.4 119.3-179.8l-120.8-228c-9.1 11.1-18.2 22.4-27.5 33.9-34.8 43.4-71 90.1-108.1 139.6-41.8 55.8-84.8 115.4-128.5 177.9-40.3 57.8-81.2 118.3-122.1 180.9-34.8 53.3-69.8 108.2-104.5 164.5l-3.9 6.3 157.2 310.5c33.6-66.5 67.6-132.1 102-196.5"/><linearGradient id="gitea-maven__c" x1="-9071.2" x2="-6533.2" y1="1047.7" y2="1047.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#282662"/><stop offset=".095" stop-color="#662E8D"/><stop offset=".788" stop-color="#9F2064"/><stop offset=".949" stop-color="#CD2032"/></linearGradient><path fill="url(#gitea-maven__c)" d="M539.7 2897.1c-20.8 57.2-41.7 115.4-62.7 174.9-.3.9-.6 1.7-.9 2.6-3 8.4-5.9 16.8-8.9 25.2-14.1 40.1-26.4 76.2-54.5 158.3 46.3 21.1 83.5 76.7 118.7 139.8-3.7-65.3-30.8-126.7-82.1-174.2 228.3 10.3 425-47.4 526.7-214.3 9.1-14.9 17.4-30.5 24.9-47.2-46.2 58.6-103.5 83.5-211.4 77.4-.2.1-.5.2-.7.3.2-.1.5-.2.7-.3 158.8-71.1 238.5-139.3 308.9-252.4 16.7-26.8 32.9-56.1 49.5-88.6-138.9 142.6-299.8 183.2-469.3 152.4l-127.1 13.9c-4 10.7-7.9 21.4-11.8 32.2"/><linearGradient id="gitea-maven__d" x1="-9346.1" x2="-5087" y1="580.82" y2="580.82" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9E2064"/><stop offset=".63" stop-color="#C92037"/><stop offset=".751" stop-color="#CD2335"/><stop offset="1" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__d)" d="M599 2612.4c27.5-71 55.8-142.8 84.8-215.3 27.8-69.4 56.4-139.2 85.6-209.4s59.1-140.5 89.6-210.9c31-71.6 62.7-143.1 94.9-214.2 31.9-70.3 64.4-140.3 97.4-209.6 11.9-25 23.9-49.9 35.9-74.7q31.2-64.35 63-127.5c1.1-2.3 2.3-4.5 3.5-6.8l-157.2-310.5c-2.6 4.2-5.1 8.4-7.7 12.6-36.6 59.8-73.1 121-108.9 183.5-36.2 63.1-71.7 127.4-106.4 192.6-29.3 55-57.9 110.5-85.7 166.5-5.6 11.4-11.1 22.6-16.6 33.9-34.3 70.5-65.2 138.6-93.2 204.1-31.7 74.2-59.6 145.1-84 212.3-16.1 44.2-30.7 86.9-44.1 127.9-11 35-21.5 70.1-31.4 105-23.5 82.3-43.7 164.4-60.3 246.2l158 311.9c20.9-55.8 42.3-112.3 64.3-169.6 6.1-15.9 12.3-32 18.5-48"/><linearGradient id="gitea-maven__e" x1="-9035.5" x2="-6797.2" y1="638.44" y2="638.44" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#282662"/><stop offset=".095" stop-color="#662E8D"/><stop offset=".788" stop-color="#9F2064"/><stop offset=".949" stop-color="#CD2032"/></linearGradient><path fill="url(#gitea-maven__e)" d="M356.1 2529.2c-19.8 99.8-33.9 199.2-41 298-.2 3.5-.6 6.9-.8 10.4-49.3-79-181.3-156.1-181-155.4 94.5 137 166.2 273 176.9 406.5-50.6 10.4-119.9-4.6-200-34.1 83.5 76.7 146.2 97.9 170.6 103.6-76.7 4.8-156.6 57.5-237.1 118.2 117.7-48 212.8-67 280.9-51.6-108 305.8-216.3 643.4-324.6 1001.8 33.2-9.8 53-32.1 64.1-62.3 19.3-64.9 147.4-490.7 348.1-1050.4 5.7-15.9 11.5-31.9 17.3-48 1.6-4.5 3.3-9 4.9-13.4 21.2-58.7 43.2-118.6 65.9-179.7 5.2-13.9 10.4-27.8 15.6-41.8.1-.3.2-.6.3-.8l-157.8-311.8c-.7 3.5-1.6 7.1-2.3 10.8"/><linearGradient id="gitea-maven__f" x1="-9346.1" x2="-5087" y1="1021.6" y2="1021.6" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9E2064"/><stop offset=".63" stop-color="#C92037"/><stop offset=".751" stop-color="#CD2335"/><stop offset="1" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__f)" d="M1178.1 1370.3c-4.5 9.2-9 18.5-13.6 27.9-13.6 28.1-27.4 56.7-41.4 86-15.1 31.7-30.3 64.1-45.7 97.3-7.8 16.7-15.5 33.5-23.4 50.6-23.5 51.1-47.3 103.9-71.5 158.4-29.7 67-60 136.6-90.7 208.9-29.3 68.9-59 140.2-89.1 214-28.6 70-57.5 142.3-86.8 216.8-26.1 66.5-52.4 134.7-79 204.9-1.3 3.4-2.6 6.8-3.8 10.2-26.4 69.7-53 141.3-79.8 214.7-.6 1.7-1.2 3.4-1.8 5l127.1-13.9c-2.5-.5-5.1-.8-7.6-1.3 152-18.9 354-132.5 484.6-272.7 60.2-64.6 114.8-140.8 165.3-230 37.6-66.4 72.9-140 106.5-221.5 29.4-71.2 57.6-148.3 84.8-231.9-34.9 18.4-74.9 31.9-119 41.3-7.7 1.6-15.6 3.2-23.6 4.6s-16.1 2.7-24.3 3.8c.1 0 .2-.1.3-.1 141.7-54.5 231.1-159.8 296.1-288.7-37.3 25.4-97.9 58.7-170.5 74.7-9.9 2.2-20 3.9-30.2 5.4-2.6.4-5.1.8-7.6 1.1.1 0 .1-.1.2-.1 0 0 .1 0 .1-.1 49.2-20.6 90.7-43.6 126.7-70.8 7.7-5.8 15.2-11.8 22.4-18.1 11-9.5 21.4-19.5 31.4-30 6.4-6.7 12.6-13.6 18.6-20.8 14.1-16.8 27.3-34.9 39.7-54.6 3.8-6 7.5-12.1 11.2-18.4 4.7-9.1 9.2-18 13.6-26.8 19.8-39.8 35.6-75.3 48.2-106.5 6.3-15.6 11.8-30 16.5-43.4 1.9-5.3 3.7-10.5 5.4-15.5 5-15 9.1-28.3 12.3-40 4.8-17.5 7.7-31.4 9.3-41.5-4.8 3.8-10.3 7.6-16.5 11.3-42.8 25.6-116.2 48.8-175.4 59.7l116.7-12.8-116.7 12.8c-.9.2-1.8.3-2.7.5-5.9 1-11.9 1.9-17.9 2.9 1.1-.5 2-1 3.1-1.4l-399.3 43.8c-.7 1.4-1.4 2.8-2.2 4.3"/><linearGradient id="gitea-maven__g" x1="-9610.3" x2="-5351.2" y1="999.73" y2="999.73" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9E2064"/><stop offset=".63" stop-color="#C92037"/><stop offset=".751" stop-color="#CD2335"/><stop offset="1" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__g)" d="M1627.6 563.1c-35.5 54.5-74.3 116.4-116 186.5-2.2 3.6-4.4 7.4-6.6 11.1-36 60.7-74.3 127.3-114.5 200.3-34.8 63-71 130.6-108.6 203.3-32.8 63.3-66.7 130.5-101.5 201.6l399.3-43.8c116.3-53.5 168.3-101.9 218.8-171.9 13.4-19.3 26.9-39.5 40.3-60.4 41-64 81.2-134.5 117.2-204.6 34.7-67.7 65.3-134.8 88.8-195.3 14.9-38.5 26.9-74.3 35.2-105.7 7.3-27.7 13-54 17.4-79.1-155.5 26.5-345.9 51.9-469.8 58"/><path fill="#BE202E" d="M1369.6 1939.4c-8 1.4-16.1 2.7-24.3 3.8 8.2-1.1 16.3-2.4 24.3-3.8"/><path fill="#BE202E" d="M1369.6 1939.4c-8 1.4-16.1 2.7-24.3 3.8 8.2-1.1 16.3-2.4 24.3-3.8" opacity=".35"/><linearGradient id="gitea-maven__h" x1="-9346.1" x2="-5087" y1="1152.7" y2="1152.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9E2064"/><stop offset=".63" stop-color="#C92037"/><stop offset=".751" stop-color="#CD2335"/><stop offset="1" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__h)" d="M1369.6 1939.4c-8 1.4-16.1 2.7-24.3 3.8 8.2-1.1 16.3-2.4 24.3-3.8"/><path fill="#BE202E" d="M1433.2 1735.7c2.6-.3 5.1-.8 7.6-1.1-2.5.3-5 .7-7.6 1.1"/><path fill="#BE202E" d="M1433.2 1735.7c2.6-.3 5.1-.8 7.6-1.1-2.5.3-5 .7-7.6 1.1" opacity=".35"/><linearGradient id="gitea-maven__i" x1="-9346.1" x2="-5087" y1="1137.7" y2="1137.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9E2064"/><stop offset=".63" stop-color="#C92037"/><stop offset=".751" stop-color="#CD2335"/><stop offset="1" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__i)" d="M1433.2 1735.7c2.6-.3 5.1-.8 7.6-1.1-2.5.3-5 .7-7.6 1.1"/><path fill="#BE202E" d="M1433.5 1735.6s.1 0 .1-.1c0 0-.1 0-.1.1"/><path fill="#BE202E" d="M1433.5 1735.6s.1 0 .1-.1c0 0-.1 0-.1.1" opacity=".35"/><linearGradient id="gitea-maven__j" x1="-6953.4" x2="-6012" y1="1134.7" y2="1134.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9E2064"/><stop offset=".63" stop-color="#C92037"/><stop offset=".751" stop-color="#CD2335"/><stop offset="1" stop-color="#E97826"/></linearGradient><path fill="url(#gitea-maven__j)" d="M1433.5 1735.6s.1 0 .1-.1c0 0-.1 0-.1.1"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2392.5 4226.6" class="svg gitea-maven" width="16" height="16" aria-hidden="true"><linearGradient id="gitea-maven__a" x1="-5167.1" x2="-4570.1" y1="697.55" y2="1395.6" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f69923"/><stop offset=".312" stop-color="#f79a23"/><stop offset=".838" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__a)" d="M1798.9 20.1C1732.6 59.2 1622.5 170 1491 330.5l120.8 228c84.8-121.3 170.9-230.4 257.8-323.6 6.7-7.4 10.2-10.9 10.2-10.9-3.4 3.6-6.8 7.3-10.2 10.9-28.1 31-113.4 130.5-242.1 328.1 123.9-6.2 314.3-31.5 469.6-58.1 46.2-258.8-45.3-377.3-45.3-377.3S1935.5-60.6 1798.9 20.1"/><path fill="none" d="M1594.4 1320.7c.9-.2 1.8-.3 2.7-.5l-17.4 1.9c-1.1.5-2 1-3.1 1.4 6-.9 11.9-1.9 17.8-2.8m-123.3 408.4c-9.9 2.2-20 3.9-30.2 5.4 10.2-1.5 20.3-3.3 30.2-5.4m-838 916.1c1.3-3.4 2.6-6.8 3.8-10.2 26.6-70.2 52.9-138.4 79-204.9 29.3-74.6 58.2-146.8 86.8-216.8 30.1-73.8 59.8-145.1 89.1-214 30.7-72.3 61-141.9 90.7-208.9 24.2-54.5 48-107.3 71.5-158.4 7.8-17 15.6-33.9 23.4-50.6 15.4-33.1 30.7-65.6 45.7-97.3 13.9-29.3 27.7-57.9 41.4-86 4.5-9.4 9.1-18.6 13.6-27.9.7-1.5 1.5-3 2.2-4.5l-14.8 1.6-11.8-23.2c-1.1 2.3-2.3 4.5-3.5 6.8q-31.8 63.15-63 127.5c-12 24.8-24 49.7-35.9 74.7-33 69.3-65.5 139.2-97.4 209.6-32.3 71.1-63.9 142.6-94.9 214.2-30.5 70.3-60.3 140.7-89.6 210.9-29.2 70.1-57.7 140-85.6 209.4-29.1 72.5-57.4 144.3-84.8 215.3-6.2 16-12.4 32-18.5 48-22 57.3-43.4 113.8-64.3 169.6l18.6 36.7 16.6-1.8c.6-1.7 1.2-3.4 1.8-5 26.9-73.5 53.5-145.1 79.9-214.8m800.1-909.5c.1 0 .1-.1.2-.1 0 0-.1 0-.2.1"/><path fill="#be202e" d="M1393.2 1934.8c-15.4 2.8-31.3 5.5-47.6 8.3-.1 0-.2.1-.3.1 8.2-1.2 16.3-2.4 24.3-3.8s15.8-2.9 23.6-4.6"/><path fill="#be202e" d="M1393.2 1934.8c-15.4 2.8-31.3 5.5-47.6 8.3-.1 0-.2.1-.3.1 8.2-1.2 16.3-2.4 24.3-3.8s15.8-2.9 23.6-4.6" opacity=".35"/><path fill="#be202e" d="M1433.6 1735.5s-.1 0-.1.1c-.1 0-.1.1-.2.1 2.6-.3 5.1-.8 7.6-1.1 10.3-1.5 20.4-3.3 30.2-5.4-12.3 2-24.8 4.2-37.5 6.3"/><path fill="#be202e" d="M1433.6 1735.5s-.1 0-.1.1c-.1 0-.1.1-.2.1 2.6-.3 5.1-.8 7.6-1.1 10.3-1.5 20.4-3.3 30.2-5.4-12.3 2-24.8 4.2-37.5 6.3" opacity=".35"/><linearGradient id="gitea-maven__b" x1="-9585.3" x2="-5326.2" y1="620.5" y2="620.5" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9e2064"/><stop offset=".63" stop-color="#c92037"/><stop offset=".751" stop-color="#cd2335"/><stop offset="1" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__b)" d="M1255.7 1147.6c36.7-68.6 73.9-135.7 111.5-201 39-67.8 78.5-133.6 118.4-197 2.3-3.7 4.7-7.5 7-11.3 39.4-62.4 79.2-122.4 119.3-179.8l-120.8-228c-9.1 11.1-18.2 22.4-27.5 33.9-34.8 43.4-71 90.1-108.1 139.6-41.8 55.8-84.8 115.4-128.5 177.9-40.3 57.8-81.2 118.3-122.1 180.9-34.8 53.3-69.8 108.2-104.5 164.5l-3.9 6.3 157.2 310.5c33.6-66.5 67.6-132.1 102-196.5"/><linearGradient id="gitea-maven__c" x1="-9071.2" x2="-6533.2" y1="1047.7" y2="1047.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#282662"/><stop offset=".095" stop-color="#662e8d"/><stop offset=".788" stop-color="#9f2064"/><stop offset=".949" stop-color="#cd2032"/></linearGradient><path fill="url(#gitea-maven__c)" d="M539.7 2897.1c-20.8 57.2-41.7 115.4-62.7 174.9-.3.9-.6 1.7-.9 2.6-3 8.4-5.9 16.8-8.9 25.2-14.1 40.1-26.4 76.2-54.5 158.3 46.3 21.1 83.5 76.7 118.7 139.8-3.7-65.3-30.8-126.7-82.1-174.2 228.3 10.3 425-47.4 526.7-214.3 9.1-14.9 17.4-30.5 24.9-47.2-46.2 58.6-103.5 83.5-211.4 77.4-.2.1-.5.2-.7.3.2-.1.5-.2.7-.3 158.8-71.1 238.5-139.3 308.9-252.4 16.7-26.8 32.9-56.1 49.5-88.6-138.9 142.6-299.8 183.2-469.3 152.4l-127.1 13.9c-4 10.7-7.9 21.4-11.8 32.2"/><linearGradient id="gitea-maven__d" x1="-9346.1" x2="-5087" y1="580.82" y2="580.82" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9e2064"/><stop offset=".63" stop-color="#c92037"/><stop offset=".751" stop-color="#cd2335"/><stop offset="1" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__d)" d="M599 2612.4c27.5-71 55.8-142.8 84.8-215.3 27.8-69.4 56.4-139.2 85.6-209.4s59.1-140.5 89.6-210.9c31-71.6 62.7-143.1 94.9-214.2 31.9-70.3 64.4-140.3 97.4-209.6 11.9-25 23.9-49.9 35.9-74.7q31.2-64.35 63-127.5c1.1-2.3 2.3-4.5 3.5-6.8l-157.2-310.5c-2.6 4.2-5.1 8.4-7.7 12.6-36.6 59.8-73.1 121-108.9 183.5-36.2 63.1-71.7 127.4-106.4 192.6-29.3 55-57.9 110.5-85.7 166.5-5.6 11.4-11.1 22.6-16.6 33.9-34.3 70.5-65.2 138.6-93.2 204.1-31.7 74.2-59.6 145.1-84 212.3-16.1 44.2-30.7 86.9-44.1 127.9-11 35-21.5 70.1-31.4 105-23.5 82.3-43.7 164.4-60.3 246.2l158 311.9c20.9-55.8 42.3-112.3 64.3-169.6 6.1-15.9 12.3-32 18.5-48"/><linearGradient id="gitea-maven__e" x1="-9035.5" x2="-6797.2" y1="638.44" y2="638.44" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#282662"/><stop offset=".095" stop-color="#662e8d"/><stop offset=".788" stop-color="#9f2064"/><stop offset=".949" stop-color="#cd2032"/></linearGradient><path fill="url(#gitea-maven__e)" d="M356.1 2529.2c-19.8 99.8-33.9 199.2-41 298-.2 3.5-.6 6.9-.8 10.4-49.3-79-181.3-156.1-181-155.4 94.5 137 166.2 273 176.9 406.5-50.6 10.4-119.9-4.6-200-34.1 83.5 76.7 146.2 97.9 170.6 103.6-76.7 4.8-156.6 57.5-237.1 118.2 117.7-48 212.8-67 280.9-51.6-108 305.8-216.3 643.4-324.6 1001.8 33.2-9.8 53-32.1 64.1-62.3 19.3-64.9 147.4-490.7 348.1-1050.4 5.7-15.9 11.5-31.9 17.3-48 1.6-4.5 3.3-9 4.9-13.4 21.2-58.7 43.2-118.6 65.9-179.7 5.2-13.9 10.4-27.8 15.6-41.8.1-.3.2-.6.3-.8l-157.8-311.8c-.7 3.5-1.6 7.1-2.3 10.8"/><linearGradient id="gitea-maven__f" x1="-9346.1" x2="-5087" y1="1021.6" y2="1021.6" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9e2064"/><stop offset=".63" stop-color="#c92037"/><stop offset=".751" stop-color="#cd2335"/><stop offset="1" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__f)" d="M1178.1 1370.3c-4.5 9.2-9 18.5-13.6 27.9-13.6 28.1-27.4 56.7-41.4 86-15.1 31.7-30.3 64.1-45.7 97.3-7.8 16.7-15.5 33.5-23.4 50.6-23.5 51.1-47.3 103.9-71.5 158.4-29.7 67-60 136.6-90.7 208.9-29.3 68.9-59 140.2-89.1 214-28.6 70-57.5 142.3-86.8 216.8-26.1 66.5-52.4 134.7-79 204.9-1.3 3.4-2.6 6.8-3.8 10.2-26.4 69.7-53 141.3-79.8 214.7-.6 1.7-1.2 3.4-1.8 5l127.1-13.9c-2.5-.5-5.1-.8-7.6-1.3 152-18.9 354-132.5 484.6-272.7 60.2-64.6 114.8-140.8 165.3-230 37.6-66.4 72.9-140 106.5-221.5 29.4-71.2 57.6-148.3 84.8-231.9-34.9 18.4-74.9 31.9-119 41.3-7.7 1.6-15.6 3.2-23.6 4.6s-16.1 2.7-24.3 3.8c.1 0 .2-.1.3-.1 141.7-54.5 231.1-159.8 296.1-288.7-37.3 25.4-97.9 58.7-170.5 74.7-9.9 2.2-20 3.9-30.2 5.4-2.6.4-5.1.8-7.6 1.1.1 0 .1-.1.2-.1 0 0 .1 0 .1-.1 49.2-20.6 90.7-43.6 126.7-70.8 7.7-5.8 15.2-11.8 22.4-18.1 11-9.5 21.4-19.5 31.4-30 6.4-6.7 12.6-13.6 18.6-20.8 14.1-16.8 27.3-34.9 39.7-54.6 3.8-6 7.5-12.1 11.2-18.4 4.7-9.1 9.2-18 13.6-26.8 19.8-39.8 35.6-75.3 48.2-106.5 6.3-15.6 11.8-30 16.5-43.4 1.9-5.3 3.7-10.5 5.4-15.5 5-15 9.1-28.3 12.3-40 4.8-17.5 7.7-31.4 9.3-41.5-4.8 3.8-10.3 7.6-16.5 11.3-42.8 25.6-116.2 48.8-175.4 59.7l116.7-12.8-116.7 12.8c-.9.2-1.8.3-2.7.5-5.9 1-11.9 1.9-17.9 2.9 1.1-.5 2-1 3.1-1.4l-399.3 43.8c-.7 1.4-1.4 2.8-2.2 4.3"/><linearGradient id="gitea-maven__g" x1="-9610.3" x2="-5351.2" y1="999.73" y2="999.73" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9e2064"/><stop offset=".63" stop-color="#c92037"/><stop offset=".751" stop-color="#cd2335"/><stop offset="1" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__g)" d="M1627.6 563.1c-35.5 54.5-74.3 116.4-116 186.5-2.2 3.6-4.4 7.4-6.6 11.1-36 60.7-74.3 127.3-114.5 200.3-34.8 63-71 130.6-108.6 203.3-32.8 63.3-66.7 130.5-101.5 201.6l399.3-43.8c116.3-53.5 168.3-101.9 218.8-171.9 13.4-19.3 26.9-39.5 40.3-60.4 41-64 81.2-134.5 117.2-204.6 34.7-67.7 65.3-134.8 88.8-195.3 14.9-38.5 26.9-74.3 35.2-105.7 7.3-27.7 13-54 17.4-79.1-155.5 26.5-345.9 51.9-469.8 58"/><path fill="#be202e" d="M1369.6 1939.4c-8 1.4-16.1 2.7-24.3 3.8 8.2-1.1 16.3-2.4 24.3-3.8"/><path fill="#be202e" d="M1369.6 1939.4c-8 1.4-16.1 2.7-24.3 3.8 8.2-1.1 16.3-2.4 24.3-3.8" opacity=".35"/><linearGradient id="gitea-maven__h" x1="-9346.1" x2="-5087" y1="1152.7" y2="1152.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9e2064"/><stop offset=".63" stop-color="#c92037"/><stop offset=".751" stop-color="#cd2335"/><stop offset="1" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__h)" d="M1369.6 1939.4c-8 1.4-16.1 2.7-24.3 3.8 8.2-1.1 16.3-2.4 24.3-3.8"/><path fill="#be202e" d="M1433.2 1735.7c2.6-.3 5.1-.8 7.6-1.1-2.5.3-5 .7-7.6 1.1"/><path fill="#be202e" d="M1433.2 1735.7c2.6-.3 5.1-.8 7.6-1.1-2.5.3-5 .7-7.6 1.1" opacity=".35"/><linearGradient id="gitea-maven__i" x1="-9346.1" x2="-5087" y1="1137.7" y2="1137.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9e2064"/><stop offset=".63" stop-color="#c92037"/><stop offset=".751" stop-color="#cd2335"/><stop offset="1" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__i)" d="M1433.2 1735.7c2.6-.3 5.1-.8 7.6-1.1-2.5.3-5 .7-7.6 1.1"/><path fill="#be202e" d="M1433.5 1735.6s.1 0 .1-.1c0 0-.1 0-.1.1"/><path fill="#be202e" d="M1433.5 1735.6s.1 0 .1-.1c0 0-.1 0-.1.1" opacity=".35"/><linearGradient id="gitea-maven__j" x1="-6953.4" x2="-6012" y1="1134.7" y2="1134.7" gradientTransform="rotate(-65.001 -2052.931 -4777.847)" gradientUnits="userSpaceOnUse"><stop offset=".323" stop-color="#9e2064"/><stop offset=".63" stop-color="#c92037"/><stop offset=".751" stop-color="#cd2335"/><stop offset="1" stop-color="#e97826"/></linearGradient><path fill="url(#gitea-maven__j)" d="M1433.5 1735.6s.1 0 .1-.1c0 0-.1 0-.1.1"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-microsoftonline.svg b/public/assets/img/svg/gitea-microsoftonline.svg
index f2ce13ac22..c143eccbb6 100644
--- a/public/assets/img/svg/gitea-microsoftonline.svg
+++ b/public/assets/img/svg/gitea-microsoftonline.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48" class="svg gitea-microsoftonline" width="16" height="16" aria-hidden="true"><path fill="url(#gitea-microsoftonline__a)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__b)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__c)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__d)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__e)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__f)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__g)" d="M4.004 30.998"/><path fill="url(#gitea-microsoftonline__h)" d="M4.004 30.998"/><defs><radialGradient id="gitea-microsoftonline__a" cx="0" cy="0" r="1" gradientTransform="rotate(110.528 5.021 11.358)scale(33.3657 58.1966)" gradientUnits="userSpaceOnUse"><stop offset=".064" stop-color="#AE7FE2"/><stop offset="1" stop-color="#0078D4"/></radialGradient><radialGradient id="gitea-microsoftonline__c" cx="0" cy="0" r="1" gradientTransform="matrix(30.7198 -4.51832 2.98465 20.29248 10.43 36.351)" gradientUnits="userSpaceOnUse"><stop offset=".134" stop-color="#D59DFF"/><stop offset="1" stop-color="#5E438F"/></radialGradient><radialGradient id="gitea-microsoftonline__e" cx="0" cy="0" r="1" gradientTransform="matrix(-24.1583 -6.12555 10.3118 -40.66824 41.055 26.504)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><radialGradient id="gitea-microsoftonline__g" cx="0" cy="0" r="1" gradientTransform="matrix(-24.1583 -6.12555 10.3118 -40.66824 41.055 26.504)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><linearGradient id="gitea-microsoftonline__b" x1="17.512" x2="12.751" y1="37.868" y2="29.635" gradientUnits="userSpaceOnUse"><stop stop-color="#114A8B"/><stop offset="1" stop-color="#0078D4" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__d" x1="40.357" x2="35.255" y1="25.377" y2="32.692" gradientUnits="userSpaceOnUse"><stop stop-color="#493474"/><stop offset="1" stop-color="#8C66BA" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__f" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__h" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient></defs></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48" class="svg gitea-microsoftonline" width="16" height="16" aria-hidden="true"><path fill="url(#gitea-microsoftonline__a)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__b)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__c)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__d)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__e)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__f)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__g)" d="M4.004 30.998"/><path fill="url(#gitea-microsoftonline__h)" d="M4.004 30.998"/><defs><radialGradient id="gitea-microsoftonline__a" cx="0" cy="0" r="1" gradientTransform="rotate(110.528 5.021 11.358)scale(33.3657 58.1966)" gradientUnits="userSpaceOnUse"><stop offset=".064" stop-color="#ae7fe2"/><stop offset="1" stop-color="#0078d4"/></radialGradient><radialGradient id="gitea-microsoftonline__c" cx="0" cy="0" r="1" gradientTransform="rotate(-8.367 253.693 -53.118)scale(31.0503 20.5108)" gradientUnits="userSpaceOnUse"><stop offset=".134" stop-color="#d59dff"/><stop offset="1" stop-color="#5e438f"/></radialGradient><radialGradient id="gitea-microsoftonline__e" cx="0" cy="0" r="1" gradientTransform="rotate(194.228 22.182 10.69)scale(24.9228 41.9552)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50e6ff"/><stop offset="1" stop-color="#436dcd"/></radialGradient><radialGradient id="gitea-microsoftonline__g" cx="0" cy="0" r="1" gradientTransform="rotate(194.228 22.182 10.69)scale(24.9228 41.9552)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50e6ff"/><stop offset="1" stop-color="#436dcd"/></radialGradient><linearGradient id="gitea-microsoftonline__b" x1="17.512" x2="12.751" y1="37.868" y2="29.635" gradientUnits="userSpaceOnUse"><stop stop-color="#114a8b"/><stop offset="1" stop-color="#0078d4" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__d" x1="40.357" x2="35.255" y1="25.377" y2="32.692" gradientUnits="userSpaceOnUse"><stop stop-color="#493474"/><stop offset="1" stop-color="#8c66ba" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__f" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2d3f80"/><stop offset="1" stop-color="#436dcd" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__h" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2d3f80"/><stop offset="1" stop-color="#436dcd" stop-opacity="0"/></linearGradient></defs></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-npm.svg b/public/assets/img/svg/gitea-npm.svg
index 7ef74e72bd..2b05c79353 100644
--- a/public/assets/img/svg/gitea-npm.svg
+++ b/public/assets/img/svg/gitea-npm.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 7" class="svg gitea-npm" width="16" height="16" aria-hidden="true"><path fill="#CB3837" d="M0 0h18v6H9v1H5V6H0zm1 5h2V2h1v3h1V1H1zm5-4v5h2V5h2V1zm2 1h1v2H8zm3-1v4h2V2h1v3h1V2h1v3h1V1z"/><path fill="#fff" d="M1 5h2V2h1v3h1V1H1zM6 1v5h2V5h2V1zm3 3H8V2h1zM11 1v4h2V2h1v3h1V2h1v3h1V1z"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 7" class="svg gitea-npm" width="16" height="16" aria-hidden="true"><path fill="#cb3837" d="M0 0h18v6H9v1H5V6H0zm1 5h2V2h1v3h1V1H1zm5-4v5h2V5h2V1zm2 1h1v2H8zm3-1v4h2V2h1v3h1V2h1v3h1V1z"/><path fill="#fff" d="M1 5h2V2h1v3h1V1H1zM6 1v5h2V5h2V1zm3 3H8V2h1zM11 1v4h2V2h1v3h1V2h1v3h1V1z"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-onedev.svg b/public/assets/img/svg/gitea-onedev.svg
index 94ad1bab31..7ecd18895d 100644
--- a/public/assets/img/svg/gitea-onedev.svg
+++ b/public/assets/img/svg/gitea-onedev.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 700 700" class="svg gitea-onedev" width="16" height="16" aria-hidden="true"><path d="M315.5 99.6c-29.5 4-55.8 12-81.2 24.8L223 130l-5.2-4c-14.9-11.3-37.6-14.9-55.8-9-19.1 6.3-35.1 22.2-41.1 41-2.7 8.3-3.6 22.9-1.9 31.2 1.5 8 5 16.5 9.1 22.5 3.1 4.7 3.1 4.8 1.4 7.8C106 260 95.1 294.4 92 337.7c-1.1 15.7-.1 40.2 2.1 53l1.1 6.5-4.9 4.4c-2.8 2.3-7.5 7.6-10.6 11.6-19.4 25.5-24.7 57.9-14.4 88.3 9.2 26.9 31.2 48.8 58.4 58.1 20.6 6.9 40.6 7 61.1.1l6.7-2.2 10.5 7.1c45.6 31 92 45.5 146 45.5 33 0 61.6-5.2 91-16.4 67.6-25.8 122.9-81.1 148.4-148.4l2.7-7.2 7.7-3.8c9.1-4.5 21.1-15.7 25.9-24.3 21.1-37.5-1-84.3-43.2-91.7-19.9-3.5-39.3 2.7-53.9 17.2-7.1 7.1-11.7 14.5-15.3 24.7-3.4 9.4-3.8 25.8-.9 35.3 2.8 9.5 8.5 19.3 15.3 26.4 7.2 7.6 7.2 6 0 20.5-8.9 18.1-20.3 34.1-35.2 49.5-34.6 35.7-78.2 56.3-128.3 60.3-42.8 3.4-89.3-8.9-125-33-1.1-.8-1-1.7.8-5.2 12.1-23.6 13.5-53.7 3.9-78-8.7-21.8-27.5-41.6-48.6-51.2-9-4.1-22.7-7.4-34-8.3l-9.1-.7-.8-9.6c-3.5-46.9 13.5-99.8 45.5-141.7 6.5-8.6 24.3-26.7 33.6-34.2 43.8-35.6 101.3-52.8 158.1-47.2 39.9 3.9 79 19.1 110.6 43 16.9 12.8 37.5 34.9 48.6 52l4.3 6.7-3.3 5.2c-2.9 4.7-3.3 6.3-3.6 13.4-.3 7.3-.1 8.6 2.5 13.6 3.2 6.1 10.2 12 16.3 13.9 22.8 6.8 43-16.9 32.6-38.2-3.1-6.4-9.3-12.2-14.7-13.8-2.5-.8-4.1-2.1-5.2-4.3-.9-1.7-3.2-5.8-5.1-9.2l-3.5-6 3.6-5c17.7-24.4 15.8-57.5-4.4-79.4-8-8.6-15.5-13.6-25.9-17.2-19.8-6.8-38.9-4.2-56.5 7.8l-7.8 5.3-15.3-7.4c-27.9-13.4-55-21.3-84-24.4-13.3-1.5-48.1-1.2-60.3.5"/><path d="M271.8 271.1c-13.9 2.1-30.5 17.3-40.5 37.4-18.3 36.4-13.4 81.5 9.8 91.5 15.2 6.5 34.5-2.7 48.6-23.2 5.5-8 9.7-15.7 9-16.5-.3-.2-2 .3-3.8 1.2-2.4 1.3-5.1 1.6-10.5 1.3-6.1-.3-7.9-.8-11.6-3.4-8.9-6.2-12.4-19.1-7.9-29 2.4-5.2 9-10.8 14.7-12.4 9.1-2.6 20 1.4 25.2 9.2l2.7 4.2.3-12.4c.4-18.9-3.4-31.6-12.4-40.5-6.3-6.3-14.2-8.8-23.6-7.4M420.5 271c-11.6 1.9-20.2 11.3-24.9 27-2.1 6.9-3.1 20-2.2 27.4l.8 5.7 2.1-3.2c10.2-15 31.6-14 39.9 2 6 11.5 1.5 25.1-10.4 31.2-5 2.5-15 2.6-20 .1l-3.6-1.9 1.4 3.3c6.1 14.5 20 30.1 32.3 36.1 5.7 2.8 14.4 4 20.4 2.9 5.2-1 12.1-6.1 16.1-11.9 18.1-26.4 8.1-79-20-105.8-10.8-10.2-21.6-14.6-31.9-12.9M322.5 431.9c-16.1 1.6-23.5 6.1-23.5 14.3 0 11.4 13 21.1 34 25.4 10.2 2 31.2 1.5 40.5-1 13.5-3.7 23.8-10.3 27.6-17.7 4.9-9.7-.2-17.1-13.8-20-6.1-1.2-54.2-2-64.8-1"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 700" class="svg gitea-onedev" width="16" height="16" aria-hidden="true"><path d="M315.5 99.6c-29.5 4-55.8 12-81.2 24.8L223 130l-5.2-4c-14.9-11.3-37.6-14.9-55.8-9-19.1 6.3-35.1 22.2-41.1 41-2.7 8.3-3.6 22.9-1.9 31.2 1.5 8 5 16.5 9.1 22.5 3.1 4.7 3.1 4.8 1.4 7.8C106 260 95.1 294.4 92 337.7c-1.1 15.7-.1 40.2 2.1 53l1.1 6.5-4.9 4.4c-2.8 2.3-7.5 7.6-10.6 11.6-19.4 25.5-24.7 57.9-14.4 88.3 9.2 26.9 31.2 48.8 58.4 58.1 20.6 6.9 40.6 7 61.1.1l6.7-2.2 10.5 7.1c45.6 31 92 45.5 146 45.5 33 0 61.6-5.2 91-16.4 67.6-25.8 122.9-81.1 148.4-148.4l2.7-7.2 7.7-3.8c9.1-4.5 21.1-15.7 25.9-24.3 21.1-37.5-1-84.3-43.2-91.7-19.9-3.5-39.3 2.7-53.9 17.2-7.1 7.1-11.7 14.5-15.3 24.7-3.4 9.4-3.8 25.8-.9 35.3 2.8 9.5 8.5 19.3 15.3 26.4 7.2 7.6 7.2 6 0 20.5-8.9 18.1-20.3 34.1-35.2 49.5-34.6 35.7-78.2 56.3-128.3 60.3-42.8 3.4-89.3-8.9-125-33-1.1-.8-1-1.7.8-5.2 12.1-23.6 13.5-53.7 3.9-78-8.7-21.8-27.5-41.6-48.6-51.2-9-4.1-22.7-7.4-34-8.3l-9.1-.7-.8-9.6c-3.5-46.9 13.5-99.8 45.5-141.7 6.5-8.6 24.3-26.7 33.6-34.2 43.8-35.6 101.3-52.8 158.1-47.2 39.9 3.9 79 19.1 110.6 43 16.9 12.8 37.5 34.9 48.6 52l4.3 6.7-3.3 5.2c-2.9 4.7-3.3 6.3-3.6 13.4-.3 7.3-.1 8.6 2.5 13.6 3.2 6.1 10.2 12 16.3 13.9 22.8 6.8 43-16.9 32.6-38.2-3.1-6.4-9.3-12.2-14.7-13.8-2.5-.8-4.1-2.1-5.2-4.3-.9-1.7-3.2-5.8-5.1-9.2l-3.5-6 3.6-5c17.7-24.4 15.8-57.5-4.4-79.4-8-8.6-15.5-13.6-25.9-17.2-19.8-6.8-38.9-4.2-56.5 7.8l-7.8 5.3-15.3-7.4c-27.9-13.4-55-21.3-84-24.4-13.3-1.5-48.1-1.2-60.3.5"/><path d="M271.8 271.1c-13.9 2.1-30.5 17.3-40.5 37.4-18.3 36.4-13.4 81.5 9.8 91.5 15.2 6.5 34.5-2.7 48.6-23.2 5.5-8 9.7-15.7 9-16.5-.3-.2-2 .3-3.8 1.2-2.4 1.3-5.1 1.6-10.5 1.3-6.1-.3-7.9-.8-11.6-3.4-8.9-6.2-12.4-19.1-7.9-29 2.4-5.2 9-10.8 14.7-12.4 9.1-2.6 20 1.4 25.2 9.2l2.7 4.2.3-12.4c.4-18.9-3.4-31.6-12.4-40.5-6.3-6.3-14.2-8.8-23.6-7.4M420.5 271c-11.6 1.9-20.2 11.3-24.9 27-2.1 6.9-3.1 20-2.2 27.4l.8 5.7 2.1-3.2c10.2-15 31.6-14 39.9 2 6 11.5 1.5 25.1-10.4 31.2-5 2.5-15 2.6-20 .1l-3.6-1.9 1.4 3.3c6.1 14.5 20 30.1 32.3 36.1 5.7 2.8 14.4 4 20.4 2.9 5.2-1 12.1-6.1 16.1-11.9 18.1-26.4 8.1-79-20-105.8-10.8-10.2-21.6-14.6-31.9-12.9M322.5 431.9c-16.1 1.6-23.5 6.1-23.5 14.3 0 11.4 13 21.1 34 25.4 10.2 2 31.2 1.5 40.5-1 13.5-3.7 23.8-10.3 27.6-17.7 4.9-9.7-.2-17.1-13.8-20-6.1-1.2-54.2-2-64.8-1"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-openid.svg b/public/assets/img/svg/gitea-openid.svg
index f4702d2cdf..10c37145a3 100644
--- a/public/assets/img/svg/gitea-openid.svg
+++ b/public/assets/img/svg/gitea-openid.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 2400 2400" class="svg gitea-openid" width="16" height="16" aria-hidden="true"><path fill="#ff7c00" d="m1270 218.3-173.1 84.3-.7 981.8c-.3 540 .2 981.4 1.1 981l174.5-81.8 172.3-80.8v-984.4c0-541.7-.2-984.7-.4-984.4z"/><path fill="#aaa" d="M981.9 785.5c-425.3 63.2-766.5 264.1-889 523a491.5 491.5 0 0 0-43.6 146c-4.2 29.2-4.7 95-1.2 124 19 152.6 115.2 299.9 273.2 418.8 147.7 111 350.5 196.5 568.6 239.7 59 11.6 179 29 200.5 29 2.3 0 3-23.2 3-109.1v-109.2l-5.1-1-37.9-6a1182 1182 0 0 1-305.4-90.6c-122.2-55.7-225.1-137.7-284.6-226.4-107.5-160.5-81.3-344.3 70-491.3 57-55.5 115.4-95.2 199.5-136.1a1112.6 1112.6 0 0 1 269.4-89.2l29.7-6c3.7-1.2 4-8.6 4-111.5V779.5l-6.3.2a823 823 0 0 0-44.8 5.8m525 104c0 103 .2 110.4 4.1 111.6l29.5 6a1221.6 1221.6 0 0 1 207.7 61.3A1088 1088 0 0 1 1862 1123c4.6 3.7 1.4 5.8-88 56-51.1 28.5-93 52.7-93 53.4 0 1.9 671.6 146.8 673.2 145.2 1.2-1.2-45.5-496-47-497.6-.2-.2-38.5 21-85 47.2l-89.6 50.2c-4.2 2-8.8.2-27.9-10.7-130.8-75-289.6-132.2-460.8-166.1a1871 1871 0 0 0-132.9-21.1c-4 0-4.2 6.7-4.2 110z"/><path fill="#cbaa7c" d="M1094.5 2156.9c0 60.6.3 85.5.5 55 .5-30.2.5-79.9 0-110.3-.2-30.2-.5-5.3-.5 55.3"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2400 2400" class="svg gitea-openid" width="16" height="16" aria-hidden="true"><path fill="#ff7c00" d="m1270 218.3-173.1 84.3-.7 981.8c-.3 540 .2 981.4 1.1 981l174.5-81.8 172.3-80.8v-984.4c0-541.7-.2-984.7-.4-984.4z"/><path fill="#aaa" d="M981.9 785.5c-425.3 63.2-766.5 264.1-889 523a491.5 491.5 0 0 0-43.6 146c-4.2 29.2-4.7 95-1.2 124 19 152.6 115.2 299.9 273.2 418.8 147.7 111 350.5 196.5 568.6 239.7 59 11.6 179 29 200.5 29 2.3 0 3-23.2 3-109.1v-109.2l-5.1-1-37.9-6a1182 1182 0 0 1-305.4-90.6c-122.2-55.7-225.1-137.7-284.6-226.4-107.5-160.5-81.3-344.3 70-491.3 57-55.5 115.4-95.2 199.5-136.1a1112.6 1112.6 0 0 1 269.4-89.2l29.7-6c3.7-1.2 4-8.6 4-111.5V779.5l-6.3.2a823 823 0 0 0-44.8 5.8m525 104c0 103 .2 110.4 4.1 111.6l29.5 6a1221.6 1221.6 0 0 1 207.7 61.3A1088 1088 0 0 1 1862 1123c4.6 3.7 1.4 5.8-88 56-51.1 28.5-93 52.7-93 53.4 0 1.9 671.6 146.8 673.2 145.2 1.2-1.2-45.5-496-47-497.6-.2-.2-38.5 21-85 47.2l-89.6 50.2c-4.2 2-8.8.2-27.9-10.7-130.8-75-289.6-132.2-460.8-166.1a1871 1871 0 0 0-132.9-21.1c-4 0-4.2 6.7-4.2 110z"/><path fill="#cbaa7c" d="M1094.5 2156.9c0 60.6.3 85.5.5 55 .5-30.2.5-79.9 0-110.3-.2-30.2-.5-5.3-.5 55.3"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-rubygems.svg b/public/assets/img/svg/gitea-rubygems.svg
index 4e43bdf2f4..7cd9d34e6a 100644
--- a/public/assets/img/svg/gitea-rubygems.svg
+++ b/public/assets/img/svg/gitea-rubygems.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.13 197.58" class="svg gitea-rubygems" width="16" height="16" aria-hidden="true"><defs><linearGradient id="gitea-rubygems__b" x1="194.9" x2="141.03" y1="153.56" y2="117.41" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#871101"/><stop offset=".99" stop-color="#911209"/><stop offset="1" stop-color="#911209"/></linearGradient><linearGradient id="gitea-rubygems__c" x1="151.8" x2="97.93" y1="217.79" y2="181.64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#871101"/><stop offset=".99" stop-color="#911209"/><stop offset="1" stop-color="#911209"/></linearGradient><linearGradient id="gitea-rubygems__d" x1="38.696" x2="47.047" y1="127.39" y2="181.66" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".23" stop-color="#E57252"/><stop offset=".46" stop-color="#DE3B20"/><stop offset=".99" stop-color="#A60003"/><stop offset="1" stop-color="#A60003"/></linearGradient><linearGradient id="gitea-rubygems__e" x1="96.133" x2="99.21" y1="76.715" y2="132.1" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".23" stop-color="#E4714E"/><stop offset=".56" stop-color="#BE1A0D"/><stop offset=".99" stop-color="#A80D00"/><stop offset="1" stop-color="#A80D00"/></linearGradient><linearGradient id="gitea-rubygems__f" x1="147.1" x2="156.31" y1="25.521" y2="65.216" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".18" stop-color="#E46342"/><stop offset=".4" stop-color="#C82410"/><stop offset=".99" stop-color="#A80D00"/><stop offset="1" stop-color="#A80D00"/></linearGradient><linearGradient id="gitea-rubygems__g" x1="118.98" x2="158.67" y1="11.542" y2="-8.305" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".54" stop-color="#C81F11"/><stop offset=".99" stop-color="#BF0905"/><stop offset="1" stop-color="#BF0905"/></linearGradient><linearGradient id="gitea-rubygems__h" x1="3.903" x2="7.17" y1="113.55" y2="146.26" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".31" stop-color="#DE4024"/><stop offset=".99" stop-color="#BF190B"/><stop offset="1" stop-color="#BF190B"/></linearGradient><linearGradient id="gitea-rubygems__i" x1="-18.556" x2="135.02" y1="155.1" y2="-2.809" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#BD0012"/><stop offset=".07" stop-color="#fff"/><stop offset=".17" stop-color="#fff"/><stop offset=".27" stop-color="#C82F1C"/><stop offset=".33" stop-color="#820C01"/><stop offset=".46" stop-color="#A31601"/><stop offset=".72" stop-color="#B31301"/><stop offset=".99" stop-color="#E82609"/><stop offset="1" stop-color="#E82609"/></linearGradient><linearGradient id="gitea-rubygems__q" x1="99.075" x2="52.818" y1="171.03" y2="159.62" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8C0C01"/><stop offset=".54" stop-color="#990C00"/><stop offset=".99" stop-color="#A80D0E"/><stop offset="1" stop-color="#A80D0E"/></linearGradient><linearGradient id="gitea-rubygems__r" x1="178.53" x2="137.43" y1="115.51" y2="78.684" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#7E110B"/><stop offset=".99" stop-color="#9E0C00"/><stop offset="1" stop-color="#9E0C00"/></linearGradient><linearGradient id="gitea-rubygems__s" x1="193.62" x2="173.15" y1="47.937" y2="26.054" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#79130D"/><stop offset=".99" stop-color="#9E120B"/><stop offset="1" stop-color="#9E120B"/></linearGradient><linearGradient id="gitea-rubygems__v" x1="26.67" x2="9.989" y1="197.34" y2="140.74" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8B2114"/><stop offset=".43" stop-color="#9E100A"/><stop offset=".99" stop-color="#B3100C"/><stop offset="1" stop-color="#B3100C"/></linearGradient><linearGradient id="gitea-rubygems__w" x1="154.64" x2="192.04" y1="9.798" y2="26.306" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#B31000"/><stop offset=".44" stop-color="#910F08"/><stop offset=".99" stop-color="#791C12"/><stop offset="1" stop-color="#791C12"/></linearGradient><linearGradient id="gitea-rubygems__a" x1="174.07" x2="132.28" y1="215.55" y2="141.75" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#FB7655"/><stop offset=".41" stop-color="#E42B1E"/><stop offset=".99" stop-color="#900"/><stop offset="1" stop-color="#900"/></linearGradient><radialGradient id="gitea-rubygems__t" cx="143.83" cy="79.388" r="50.358" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#A80D00"/><stop offset=".99" stop-color="#7E0E08"/><stop offset="1" stop-color="#7E0E08"/></radialGradient><radialGradient id="gitea-rubygems__u" cx="74.092" cy="145.75" r="66.944" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#A30C00"/><stop offset=".99" stop-color="#800E08"/><stop offset="1" stop-color="#800E08"/></radialGradient></defs><path fill="url(#gitea-rubygems__a)" fill-rule="evenodd" d="M153.5 130.41 40.38 197.58l146.47-9.94 11.28-147.69z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__b)" fill-rule="evenodd" d="m187.09 187.54-12.59-86.89-34.29 45.28z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__c)" fill-rule="evenodd" d="m187.26 187.54-92.23-7.24-54.16 17.09z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__d)" fill-rule="evenodd" d="m41 197.41 23.04-75.48-50.7 10.84z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__e)" fill-rule="evenodd" d="M140.2 146.18 119 63.14l-60.67 56.87z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__f)" fill-rule="evenodd" d="m193.32 64.31-57.35-46.84L120 69.1z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__g)" fill-rule="evenodd" d="m166.5.77-33.73 18.64L111.49.52z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__h)" fill-rule="evenodd" d="m0 158.09 14.13-25.77-11.43-30.7z" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="m1.94 100.65 11.5 32.62 49.97-11.211 57.05-53.02 16.1-51.139L111.209 0l-43.1 16.13C54.53 28.76 28.18 53.75 27.23 54.22c-.94.48-17.4 31.59-25.29 46.43" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__i)" fill-rule="evenodd" d="M42.32 42.05c29.43-29.18 67.37-46.42 81.93-31.73 14.551 14.69-.88 50.39-30.31 79.56s-66.9 47.36-81.45 32.67c-14.56-14.68.4-51.33 29.83-80.5" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__q)" fill-rule="evenodd" d="m41 197.38 22.86-75.72 75.92 24.39c-27.45 25.74-57.98 47.5-98.78 51.33" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__r)" fill-rule="evenodd" d="m120.56 68.89 19.49 77.2c22.93-24.11 43.51-50.03 53.589-82.09z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__s)" fill-rule="evenodd" d="M193.44 64.39c7.8-23.54 9.6-57.31-27.181-63.58l-30.18 16.67z" clip-rule="evenodd"/><path fill="#9e1209" fill-rule="evenodd" d="M0 157.75c1.08 38.851 29.11 39.43 41.05 39.771L13.47 133.11z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__t)" fill-rule="evenodd" d="M120.67 69.01c17.62 10.83 53.131 32.58 53.851 32.98 1.119.63 15.31-23.93 18.53-37.81z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__u)" fill-rule="evenodd" d="m63.83 121.66 30.56 58.96c18.07-9.8 32.22-21.74 45.18-34.53z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__v)" fill-rule="evenodd" d="m13.35 133.19-4.33 51.56c8.17 11.16 19.41 12.13 31.2 11.26-8.53-21.23-25.57-63.68-26.87-62.82" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__w)" fill-rule="evenodd" d="m135.9 17.61 60.71 8.52C193.37 12.4 183.42 3.54 166.46.77z" clip-rule="evenodd"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.13 197.58" class="svg gitea-rubygems" width="16" height="16" aria-hidden="true"><defs><linearGradient id="gitea-rubygems__b" x1="194.9" x2="141.03" y1="153.56" y2="117.41" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#871101"/><stop offset=".99" stop-color="#911209"/><stop offset="1" stop-color="#911209"/></linearGradient><linearGradient id="gitea-rubygems__c" x1="151.8" x2="97.93" y1="217.79" y2="181.64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#871101"/><stop offset=".99" stop-color="#911209"/><stop offset="1" stop-color="#911209"/></linearGradient><linearGradient id="gitea-rubygems__d" x1="38.696" x2="47.047" y1="127.39" y2="181.66" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".23" stop-color="#e57252"/><stop offset=".46" stop-color="#de3b20"/><stop offset=".99" stop-color="#a60003"/><stop offset="1" stop-color="#a60003"/></linearGradient><linearGradient id="gitea-rubygems__e" x1="96.133" x2="99.21" y1="76.715" y2="132.1" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".23" stop-color="#e4714e"/><stop offset=".56" stop-color="#be1a0d"/><stop offset=".99" stop-color="#a80d00"/><stop offset="1" stop-color="#a80d00"/></linearGradient><linearGradient id="gitea-rubygems__f" x1="147.1" x2="156.31" y1="25.521" y2="65.216" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".18" stop-color="#e46342"/><stop offset=".4" stop-color="#c82410"/><stop offset=".99" stop-color="#a80d00"/><stop offset="1" stop-color="#a80d00"/></linearGradient><linearGradient id="gitea-rubygems__g" x1="118.98" x2="158.67" y1="11.542" y2="-8.305" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".54" stop-color="#c81f11"/><stop offset=".99" stop-color="#bf0905"/><stop offset="1" stop-color="#bf0905"/></linearGradient><linearGradient id="gitea-rubygems__h" x1="3.903" x2="7.17" y1="113.55" y2="146.26" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".31" stop-color="#de4024"/><stop offset=".99" stop-color="#bf190b"/><stop offset="1" stop-color="#bf190b"/></linearGradient><linearGradient id="gitea-rubygems__i" x1="-18.556" x2="135.02" y1="155.1" y2="-2.809" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#bd0012"/><stop offset=".07" stop-color="#fff"/><stop offset=".17" stop-color="#fff"/><stop offset=".27" stop-color="#c82f1c"/><stop offset=".33" stop-color="#820c01"/><stop offset=".46" stop-color="#a31601"/><stop offset=".72" stop-color="#b31301"/><stop offset=".99" stop-color="#e82609"/><stop offset="1" stop-color="#e82609"/></linearGradient><linearGradient id="gitea-rubygems__q" x1="99.075" x2="52.818" y1="171.03" y2="159.62" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8c0c01"/><stop offset=".54" stop-color="#990c00"/><stop offset=".99" stop-color="#a80d0e"/><stop offset="1" stop-color="#a80d0e"/></linearGradient><linearGradient id="gitea-rubygems__r" x1="178.53" x2="137.43" y1="115.51" y2="78.684" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#7e110b"/><stop offset=".99" stop-color="#9e0c00"/><stop offset="1" stop-color="#9e0c00"/></linearGradient><linearGradient id="gitea-rubygems__s" x1="193.62" x2="173.15" y1="47.937" y2="26.054" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#79130d"/><stop offset=".99" stop-color="#9e120b"/><stop offset="1" stop-color="#9e120b"/></linearGradient><linearGradient id="gitea-rubygems__v" x1="26.67" x2="9.989" y1="197.34" y2="140.74" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8b2114"/><stop offset=".43" stop-color="#9e100a"/><stop offset=".99" stop-color="#b3100c"/><stop offset="1" stop-color="#b3100c"/></linearGradient><linearGradient id="gitea-rubygems__w" x1="154.64" x2="192.04" y1="9.798" y2="26.306" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b31000"/><stop offset=".44" stop-color="#910f08"/><stop offset=".99" stop-color="#791c12"/><stop offset="1" stop-color="#791c12"/></linearGradient><linearGradient id="gitea-rubygems__a" x1="174.07" x2="132.28" y1="215.55" y2="141.75" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fb7655"/><stop offset=".41" stop-color="#e42b1e"/><stop offset=".99" stop-color="#900"/><stop offset="1" stop-color="#900"/></linearGradient><radialGradient id="gitea-rubygems__t" cx="143.83" cy="79.388" r="50.358" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a80d00"/><stop offset=".99" stop-color="#7e0e08"/><stop offset="1" stop-color="#7e0e08"/></radialGradient><radialGradient id="gitea-rubygems__u" cx="74.092" cy="145.75" r="66.944" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a30c00"/><stop offset=".99" stop-color="#800e08"/><stop offset="1" stop-color="#800e08"/></radialGradient></defs><path fill="url(#gitea-rubygems__a)" fill-rule="evenodd" d="M153.5 130.41 40.38 197.58l146.47-9.94 11.28-147.69z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__b)" fill-rule="evenodd" d="m187.09 187.54-12.59-86.89-34.29 45.28z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__c)" fill-rule="evenodd" d="m187.26 187.54-92.23-7.24-54.16 17.09z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__d)" fill-rule="evenodd" d="m41 197.41 23.04-75.48-50.7 10.84z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__e)" fill-rule="evenodd" d="M140.2 146.18 119 63.14l-60.67 56.87z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__f)" fill-rule="evenodd" d="m193.32 64.31-57.35-46.84L120 69.1z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__g)" fill-rule="evenodd" d="m166.5.77-33.73 18.64L111.49.52z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__h)" fill-rule="evenodd" d="m0 158.09 14.13-25.77-11.43-30.7z" clip-rule="evenodd"/><path fill="#fff" fill-rule="evenodd" d="m1.94 100.65 11.5 32.62 49.97-11.211 57.05-53.02 16.1-51.139L111.209 0l-43.1 16.13C54.53 28.76 28.18 53.75 27.23 54.22c-.94.48-17.4 31.59-25.29 46.43" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__i)" fill-rule="evenodd" d="M42.32 42.05c29.43-29.18 67.37-46.42 81.93-31.73 14.551 14.69-.88 50.39-30.31 79.56s-66.9 47.36-81.45 32.67c-14.56-14.68.4-51.33 29.83-80.5" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__q)" fill-rule="evenodd" d="m41 197.38 22.86-75.72 75.92 24.39c-27.45 25.74-57.98 47.5-98.78 51.33" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__r)" fill-rule="evenodd" d="m120.56 68.89 19.49 77.2c22.93-24.11 43.51-50.03 53.589-82.09z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__s)" fill-rule="evenodd" d="M193.44 64.39c7.8-23.54 9.6-57.31-27.181-63.58l-30.18 16.67z" clip-rule="evenodd"/><path fill="#9e1209" fill-rule="evenodd" d="M0 157.75c1.08 38.851 29.11 39.43 41.05 39.771L13.47 133.11z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__t)" fill-rule="evenodd" d="M120.67 69.01c17.62 10.83 53.131 32.58 53.851 32.98 1.119.63 15.31-23.93 18.53-37.81z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__u)" fill-rule="evenodd" d="m63.83 121.66 30.56 58.96c18.07-9.8 32.22-21.74 45.18-34.53z" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__v)" fill-rule="evenodd" d="m13.35 133.19-4.33 51.56c8.17 11.16 19.41 12.13 31.2 11.26-8.53-21.23-25.57-63.68-26.87-62.82" clip-rule="evenodd"/><path fill="url(#gitea-rubygems__w)" fill-rule="evenodd" d="m135.9 17.61 60.71 8.52C193.37 12.4 183.42 3.54 166.46.77z" clip-rule="evenodd"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-swift.svg b/public/assets/img/svg/gitea-swift.svg
index 4182100185..891ac12b56 100644
--- a/public/assets/img/svg/gitea-swift.svg
+++ b/public/assets/img/svg/gitea-swift.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 59.5 59.5" class="svg gitea-swift" width="16" height="16" aria-hidden="true"><path fill="#F05138" d="M59.387 16.45a83 83 0 0 0-.027-1.792c-.034-1.301-.111-2.614-.343-3.9-.234-1.308-.618-2.523-1.222-3.71a12.46 12.46 0 0 0-5.452-5.452C51.156.992 49.94.609 48.635.374c-1.287-.232-2.6-.308-3.902-.343a86 86 0 0 0-1.792-.027Q41.876-.001 40.813 0H18.578q-1.064-.001-2.127.004c-.598.004-1.196.01-1.793.027q-.488.012-.978.036c-.978.047-1.959.133-2.924.307-.98.176-1.908.436-2.811.81A12.5 12.5 0 0 0 3.89 3.89a12.5 12.5 0 0 0-2.294 3.158C.992 8.235.61 9.45.374 10.758c-.231 1.286-.308 2.599-.343 3.9a86 86 0 0 0-.027 1.792Q-.002 17.515 0 18.578v22.234q-.001 1.064.004 2.129c.004.597.01 1.194.027 1.79.035 1.302.112 2.615.343 3.902.235 1.306.618 2.522 1.222 3.71a12.457 12.457 0 0 0 5.453 5.453c1.186.603 2.401.986 3.707 1.22 1.287.232 2.6.309 3.902.344q.896.023 1.793.026 1.063.006 2.127.004h22.235q1.065.002 2.128-.004.897-.003 1.792-.026c1.302-.035 2.615-.112 3.902-.344 1.306-.234 2.521-.617 3.708-1.221a12.46 12.46 0 0 0 5.452-5.453c.604-1.187.988-2.403 1.223-3.71.23-1.286.308-2.599.342-3.9.017-.597.023-1.194.027-1.791q.006-1.065.004-2.129V18.578q.001-1.065-.004-2.128"/><path fill="#fff" d="m47.061 36.661-.004-.005c.066-.223.133-.446.19-.675 2.466-9.82-3.55-21.432-13.731-27.545 4.461 6.048 6.434 13.373 4.681 19.78-.156.572-.344 1.12-.552 1.653-.225-.148-.51-.316-.89-.526 0 0-10.128-6.253-21.104-17.312-.288-.29 5.853 8.776 12.822 16.14-3.283-1.843-12.434-8.5-18.227-13.802.712 1.186 1.559 2.33 2.49 3.43 4.837 6.135 11.145 13.704 18.703 19.517-5.31 3.25-12.814 3.502-20.285.003a30.7 30.7 0 0 1-5.193-3.098c3.162 5.058 8.033 9.423 13.96 11.97 7.07 3.039 14.1 2.833 19.337.05l-.004.007.079-.047q.323-.172.637-.358c2.516-1.306 7.485-2.63 10.152 2.559.653 1.27 2.041-5.46-3.062-11.739z"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 59.5 59.5" class="svg gitea-swift" width="16" height="16" aria-hidden="true"><path fill="#f05138" d="M59.387 16.45a83 83 0 0 0-.027-1.792c-.034-1.301-.111-2.614-.343-3.9-.234-1.308-.618-2.523-1.222-3.71a12.46 12.46 0 0 0-5.452-5.452C51.156.992 49.94.609 48.635.374c-1.287-.232-2.6-.308-3.902-.343a86 86 0 0 0-1.792-.027Q41.876-.001 40.813 0H18.578q-1.064-.001-2.127.004c-.598.004-1.196.01-1.793.027q-.488.012-.978.036c-.978.047-1.959.133-2.924.307-.98.176-1.908.436-2.811.81A12.5 12.5 0 0 0 3.89 3.89a12.5 12.5 0 0 0-2.294 3.158C.992 8.235.61 9.45.374 10.758c-.231 1.286-.308 2.599-.343 3.9a86 86 0 0 0-.027 1.792Q-.002 17.515 0 18.578v22.234q-.001 1.064.004 2.129c.004.597.01 1.194.027 1.79.035 1.302.112 2.615.343 3.902.235 1.306.618 2.522 1.222 3.71a12.457 12.457 0 0 0 5.453 5.453c1.186.603 2.401.986 3.707 1.22 1.287.232 2.6.309 3.902.344q.896.023 1.793.026 1.063.006 2.127.004h22.235q1.065.002 2.128-.004.897-.003 1.792-.026c1.302-.035 2.615-.112 3.902-.344 1.306-.234 2.521-.617 3.708-1.221a12.46 12.46 0 0 0 5.452-5.453c.604-1.187.988-2.403 1.223-3.71.23-1.286.308-2.599.342-3.9.017-.597.023-1.194.027-1.791q.006-1.065.004-2.129V18.578q.001-1.065-.004-2.128"/><path fill="#fff" d="m47.061 36.661-.004-.005c.066-.223.133-.446.19-.675 2.466-9.82-3.55-21.432-13.731-27.545 4.461 6.048 6.434 13.373 4.681 19.78-.156.572-.344 1.12-.552 1.653-.225-.148-.51-.316-.89-.526 0 0-10.128-6.253-21.104-17.312-.288-.29 5.853 8.776 12.822 16.14-3.283-1.843-12.434-8.5-18.227-13.802.712 1.186 1.559 2.33 2.49 3.43 4.837 6.135 11.145 13.704 18.703 19.517-5.31 3.25-12.814 3.502-20.285.003a30.7 30.7 0 0 1-5.193-3.098c3.162 5.058 8.033 9.423 13.96 11.97 7.07 3.039 14.1 2.833 19.337.05l-.004.007.079-.047q.323-.172.637-.358c2.516-1.306 7.485-2.63 10.152 2.559.653 1.27 2.041-5.46-3.062-11.739z"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/gitea-vagrant.svg b/public/assets/img/svg/gitea-vagrant.svg
index ba50101d52..18b05e900d 100644
--- a/public/assets/img/svg/gitea-vagrant.svg
+++ b/public/assets/img/svg/gitea-vagrant.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 255 263" class="svg gitea-vagrant" width="16" height="16" aria-hidden="true"><path fill="#1159CC" d="M254.22 20.234 196.03 53.47l-1.64 20.618-44.19 99.772-26.27 17.34 3.18 71.6 49.53-28.55 77.58-189.946zM92.45 56.933V34.051l-.238-.136-38.483 19.102 1.642 23.034L103.4 180.6l26.02-14.71-2.31-28.09z"/><path fill="#127EFF" d="m219.56 0-57.75 33.814h-.04v23.119L127.11 137.8v27.02l-23.12 13.41L57.788 74.146V53.81L92.45 33.848 34.668 0 .006 20.234v24.783L78.022 234.49l49.088 28.31v-71.16l23.09-13.41-.27-.17 46.51-103.914V53.81l57.78-33.576z"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 255 263" class="svg gitea-vagrant" width="16" height="16" aria-hidden="true"><path fill="#1159cc" d="M254.22 20.234 196.03 53.47l-1.64 20.618-44.19 99.772-26.27 17.34 3.18 71.6 49.53-28.55 77.58-189.946zM92.45 56.933V34.051l-.238-.136-38.483 19.102 1.642 23.034L103.4 180.6l26.02-14.71-2.31-28.09z"/><path fill="#127eff" d="m219.56 0-57.75 33.814h-.04v23.119L127.11 137.8v27.02l-23.12 13.41L57.788 74.146V53.81L92.45 33.848 34.668 0 .006 20.234v24.783L78.022 234.49l49.088 28.31v-71.16l23.09-13.41-.27-.17 46.51-103.914V53.81l57.78-33.576z"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/octicon-pause.svg b/public/assets/img/svg/octicon-pause.svg
new file mode 100644
index 0000000000..008de5afdf
--- /dev/null
+++ b/public/assets/img/svg/octicon-pause.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-pause" width="16" height="16" aria-hidden="true"><path d="M5 2h1a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1m5 0h1a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1"/></svg> \ No newline at end of file
diff --git a/public/assets/img/svg/octicon-sparkle.svg b/public/assets/img/svg/octicon-sparkle.svg
new file mode 100644
index 0000000000..769350bbbe
--- /dev/null
+++ b/public/assets/img/svg/octicon-sparkle.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-sparkle" width="16" height="16" aria-hidden="true"><path d="M7.198.57c.275-.752 1.34-.752 1.615 0l.849 2.317a5.82 5.82 0 0 0 3.462 3.463l2.317.848c.753.275.753 1.34 0 1.615l-2.317.849a5.82 5.82 0 0 0-3.462 3.462l-.849 2.317c-.275.753-1.34.753-1.615 0l-.848-2.317a5.82 5.82 0 0 0-3.463-3.462L.57 8.813c-.752-.275-.752-1.34 0-1.615l2.317-.848A5.82 5.82 0 0 0 6.35 2.887zm.562 2.833A7.32 7.32 0 0 1 3.403 7.76l-.673.246.673.246a7.32 7.32 0 0 1 4.357 4.356l.246.673.246-.673a7.32 7.32 0 0 1 4.356-4.356l.673-.246-.673-.246a7.32 7.32 0 0 1-4.356-4.357l-.246-.673z"/></svg> \ No newline at end of file
diff --git a/routers/api/actions/artifacts_utils.go b/routers/api/actions/artifacts_utils.go
index 77ce765098..35868c290e 100644
--- a/routers/api/actions/artifacts_utils.go
+++ b/routers/api/actions/artifacts_utils.go
@@ -43,7 +43,7 @@ func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
return task, runID, true
}
-func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) { //nolint:unparam
+func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) { //nolint:unparam // ActionTask is never used
task := ctx.ActionTask
runID, err := strconv.ParseInt(rawRunID, 10, 64)
if err != nil || task.Job.RunID != runID {
diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go
index f35cff3df2..ba4a4f23ce 100644
--- a/routers/api/packages/alpine/alpine.go
+++ b/routers/api/packages/alpine/alpine.go
@@ -68,7 +68,7 @@ func GetRepositoryFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
@@ -216,7 +216,7 @@ func DownloadPackageFile(ctx *context.Context) {
}
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index ae4ea7ea87..878e0f9945 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -5,8 +5,6 @@ package packages
import (
"net/http"
- "regexp"
- "strings"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm"
@@ -282,42 +280,10 @@ func CommonRoutes() *web.Router {
})
})
}, reqPackageAccess(perm.AccessModeRead))
- r.Group("/conda", func() {
- var (
- downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`)
- uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`)
- )
-
- r.Get("/*", func(ctx *context.Context) {
- m := downloadPattern.FindStringSubmatch(ctx.PathParam("*"))
- if len(m) == 0 {
- ctx.Status(http.StatusNotFound)
- return
- }
-
- ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
- ctx.SetPathParam("architecture", m[2])
- ctx.SetPathParam("filename", m[3])
-
- switch m[3] {
- case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
- conda.EnumeratePackages(ctx)
- default:
- conda.DownloadPackageFile(ctx)
- }
- })
- r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) {
- m := uploadPattern.FindStringSubmatch(ctx.PathParam("*"))
- if len(m) == 0 {
- ctx.Status(http.StatusNotFound)
- return
- }
-
- ctx.SetPathParam("channel", strings.TrimSuffix(m[1], "/"))
- ctx.SetPathParam("filename", m[2])
-
- conda.UploadPackageFile(ctx)
- })
+ r.PathGroup("/conda/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("GET", "/<architecture>/<filename>", conda.ListOrGetPackages)
+ g.MatchPath("GET", "/<channel:*>/<architecture>/<filename>", conda.ListOrGetPackages)
+ g.MatchPath("PUT", "/<channel:*>/<filename>", reqPackageAccess(perm.AccessModeWrite), conda.UploadPackageFile)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/cran", func() {
r.Group("/src", func() {
@@ -358,60 +324,15 @@ func CommonRoutes() *web.Router {
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/go", func() {
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), goproxy.UploadPackage)
- r.Get("/sumdb/sum.golang.org/supported", func(ctx *context.Context) {
- ctx.Status(http.StatusNotFound)
- })
+ r.Get("/sumdb/sum.golang.org/supported", http.NotFound)
- // Manual mapping of routes because the package name contains slashes which chi does not support
// https://go.dev/ref/mod#goproxy-protocol
- r.Get("/*", func(ctx *context.Context) {
- path := ctx.PathParam("*")
-
- if strings.HasSuffix(path, "/@latest") {
- ctx.SetPathParam("name", path[:len(path)-len("/@latest")])
- ctx.SetPathParam("version", "latest")
-
- goproxy.PackageVersionMetadata(ctx)
- return
- }
-
- parts := strings.SplitN(path, "/@v/", 2)
- if len(parts) != 2 {
- ctx.Status(http.StatusNotFound)
- return
- }
-
- ctx.SetPathParam("name", parts[0])
-
- // <package/name>/@v/list
- if parts[1] == "list" {
- goproxy.EnumeratePackageVersions(ctx)
- return
- }
-
- // <package/name>/@v/<version>.zip
- if strings.HasSuffix(parts[1], ".zip") {
- ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".zip")])
-
- goproxy.DownloadPackageFile(ctx)
- return
- }
- // <package/name>/@v/<version>.info
- if strings.HasSuffix(parts[1], ".info") {
- ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".info")])
-
- goproxy.PackageVersionMetadata(ctx)
- return
- }
- // <package/name>/@v/<version>.mod
- if strings.HasSuffix(parts[1], ".mod") {
- ctx.SetPathParam("version", parts[1][:len(parts[1])-len(".mod")])
-
- goproxy.PackageVersionGoModContent(ctx)
- return
- }
-
- ctx.Status(http.StatusNotFound)
+ r.PathGroup("/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("GET", "/<name:*>/@<version:latest>", goproxy.PackageVersionMetadata)
+ g.MatchPath("GET", "/<name:*>/@v/list", goproxy.EnumeratePackageVersions)
+ g.MatchPath("GET", "/<name:*>/@v/<version>.zip", goproxy.DownloadPackageFile)
+ g.MatchPath("GET", "/<name:*>/@v/<version>.info", goproxy.PackageVersionMetadata)
+ g.MatchPath("GET", "/<name:*>/@v/<version>.mod", goproxy.PackageVersionGoModContent)
})
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/generic", func() {
@@ -532,82 +453,26 @@ func CommonRoutes() *web.Router {
})
})
}, reqPackageAccess(perm.AccessModeRead))
+
r.Group("/pypi", func() {
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
r.Get("/simple/{id}", pypi.PackageMetadata)
}, reqPackageAccess(perm.AccessModeRead))
- r.Group("/rpm", func() {
- r.Group("/repository.key", func() {
- r.Head("", rpm.GetRepositoryKey)
- r.Get("", rpm.GetRepositoryKey)
- })
-
- var (
- repoPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
- uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
- filePattern = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
- repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
- )
-
- r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
- path := ctx.PathParam("*")
- isHead := ctx.Req.Method == http.MethodHead
- isGetHead := ctx.Req.Method == http.MethodHead || ctx.Req.Method == http.MethodGet
- isPut := ctx.Req.Method == http.MethodPut
- isDelete := ctx.Req.Method == http.MethodDelete
-
- m := repoPattern.FindStringSubmatch(path)
- if len(m) == 2 && isGetHead {
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- rpm.GetRepositoryConfig(ctx)
- return
- }
- m = repoFilePattern.FindStringSubmatch(path)
- if len(m) == 3 && isGetHead {
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- ctx.SetPathParam("filename", m[2])
- if isHead {
- rpm.CheckRepositoryFileExistence(ctx)
- } else {
- rpm.GetRepositoryFile(ctx)
- }
- return
- }
-
- m = uploadPattern.FindStringSubmatch(path)
- if len(m) == 2 && isPut {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- rpm.UploadPackageFile(ctx)
- return
- }
-
- m = filePattern.FindStringSubmatch(path)
- if len(m) == 6 && (isGetHead || isDelete) {
- ctx.SetPathParam("group", strings.Trim(m[1], "/"))
- ctx.SetPathParam("name", m[2])
- ctx.SetPathParam("version", m[3])
- ctx.SetPathParam("architecture", m[4])
- if isGetHead {
- rpm.DownloadPackageFile(ctx)
- } else {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- rpm.DeletePackageFile(ctx)
- }
- return
- }
-
- ctx.Status(http.StatusNotFound)
- })
+ r.Methods("HEAD,GET", "/rpm.repo", reqPackageAccess(perm.AccessModeRead), rpm.GetRepositoryConfig)
+ r.PathGroup("/rpm/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("HEAD,GET", "/repository.key", rpm.GetRepositoryKey)
+ g.MatchPath("HEAD,GET", "/<group:*>.repo", rpm.GetRepositoryConfig)
+ g.MatchPath("HEAD", "/<group:*>/repodata/<filename>", rpm.CheckRepositoryFileExistence)
+ g.MatchPath("GET", "/<group:*>/repodata/<filename>", rpm.GetRepositoryFile)
+ g.MatchPath("PUT", "/<group:*>/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
+ // this URL pattern is only used internally in the RPM index, it is generated by us, the filename part is not really used (can be anything)
+ g.MatchPath("HEAD,GET", "/<group:*>/package/<name>/<version>/<architecture>", rpm.DownloadPackageFile)
+ g.MatchPath("HEAD,GET", "/<group:*>/package/<name>/<version>/<architecture>/<filename>", rpm.DownloadPackageFile)
+ g.MatchPath("DELETE", "/<group:*>/package/<name>/<version>/<architecture>", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
}, reqPackageAccess(perm.AccessModeRead))
+
r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@@ -621,6 +486,7 @@ func CommonRoutes() *web.Router {
r.Delete("/yank", rubygems.DeletePackage)
}, reqPackageAccess(perm.AccessModeWrite))
}, reqPackageAccess(perm.AccessModeRead))
+
r.Group("/swift", func() {
r.Group("", func() { // Needs to be unauthenticated.
r.Post("", swift.CheckAuthenticate)
@@ -632,31 +498,12 @@ func CommonRoutes() *web.Router {
r.Get("", swift.EnumeratePackageVersions)
r.Get(".json", swift.EnumeratePackageVersions)
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
- r.Group("/{version}", func() {
- r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
- r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
- r.Get("", func(ctx *context.Context) {
- // Can't use normal routes here: https://github.com/go-chi/chi/issues/781
-
- version := ctx.PathParam("version")
- if strings.HasSuffix(version, ".zip") {
- swift.CheckAcceptMediaType(swift.AcceptZip)(ctx)
- if ctx.Written() {
- return
- }
- ctx.SetPathParam("version", version[:len(version)-4])
- swift.DownloadPackageFile(ctx)
- } else {
- swift.CheckAcceptMediaType(swift.AcceptJSON)(ctx)
- if ctx.Written() {
- return
- }
- if strings.HasSuffix(version, ".json") {
- ctx.SetPathParam("version", version[:len(version)-5])
- }
- swift.PackageVersionMetadata(ctx)
- }
- })
+ r.PathGroup("/*", func(g *web.RouterPathGroup) {
+ g.MatchPath("GET", "/<version>.json", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
+ g.MatchPath("GET", "/<version>.zip", swift.CheckAcceptMediaType(swift.AcceptZip), swift.DownloadPackageFile)
+ g.MatchPath("GET", "/<version>/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
+ g.MatchPath("GET", "/<version>", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.PackageVersionMetadata)
+ g.MatchPath("PUT", "/<version>", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
})
})
r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers)
@@ -693,6 +540,8 @@ func ContainerRoutes() *web.Router {
&container.Auth{},
})
+ // TODO: Content Discovery / References (not implemented yet)
+
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Group("/token", func() {
r.Get("", container.Authenticate)
@@ -701,27 +550,22 @@ func ContainerRoutes() *web.Router {
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.PathGroup("/*", func(g *web.RouterPathGroup) {
- g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.InitiateUploadBlob)
- g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagList)
- g.MatchPath("GET,PATCH,PUT,DELETE", `/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, func(ctx *context.Context) {
- switch ctx.Req.Method {
- case http.MethodGet:
- container.GetUploadBlob(ctx)
- case http.MethodPatch:
- container.UploadBlob(ctx)
- case http.MethodPut:
- container.EndUploadBlob(ctx)
- default: /* DELETE */
- container.CancelUploadBlob(ctx)
- }
- })
+ g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.PostBlobsUploads)
+ g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagsList)
+
+ patternBlobsUploadsUUID := g.PatternRegexp(`/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName)
+ g.MatchPattern("GET", patternBlobsUploadsUUID, container.GetBlobsUpload)
+ g.MatchPattern("PATCH", patternBlobsUploadsUUID, container.PatchBlobsUpload)
+ g.MatchPattern("PUT", patternBlobsUploadsUUID, container.PutBlobsUpload)
+ g.MatchPattern("DELETE", patternBlobsUploadsUUID, container.DeleteBlobsUpload)
+
g.MatchPath("HEAD", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.HeadBlob)
g.MatchPath("GET", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.GetBlob)
g.MatchPath("DELETE", `/<image:*>/blobs/<digest>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
g.MatchPath("HEAD", `/<image:*>/manifests/<reference>`, container.VerifyImageName, container.HeadManifest)
g.MatchPath("GET", `/<image:*>/manifests/<reference>`, container.VerifyImageName, container.GetManifest)
- g.MatchPath("PUT", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.UploadManifest)
+ g.MatchPath("PUT", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.PutManifest)
g.MatchPath("DELETE", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteManifest)
})
}, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
diff --git a/routers/api/packages/arch/arch.go b/routers/api/packages/arch/arch.go
index f5dc6c1d01..bf9cc3f1b8 100644
--- a/routers/api/packages/arch/arch.go
+++ b/routers/api/packages/arch/arch.go
@@ -239,7 +239,7 @@ func GetPackageOrRepositoryFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go
index 710c614c6e..cfcf79244f 100644
--- a/routers/api/packages/cargo/cargo.go
+++ b/routers/api/packages/cargo/cargo.go
@@ -95,10 +95,7 @@ type SearchResultMeta struct {
// https://doc.rust-lang.org/cargo/reference/registries.html#search
func SearchPackages(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page < 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
perPage := ctx.FormInt("per_page")
paginator := db.ListOptions{
Page: page,
@@ -168,7 +165,7 @@ func ListOwners(ctx *context.Context) {
// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go
index a0c8c5696c..1f11afe548 100644
--- a/routers/api/packages/chef/chef.go
+++ b/routers/api/packages/chef/chef.go
@@ -343,7 +343,7 @@ func DownloadPackage(ctx *context.Context) {
pf := pd.Files[0].File
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go
index c6c14e5cf4..9daf0ffeff 100644
--- a/routers/api/packages/composer/composer.go
+++ b/routers/api/packages/composer/composer.go
@@ -53,10 +53,7 @@ func ServiceIndex(ctx *context.Context) {
// SearchPackages searches packages, only "q" is supported
// https://packagist.org/apidoc#search-packages
func SearchPackages(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page < 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
perPage := ctx.FormInt("per_page")
paginator := db.ListOptions{
Page: page,
@@ -163,7 +160,7 @@ func PackageMetadata(ctx *context.Context) {
// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go
index 8019eee9f7..fe70e02cd6 100644
--- a/routers/api/packages/conan/conan.go
+++ b/routers/api/packages/conan/conan.go
@@ -480,7 +480,7 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go
index 7a46681235..e8c97503c8 100644
--- a/routers/api/packages/conda/conda.go
+++ b/routers/api/packages/conda/conda.go
@@ -36,6 +36,24 @@ func apiError(ctx *context.Context, status int, obj any) {
})
}
+func isCondaPackageFileName(filename string) bool {
+ return strings.HasSuffix(filename, ".tar.bz2") || strings.HasSuffix(filename, ".conda")
+}
+
+func ListOrGetPackages(ctx *context.Context) {
+ filename := ctx.PathParam("filename")
+ switch filename {
+ case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
+ EnumeratePackages(ctx)
+ return
+ }
+ if isCondaPackageFileName(filename) {
+ DownloadPackageFile(ctx)
+ return
+ }
+ http.NotFound(ctx.Resp, ctx.Req)
+}
+
func EnumeratePackages(ctx *context.Context) {
type Info struct {
Subdir string `json:"subdir"`
@@ -174,6 +192,12 @@ func EnumeratePackages(ctx *context.Context) {
}
func UploadPackageFile(ctx *context.Context) {
+ filename := ctx.PathParam("filename")
+ if !isCondaPackageFileName(filename) {
+ apiError(ctx, http.StatusBadRequest, nil)
+ return
+ }
+
upload, needToClose, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -191,7 +215,7 @@ func UploadPackageFile(ctx *context.Context) {
defer buf.Close()
var pck *conda_module.Package
- if strings.HasSuffix(strings.ToLower(ctx.PathParam("filename")), ".tar.bz2") {
+ if strings.HasSuffix(filename, ".tar.bz2") {
pck, err = conda_module.ParsePackageBZ2(buf)
} else {
pck, err = conda_module.ParsePackageConda(buf, buf.Size())
@@ -293,7 +317,7 @@ func DownloadPackageFile(ctx *context.Context) {
pf := pfs[0]
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/container/auth.go b/routers/api/packages/container/auth.go
index 1d8ae6af7d..1e1b87eb79 100644
--- a/routers/api/packages/container/auth.go
+++ b/routers/api/packages/container/auth.go
@@ -21,7 +21,7 @@ func (a *Auth) Name() string {
}
// Verify extracts the user from the Bearer token
-// If it's an anonymous session a ghost user is returned
+// If it's an anonymous session, a ghost user is returned
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
packageMeta, err := packages.ParseAuthorizationRequest(req)
if err != nil {
diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go
index 4a2320ab76..4b7bcee9d0 100644
--- a/routers/api/packages/container/blob.go
+++ b/routers/api/packages/container/blob.go
@@ -20,11 +20,13 @@ import (
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
+
+ "github.com/opencontainers/go-digest"
)
// saveAsPackageBlob creates a package blob from an upload
// The uploaded blob gets stored in a special upload version to link them to the package/image
-func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam
+func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam // PackageBlob is never used
pb := packages_service.NewPackageBlob(hsr)
exists := false
@@ -88,20 +90,18 @@ func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packag
})
}
-func containerPkgName(piOwnerID int64, piName string) string {
- return fmt.Sprintf("pkg_%d_container_%s", piOwnerID, strings.ToLower(piName))
+func containerGlobalLockKey(piOwnerID int64, piName, usage string) string {
+ return fmt.Sprintf("pkg_%d_container_%s_%s", piOwnerID, strings.ToLower(piName), usage)
}
func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
- var uploadVersion *packages_model.PackageVersion
-
- releaser, err := globallock.Lock(ctx, containerPkgName(pi.Owner.ID, pi.Name))
+ releaser, err := globallock.Lock(ctx, containerGlobalLockKey(pi.Owner.ID, pi.Name, "package"))
if err != nil {
return nil, err
}
defer releaser()
- err = db.WithTx(ctx, func(ctx context.Context) error {
+ return db.WithTx2(ctx, func(ctx context.Context) (*packages_model.PackageVersion, error) {
created := true
p := &packages_model.Package{
OwnerID: pi.Owner.ID,
@@ -113,7 +113,7 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackage) {
log.Error("Error inserting package: %v", err)
- return err
+ return nil, err
}
created = false
}
@@ -121,31 +121,26 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
if created {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil {
log.Error("Error setting package property: %v", err)
- return err
+ return nil, err
}
}
pv := &packages_model.PackageVersion{
PackageID: p.ID,
CreatorID: pi.Owner.ID,
- Version: container_model.UploadVersion,
- LowerVersion: container_model.UploadVersion,
+ Version: container_module.UploadVersion,
+ LowerVersion: container_module.UploadVersion,
IsInternal: true,
MetadataJSON: "null",
}
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
log.Error("Error inserting package: %v", err)
- return err
+ return nil, err
}
}
-
- uploadVersion = pv
-
- return nil
+ return pv, nil
})
-
- return uploadVersion, err
}
func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
@@ -175,8 +170,8 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
return nil
}
-func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error {
- releaser, err := globallock.Lock(ctx, containerPkgName(ownerID, image))
+func deleteBlob(ctx context.Context, ownerID int64, image string, digest digest.Digest) error {
+ releaser, err := globallock.Lock(ctx, containerGlobalLockKey(ownerID, image, "blob"))
if err != nil {
return err
}
@@ -186,7 +181,7 @@ func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error
pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{
OwnerID: ownerID,
Image: image,
- Digest: digest,
+ Digest: string(digest),
})
if err != nil {
return err
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go
index 6ef1655235..d532f698ad 100644
--- a/routers/api/packages/container/container.go
+++ b/routers/api/packages/container/container.go
@@ -13,6 +13,7 @@ import (
"regexp"
"strconv"
"strings"
+ "sync"
auth_model "code.gitea.io/gitea/models/auth"
packages_model "code.gitea.io/gitea/models/packages"
@@ -21,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/setting"
@@ -31,17 +33,21 @@ import (
packages_service "code.gitea.io/gitea/services/packages"
container_service "code.gitea.io/gitea/services/packages/container"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
)
// maximum size of a container manifest
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
const maxManifestSize = 10 * 1024 * 1024
-var (
- imageNamePattern = regexp.MustCompile(`\A[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*\z`)
- referencePattern = regexp.MustCompile(`\A[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}\z`)
-)
+var globalVars = sync.OnceValue(func() (ret struct {
+ imageNamePattern, referencePattern *regexp.Regexp
+},
+) {
+ ret.imageNamePattern = regexp.MustCompile(`\A[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*\z`)
+ ret.referencePattern = regexp.MustCompile(`\A[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}\z`)
+ return ret
+})
type containerHeaders struct {
Status int
@@ -50,7 +56,7 @@ type containerHeaders struct {
Range string
Location string
ContentType string
- ContentLength int64
+ ContentLength optional.Option[int64]
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#legacy-docker-support-http-headers
@@ -64,8 +70,8 @@ func setResponseHeaders(resp http.ResponseWriter, h *containerHeaders) {
if h.ContentType != "" {
resp.Header().Set("Content-Type", h.ContentType)
}
- if h.ContentLength != 0 {
- resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength, 10))
+ if h.ContentLength.Has() {
+ resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength.Value(), 10))
}
if h.UploadUUID != "" {
resp.Header().Set("Docker-Upload-Uuid", h.UploadUUID)
@@ -83,9 +89,7 @@ func jsonResponse(ctx *context.Context, status int, obj any) {
Status: status,
ContentType: "application/json",
})
- if err := json.NewEncoder(ctx.Resp).Encode(obj); err != nil {
- log.Error("JSON encode: %v", err)
- }
+ _ = json.NewEncoder(ctx.Resp).Encode(obj) // ignore network errors
}
func apiError(ctx *context.Context, status int, err error) {
@@ -133,7 +137,7 @@ func ReqContainerAccess(ctx *context.Context) {
// VerifyImageName is a middleware which checks if the image name is allowed
func VerifyImageName(ctx *context.Context) {
- if !imageNamePattern.MatchString(ctx.PathParam("image")) {
+ if !globalVars().imageNamePattern.MatchString(ctx.PathParam("image")) {
apiErrorDefined(ctx, errNameInvalid)
}
}
@@ -215,7 +219,7 @@ func GetRepositoryList(ctx *context.Context) {
if len(repositories) == n {
v := url.Values{}
if n > 0 {
- v.Add("n", strconv.Itoa(n))
+ v.Add("n", strconv.Itoa(n)) // FIXME: "n" can't be zero here, the logic is inconsistent with GetTagsList
}
v.Add("last", repositories[len(repositories)-1])
@@ -230,7 +234,7 @@ func GetRepositoryList(ctx *context.Context) {
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
-func InitiateUploadBlob(ctx *context.Context) {
+func PostBlobsUploads(ctx *context.Context) {
image := ctx.PathParam("image")
mount := ctx.FormTrim("mount")
@@ -312,14 +316,14 @@ func InitiateUploadBlob(ctx *context.Context) {
setResponseHeaders(ctx.Resp, &containerHeaders{
Location: fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", ctx.Package.Owner.LowerName, image, upload.ID),
- Range: "0-0",
UploadUUID: upload.ID,
Status: http.StatusAccepted,
})
}
-// https://docs.docker.com/registry/spec/api/#get-blob-upload
-func GetUploadBlob(ctx *context.Context) {
+// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
+func GetBlobsUpload(ctx *context.Context) {
+ image := ctx.PathParam("image")
uuid := ctx.PathParam("uuid")
upload, err := packages_model.GetBlobUploadByID(ctx, uuid)
@@ -332,15 +336,21 @@ func GetUploadBlob(ctx *context.Context) {
return
}
- setResponseHeaders(ctx.Resp, &containerHeaders{
- Range: fmt.Sprintf("0-%d", upload.BytesReceived),
+ // FIXME: undefined behavior when the uploaded content is empty: https://github.com/opencontainers/distribution-spec/issues/578
+ respHeaders := &containerHeaders{
+ Location: fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", ctx.Package.Owner.LowerName, image, upload.ID),
UploadUUID: upload.ID,
Status: http.StatusNoContent,
- })
+ }
+ if upload.BytesReceived > 0 {
+ respHeaders.Range = fmt.Sprintf("0-%d", upload.BytesReceived-1)
+ }
+ setResponseHeaders(ctx.Resp, respHeaders)
}
+// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
-func UploadBlob(ctx *context.Context) {
+func PatchBlobsUpload(ctx *context.Context) {
image := ctx.PathParam("image")
uploader, err := container_service.NewBlobUploader(ctx, ctx.PathParam("uuid"))
@@ -376,16 +386,19 @@ func UploadBlob(ctx *context.Context) {
return
}
- setResponseHeaders(ctx.Resp, &containerHeaders{
+ respHeaders := &containerHeaders{
Location: fmt.Sprintf("/v2/%s/%s/blobs/uploads/%s", ctx.Package.Owner.LowerName, image, uploader.ID),
- Range: fmt.Sprintf("0-%d", uploader.Size()-1),
UploadUUID: uploader.ID,
Status: http.StatusAccepted,
- })
+ }
+ if uploader.Size() > 0 {
+ respHeaders.Range = fmt.Sprintf("0-%d", uploader.Size()-1)
+ }
+ setResponseHeaders(ctx.Resp, respHeaders)
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
-func EndUploadBlob(ctx *context.Context) {
+func PutBlobsUpload(ctx *context.Context) {
image := ctx.PathParam("image")
digest := ctx.FormTrim("digest")
@@ -403,12 +416,7 @@ func EndUploadBlob(ctx *context.Context) {
}
return
}
- doClose := true
- defer func() {
- if doClose {
- uploader.Close()
- }
- }()
+ defer uploader.Close()
if ctx.Req.Body != nil {
if err := uploader.Append(ctx, ctx.Req.Body); err != nil {
@@ -441,11 +449,10 @@ func EndUploadBlob(ctx *context.Context) {
return
}
- if err := uploader.Close(); err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- doClose = false
+ // There was a strange bug: the "Close" fails with error "close .../tmp/package-upload/....: file already closed"
+ // AFAIK there should be no other "Close" call to the uploader between NewBlobUploader and this line.
+ // At least it's safe to call Close twice, so ignore the error.
+ _ = uploader.Close()
if err := container_service.RemoveBlobUploadByID(ctx, uploader.ID); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -460,7 +467,7 @@ func EndUploadBlob(ctx *context.Context) {
}
// https://docs.docker.com/registry/spec/api/#delete-blob-upload
-func CancelUploadBlob(ctx *context.Context) {
+func DeleteBlobsUpload(ctx *context.Context) {
uuid := ctx.PathParam("uuid")
_, err := packages_model.GetBlobUploadByID(ctx, uuid)
@@ -484,16 +491,15 @@ func CancelUploadBlob(ctx *context.Context) {
}
func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescriptor, error) {
- d := ctx.PathParam("digest")
-
- if digest.Digest(d).Validate() != nil {
+ d := digest.Digest(ctx.PathParam("digest"))
+ if d.Validate() != nil {
return nil, container_model.ErrContainerBlobNotExist
}
return workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Image: ctx.PathParam("image"),
- Digest: d,
+ Digest: string(d),
})
}
@@ -511,7 +517,7 @@ func HeadBlob(ctx *context.Context) {
setResponseHeaders(ctx.Resp, &containerHeaders{
ContentDigest: blob.Properties.GetByName(container_module.PropertyDigest),
- ContentLength: blob.Blob.Size,
+ ContentLength: optional.Some(blob.Blob.Size),
Status: http.StatusOK,
})
}
@@ -533,9 +539,8 @@ func GetBlob(ctx *context.Context) {
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-blobs
func DeleteBlob(ctx *context.Context) {
- d := ctx.PathParam("digest")
-
- if digest.Digest(d).Validate() != nil {
+ d := digest.Digest(ctx.PathParam("digest"))
+ if d.Validate() != nil {
apiErrorDefined(ctx, errBlobUnknown)
return
}
@@ -551,7 +556,7 @@ func DeleteBlob(ctx *context.Context) {
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
-func UploadManifest(ctx *context.Context) {
+func PutManifest(ctx *context.Context) {
reference := ctx.PathParam("reference")
mci := &manifestCreationInfo{
@@ -563,7 +568,7 @@ func UploadManifest(ctx *context.Context) {
IsTagged: digest.Digest(reference).Validate() != nil,
}
- if mci.IsTagged && !referencePattern.MatchString(reference) {
+ if mci.IsTagged && !globalVars().referencePattern.MatchString(reference) {
apiErrorDefined(ctx, errManifestInvalid.WithMessage("Tag is invalid"))
return
}
@@ -607,18 +612,18 @@ func UploadManifest(ctx *context.Context) {
}
func getBlobSearchOptionsFromContext(ctx *context.Context) (*container_model.BlobSearchOptions, error) {
- reference := ctx.PathParam("reference")
-
opts := &container_model.BlobSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Image: ctx.PathParam("image"),
IsManifest: true,
}
- if digest.Digest(reference).Validate() == nil {
- opts.Digest = reference
- } else if referencePattern.MatchString(reference) {
+ reference := ctx.PathParam("reference")
+ if d := digest.Digest(reference); d.Validate() == nil {
+ opts.Digest = string(d)
+ } else if globalVars().referencePattern.MatchString(reference) {
opts.Tag = reference
+ opts.OnlyLead = true
} else {
return nil, container_model.ErrContainerBlobNotExist
}
@@ -650,7 +655,7 @@ func HeadManifest(ctx *context.Context) {
setResponseHeaders(ctx.Resp, &containerHeaders{
ContentDigest: manifest.Properties.GetByName(container_module.PropertyDigest),
ContentType: manifest.Properties.GetByName(container_module.PropertyMediaType),
- ContentLength: manifest.Blob.Size,
+ ContentLength: optional.Some(manifest.Blob.Size),
Status: http.StatusOK,
})
}
@@ -705,7 +710,7 @@ func DeleteManifest(ctx *context.Context) {
func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) {
serveDirectReqParams := make(url.Values)
serveDirectReqParams.Set("response-content-type", pfd.Properties.GetByName(container_module.PropertyMediaType))
- s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob, serveDirectReqParams)
+ s, u, _, err := packages_service.OpenBlobForDownload(ctx, pfd.File, pfd.Blob, serveDirectReqParams)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -714,14 +719,14 @@ func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor)
headers := &containerHeaders{
ContentDigest: pfd.Properties.GetByName(container_module.PropertyDigest),
ContentType: pfd.Properties.GetByName(container_module.PropertyMediaType),
- ContentLength: pfd.Blob.Size,
+ ContentLength: optional.Some(pfd.Blob.Size),
Status: http.StatusOK,
}
if u != nil {
headers.Status = http.StatusTemporaryRedirect
headers.Location = u.String()
-
+ headers.ContentLength = optional.None[int64]() // do not set Content-Length for redirect responses
setResponseHeaders(ctx.Resp, headers)
return
}
@@ -735,7 +740,7 @@ func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor)
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery
-func GetTagList(ctx *context.Context) {
+func GetTagsList(ctx *context.Context) {
image := ctx.PathParam("image")
if _, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeContainer, image); err != nil {
@@ -780,7 +785,8 @@ func GetTagList(ctx *context.Context) {
})
}
-// FIXME: Workaround to be removed in v1.20
+// FIXME: Workaround to be removed in v1.20.
+// Update maybe we should never really remote it, as long as there is legacy data?
// https://github.com/go-gitea/gitea/issues/19586
func workaroundGetContainerBlob(ctx *context.Context, opts *container_model.BlobSearchOptions) (*packages_model.PackageFileDescriptor, error) {
blob, err := container_model.GetContainerBlob(ctx, opts)
diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go
index 26faa7b024..de40215aa7 100644
--- a/routers/api/packages/container/manifest.go
+++ b/routers/api/packages/container/manifest.go
@@ -10,11 +10,13 @@ import (
"io"
"os"
"strings"
+ "time"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
@@ -22,23 +24,12 @@ import (
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages"
+ container_service "code.gitea.io/gitea/services/packages/container"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
oci "github.com/opencontainers/image-spec/specs-go/v1"
)
-func isValidMediaType(mt string) bool {
- return strings.HasPrefix(mt, "application/vnd.docker.") || strings.HasPrefix(mt, "application/vnd.oci.")
-}
-
-func isImageManifestMediaType(mt string) bool {
- return strings.EqualFold(mt, oci.MediaTypeImageManifest) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.v2+json")
-}
-
-func isImageIndexMediaType(mt string) bool {
- return strings.EqualFold(mt, oci.MediaTypeImageIndex) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.list.v2+json")
-}
-
// manifestCreationInfo describes a manifest to create
type manifestCreationInfo struct {
MediaType string
@@ -55,71 +46,71 @@ func processManifest(ctx context.Context, mci *manifestCreationInfo, buf *packag
if err := json.NewDecoder(buf).Decode(&index); err != nil {
return "", err
}
-
if index.SchemaVersion != 2 {
return "", errUnsupported.WithMessage("Schema version is not supported")
}
-
if _, err := buf.Seek(0, io.SeekStart); err != nil {
return "", err
}
- if !isValidMediaType(mci.MediaType) {
+ if !container_module.IsMediaTypeValid(mci.MediaType) {
mci.MediaType = index.MediaType
- if !isValidMediaType(mci.MediaType) {
+ if !container_module.IsMediaTypeValid(mci.MediaType) {
return "", errManifestInvalid.WithMessage("MediaType not recognized")
}
}
- if isImageManifestMediaType(mci.MediaType) {
- return processImageManifest(ctx, mci, buf)
- } else if isImageIndexMediaType(mci.MediaType) {
- return processImageManifestIndex(ctx, mci, buf)
+ // .../container/manifest.go:453:createManifestBlob() [E] Error inserting package blob: Error 1062 (23000): Duplicate entry '..........' for key 'package_blob.UQE_package_blob_md5'
+ releaser, err := globallock.Lock(ctx, containerGlobalLockKey(mci.Owner.ID, mci.Image, "manifest"))
+ if err != nil {
+ return "", err
+ }
+ defer releaser()
+
+ if container_module.IsMediaTypeImageManifest(mci.MediaType) {
+ return processOciImageManifest(ctx, mci, buf)
+ } else if container_module.IsMediaTypeImageIndex(mci.MediaType) {
+ return processOciImageIndex(ctx, mci, buf)
}
return "", errManifestInvalid
}
-func processImageManifest(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (string, error) {
- manifestDigest := ""
-
- err := func() error {
- var manifest oci.Manifest
- if err := json.NewDecoder(buf).Decode(&manifest); err != nil {
- return err
- }
-
- if _, err := buf.Seek(0, io.SeekStart); err != nil {
- return err
- }
-
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- configDescriptor, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
- OwnerID: mci.Owner.ID,
- Image: mci.Image,
- Digest: string(manifest.Config.Digest),
- })
- if err != nil {
- return err
- }
+type processManifestTxRet struct {
+ pv *packages_model.PackageVersion
+ pb *packages_model.PackageBlob
+ created bool
+ digest string
+}
- configReader, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(configDescriptor.Blob.HashSHA256))
- if err != nil {
- return err
+func handleCreateManifestResult(ctx context.Context, err error, mci *manifestCreationInfo, contentStore *packages_module.ContentStore, txRet *processManifestTxRet) (string, error) {
+ if err != nil && txRet.created && txRet.pb != nil {
+ if err := contentStore.Delete(packages_module.BlobHash256Key(txRet.pb.HashSHA256)); err != nil {
+ log.Error("Error deleting package blob from content store: %v", err)
}
- defer configReader.Close()
+ return "", err
+ }
+ pd, err := packages_model.GetPackageDescriptor(ctx, txRet.pv)
+ if err != nil {
+ log.Error("Error getting package descriptor: %v", err) // ignore this error
+ } else {
+ notify_service.PackageCreate(ctx, mci.Creator, pd)
+ }
+ return txRet.digest, nil
+}
- metadata, err := container_module.ParseImageConfig(manifest.Config.MediaType, configReader)
- if err != nil {
- return err
- }
+func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (manifestDigest string, errRet error) {
+ manifest, configDescriptor, metadata, err := container_service.ParseManifestMetadata(ctx, buf, mci.Owner.ID, mci.Image)
+ if err != nil {
+ return "", err
+ }
+ if _, err = buf.Seek(0, io.SeekStart); err != nil {
+ return "", err
+ }
+ contentStore := packages_module.NewContentStore()
+ var txRet processManifestTxRet
+ err = db.WithTx(ctx, func(ctx context.Context) (err error) {
blobReferences := make([]*blobReference, 0, 1+len(manifest.Layers))
-
blobReferences = append(blobReferences, &blobReference{
Digest: manifest.Config.Digest,
MediaType: manifest.Config.MediaType,
@@ -150,78 +141,43 @@ func processImageManifest(ctx context.Context, mci *manifestCreationInfo, buf *p
return err
}
- uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_model.UploadVersion)
- if err != nil && err != packages_model.ErrPackageNotExist {
+ uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_module.UploadVersion)
+ if err != nil && !errors.Is(err, packages_model.ErrPackageNotExist) {
return err
}
for _, ref := range blobReferences {
- if err := createFileFromBlobReference(ctx, pv, uploadVersion, ref); err != nil {
+ if _, err = createFileFromBlobReference(ctx, pv, uploadVersion, ref); err != nil {
return err
}
}
+ txRet.pv = pv
+ txRet.pb, txRet.created, txRet.digest, err = createManifestBlob(ctx, contentStore, mci, pv, buf)
+ return err
+ })
- pb, created, digest, err := createManifestBlob(ctx, mci, pv, buf)
- removeBlob := false
- defer func() {
- if removeBlob {
- contentStore := packages_module.NewContentStore()
- if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
- log.Error("Error deleting package blob from content store: %v", err)
- }
- }
- }()
- if err != nil {
- removeBlob = created
- return err
- }
-
- if err := committer.Commit(); err != nil {
- removeBlob = created
- return err
- }
-
- if err := notifyPackageCreate(ctx, mci.Creator, pv); err != nil {
- return err
- }
-
- manifestDigest = digest
+ return handleCreateManifestResult(ctx, err, mci, contentStore, &txRet)
+}
- return nil
- }()
- if err != nil {
+func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (manifestDigest string, errRet error) {
+ var index oci.Index
+ if err := json.NewDecoder(buf).Decode(&index); err != nil {
+ return "", err
+ }
+ if _, err := buf.Seek(0, io.SeekStart); err != nil {
return "", err
}
- return manifestDigest, nil
-}
-
-func processImageManifestIndex(ctx context.Context, mci *manifestCreationInfo, buf *packages_module.HashedBuffer) (string, error) {
- manifestDigest := ""
-
- err := func() error {
- var index oci.Index
- if err := json.NewDecoder(buf).Decode(&index); err != nil {
- return err
- }
-
- if _, err := buf.Seek(0, io.SeekStart); err != nil {
- return err
- }
-
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
+ contentStore := packages_module.NewContentStore()
+ var txRet processManifestTxRet
+ err := db.WithTx(ctx, func(ctx context.Context) (err error) {
metadata := &container_module.Metadata{
Type: container_module.TypeOCI,
Manifests: make([]*container_module.Manifest, 0, len(index.Manifests)),
}
for _, manifest := range index.Manifests {
- if !isImageManifestMediaType(manifest.MediaType) {
+ if !container_module.IsMediaTypeImageManifest(manifest.MediaType) {
return errManifestInvalid
}
@@ -265,50 +221,12 @@ func processImageManifestIndex(ctx context.Context, mci *manifestCreationInfo, b
return err
}
- pb, created, digest, err := createManifestBlob(ctx, mci, pv, buf)
- removeBlob := false
- defer func() {
- if removeBlob {
- contentStore := packages_module.NewContentStore()
- if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
- log.Error("Error deleting package blob from content store: %v", err)
- }
- }
- }()
- if err != nil {
- removeBlob = created
- return err
- }
-
- if err := committer.Commit(); err != nil {
- removeBlob = created
- return err
- }
-
- if err := notifyPackageCreate(ctx, mci.Creator, pv); err != nil {
- return err
- }
-
- manifestDigest = digest
-
- return nil
- }()
- if err != nil {
- return "", err
- }
-
- return manifestDigest, nil
-}
-
-func notifyPackageCreate(ctx context.Context, doer *user_model.User, pv *packages_model.PackageVersion) error {
- pd, err := packages_model.GetPackageDescriptor(ctx, pv)
- if err != nil {
+ txRet.pv = pv
+ txRet.pb, txRet.created, txRet.digest, err = createManifestBlob(ctx, contentStore, mci, pv, buf)
return err
- }
-
- notify_service.PackageCreate(ctx, doer, pd)
+ })
- return nil
+ return handleCreateManifestResult(ctx, err, mci, contentStore, &txRet)
}
func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) {
@@ -349,24 +267,31 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
LowerVersion: strings.ToLower(mci.Reference),
MetadataJSON: string(metadataJSON),
}
- var pv *packages_model.PackageVersion
- if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
+ pv, err := packages_model.GetOrInsertVersion(ctx, _pv)
+ if err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
log.Error("Error inserting package: %v", err)
return nil, err
}
- if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
- return nil, err
- }
-
- // keep download count on overwrite
- _pv.DownloadCount = pv.DownloadCount
-
- if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
- if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
- log.Error("Error inserting package: %v", err)
- return nil, err
+ if container_module.IsMediaTypeImageIndex(mci.MediaType) {
+ if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) {
+ if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
+ return nil, err
+ }
+ // keep download count on overwriting
+ _pv.DownloadCount = pv.DownloadCount
+ if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
+ if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
+ log.Error("Error inserting package: %v", err)
+ return nil, err
+ }
+ }
+ } else {
+ err = packages_model.UpdateVersion(ctx, &packages_model.PackageVersion{ID: pv.ID, MetadataJSON: _pv.MetadataJSON})
+ if err != nil {
+ return nil, err
+ }
}
}
}
@@ -376,14 +301,20 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
}
if mci.IsTagged {
- if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
- log.Error("Error setting package version property: %v", err)
+ if err = packages_model.InsertOrUpdateProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
+ return nil, err
+ }
+ } else {
+ if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged); err != nil {
return nil, err
}
}
+
+ if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference); err != nil {
+ return nil, err
+ }
for _, manifest := range metadata.Manifests {
- if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
- log.Error("Error setting package version property: %v", err)
+ if _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
return nil, err
}
}
@@ -400,9 +331,9 @@ type blobReference struct {
IsLead bool
}
-func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *packages_model.PackageVersion, ref *blobReference) error {
+func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *packages_model.PackageVersion, ref *blobReference) (*packages_model.PackageFile, error) {
if ref.File.Blob.Size != ref.ExpectedSize {
- return errSizeInvalid
+ return nil, errSizeInvalid
}
if ref.Name == "" {
@@ -410,20 +341,21 @@ func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *package
}
pf := &packages_model.PackageFile{
- VersionID: pv.ID,
- BlobID: ref.File.Blob.ID,
- Name: ref.Name,
- LowerName: ref.Name,
- IsLead: ref.IsLead,
+ VersionID: pv.ID,
+ BlobID: ref.File.Blob.ID,
+ Name: ref.Name,
+ LowerName: ref.Name,
+ CompositeKey: string(ref.Digest),
+ IsLead: ref.IsLead,
}
var err error
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
if errors.Is(err, packages_model.ErrDuplicatePackageFile) {
// Skip this blob because the manifest contains the same filesystem layer multiple times.
- return nil
+ return pf, nil
}
log.Error("Error inserting package file: %v", err)
- return err
+ return nil, err
}
props := map[string]string{
@@ -433,21 +365,21 @@ func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *package
for name, value := range props {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, name, value); err != nil {
log.Error("Error setting package file property: %v", err)
- return err
+ return nil, err
}
}
- // Remove the file from the blob upload version
+ // Remove the ref file (old file) from the blob upload version
if uploadVersion != nil && ref.File.File != nil && uploadVersion.ID == ref.File.File.VersionID {
if err := packages_service.DeletePackageFile(ctx, ref.File.File); err != nil {
- return err
+ return nil, err
}
}
- return nil
+ return pf, nil
}
-func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *packages_model.PackageVersion, buf *packages_module.HashedBuffer) (*packages_model.PackageBlob, bool, string, error) {
+func createManifestBlob(ctx context.Context, contentStore *packages_module.ContentStore, mci *manifestCreationInfo, pv *packages_model.PackageVersion, buf *packages_module.HashedBuffer) (_ *packages_model.PackageBlob, created bool, manifestDigest string, _ error) {
pb, exists, err := packages_model.GetOrInsertBlob(ctx, packages_service.NewPackageBlob(buf))
if err != nil {
log.Error("Error inserting package blob: %v", err)
@@ -456,29 +388,48 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
// FIXME: Workaround to be removed in v1.20
// https://github.com/go-gitea/gitea/issues/19586
if exists {
- err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(pb.HashSHA256))
+ err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
exists = false
}
}
if !exists {
- contentStore := packages_module.NewContentStore()
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {
log.Error("Error saving package blob in content store: %v", err)
return nil, false, "", err
}
}
- manifestDigest := digestFromHashSummer(buf)
- err = createFileFromBlobReference(ctx, pv, nil, &blobReference{
+ manifestDigest = digestFromHashSummer(buf)
+ pf, err := createFileFromBlobReference(ctx, pv, nil, &blobReference{
Digest: digest.Digest(manifestDigest),
MediaType: mci.MediaType,
- Name: container_model.ManifestFilename,
+ Name: container_module.ManifestFilename,
File: &packages_model.PackageFileDescriptor{Blob: pb},
ExpectedSize: pb.Size,
IsLead: true,
})
+ if err != nil {
+ return nil, false, "", err
+ }
+ oldManifestFiles, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
+ OwnerID: mci.Owner.ID,
+ PackageType: packages_model.TypeContainer,
+ VersionID: pv.ID,
+ Query: container_module.ManifestFilename,
+ })
+ if err != nil {
+ return nil, false, "", err
+ }
+ for _, oldManifestFile := range oldManifestFiles {
+ if oldManifestFile.ID != pf.ID && oldManifestFile.IsLead {
+ err = packages_model.UpdateFile(ctx, &packages_model.PackageFile{ID: oldManifestFile.ID, IsLead: false}, []string{"is_lead"})
+ if err != nil {
+ return nil, false, "", err
+ }
+ }
+ }
return pb, !exists, manifestDigest, err
}
diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go
index 8a20072cb6..732acd215f 100644
--- a/routers/api/packages/cran/cran.go
+++ b/routers/api/packages/cran/cran.go
@@ -250,7 +250,7 @@ func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) {
return
}
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go
index fec34c91a6..346f71fa5d 100644
--- a/routers/api/packages/debian/debian.go
+++ b/routers/api/packages/debian/debian.go
@@ -59,7 +59,7 @@ func GetRepositoryFile(ctx *context.Context) {
key += "|" + component + "|" + architecture
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
@@ -106,7 +106,7 @@ func GetRepositoryFileByHash(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
@@ -210,7 +210,7 @@ func DownloadPackageFile(ctx *context.Context) {
name := ctx.PathParam("name")
version := ctx.PathParam("version")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index 0b5daa7334..db7aeace50 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -31,7 +31,7 @@ func apiError(ctx *context.Context, status int, obj any) {
// DownloadPackageFile serves the specific generic package.
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go
index bde29df739..89ec86bce9 100644
--- a/routers/api/packages/goproxy/goproxy.go
+++ b/routers/api/packages/goproxy/goproxy.go
@@ -106,7 +106,7 @@ func DownloadPackageFile(ctx *context.Context) {
return
}
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go
index fb12daaa46..39c34f4da4 100644
--- a/routers/api/packages/helm/helm.go
+++ b/routers/api/packages/helm/helm.go
@@ -122,7 +122,7 @@ func DownloadPackageFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pvs[0],
&packages_service.PackageFileInfo{
diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go
index 9089c2eccf..40a8ff8242 100644
--- a/routers/api/packages/maven/maven.go
+++ b/routers/api/packages/maven/maven.go
@@ -223,7 +223,7 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool
return
}
- s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb, nil)
+ s, u, _, err := packages_service.OpenBlobForDownload(ctx, pf, pb, nil)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 6ec46bcb36..1f09816d32 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -85,7 +85,7 @@ func DownloadPackageFile(ctx *context.Context) {
packageVersion := ctx.PathParam("version")
filename := ctx.PathParam("filename")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
@@ -132,7 +132,7 @@ func DownloadPackageFileByName(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pvs[0],
&packages_service.PackageFileInfo{
diff --git a/routers/api/packages/nuget/api_v2.go b/routers/api/packages/nuget/api_v2.go
index a726065ad0..801c60af13 100644
--- a/routers/api/packages/nuget/api_v2.go
+++ b/routers/api/packages/nuget/api_v2.go
@@ -246,21 +246,30 @@ type TypedValue[T any] struct {
}
type FeedEntryProperties struct {
- Version string `xml:"d:Version"`
- NormalizedVersion string `xml:"d:NormalizedVersion"`
Authors string `xml:"d:Authors"`
+ Copyright string `xml:"d:Copyright,omitempty"`
+ Created TypedValue[time.Time] `xml:"d:Created"`
Dependencies string `xml:"d:Dependencies"`
Description string `xml:"d:Description"`
- VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"`
+ DevelopmentDependency TypedValue[bool] `xml:"d:DevelopmentDependency"`
DownloadCount TypedValue[int64] `xml:"d:DownloadCount"`
- PackageSize TypedValue[int64] `xml:"d:PackageSize"`
- Created TypedValue[time.Time] `xml:"d:Created"`
+ ID string `xml:"d:Id"`
+ IconURL string `xml:"d:IconUrl,omitempty"`
+ Language string `xml:"d:Language,omitempty"`
LastUpdated TypedValue[time.Time] `xml:"d:LastUpdated"`
- Published TypedValue[time.Time] `xml:"d:Published"`
+ LicenseURL string `xml:"d:LicenseUrl,omitempty"`
+ MinClientVersion string `xml:"d:MinClientVersion,omitempty"`
+ NormalizedVersion string `xml:"d:NormalizedVersion"`
+ Owners string `xml:"d:Owners,omitempty"`
+ PackageSize TypedValue[int64] `xml:"d:PackageSize"`
ProjectURL string `xml:"d:ProjectUrl,omitempty"`
+ Published TypedValue[time.Time] `xml:"d:Published"`
ReleaseNotes string `xml:"d:ReleaseNotes,omitempty"`
RequireLicenseAcceptance TypedValue[bool] `xml:"d:RequireLicenseAcceptance"`
- Title string `xml:"d:Title"`
+ Tags string `xml:"d:Tags,omitempty"`
+ Title string `xml:"d:Title,omitempty"`
+ Version string `xml:"d:Version"`
+ VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"`
}
type FeedEntry struct {
@@ -353,21 +362,30 @@ func createEntry(l *linkBuilder, pd *packages_model.PackageDescriptor, withNames
Author: metadata.Authors,
Content: content,
Properties: &FeedEntryProperties{
- Version: pd.Version.Version,
- NormalizedVersion: pd.Version.Version,
Authors: metadata.Authors,
+ Copyright: metadata.Copyright,
+ Created: createdValue,
Dependencies: buildDependencyString(metadata),
Description: metadata.Description,
- VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
+ DevelopmentDependency: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.DevelopmentDependency},
DownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
- PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()},
- Created: createdValue,
+ ID: pd.Package.Name,
+ IconURL: metadata.IconURL,
+ Language: metadata.Language,
LastUpdated: createdValue,
- Published: createdValue,
+ LicenseURL: metadata.LicenseURL,
+ MinClientVersion: metadata.MinClientVersion,
+ NormalizedVersion: pd.Version.Version,
+ Owners: metadata.Owners,
+ PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()},
ProjectURL: metadata.ProjectURL,
+ Published: createdValue,
ReleaseNotes: metadata.ReleaseNotes,
RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance},
- Title: pd.Package.Name,
+ Tags: metadata.Tags,
+ Title: metadata.Title,
+ Version: pd.Version.Version,
+ VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
},
}
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index fa5067a278..92d62d90b1 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -36,7 +36,7 @@ func apiError(ctx *context.Context, status int, obj any) {
})
}
-func xmlResponse(ctx *context.Context, status int, obj any) { //nolint:unparam
+func xmlResponse(ctx *context.Context, status int, obj any) { //nolint:unparam // status is always StatusOK
ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
ctx.Resp.WriteHeader(status)
if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
@@ -405,7 +405,7 @@ func DownloadPackageFile(ctx *context.Context) {
packageVersion := ctx.PathParam("version")
filename := ctx.PathParam("filename")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
@@ -669,7 +669,7 @@ func DownloadSymbolFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+ s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err)
diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go
index e7b07aefd0..4bd36e94b6 100644
--- a/routers/api/packages/pub/pub.go
+++ b/routers/api/packages/pub/pub.go
@@ -274,7 +274,7 @@ func DownloadPackageFile(ctx *context.Context) {
pf := pd.Files[0].File
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go
index 199f4e7478..9b5ae6c89d 100644
--- a/routers/api/packages/pypi/pypi.go
+++ b/routers/api/packages/pypi/pypi.go
@@ -82,7 +82,7 @@ func DownloadPackageFile(ctx *context.Context) {
packageVersion := ctx.PathParam("version")
filename := ctx.PathParam("filename")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go
index a00a61c079..938c35341d 100644
--- a/routers/api/packages/rpm/rpm.go
+++ b/routers/api/packages/rpm/rpm.go
@@ -96,7 +96,7 @@ func GetRepositoryFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
@@ -220,7 +220,7 @@ func DownloadPackageFile(ctx *context.Context) {
name := ctx.PathParam("name")
version := ctx.PathParam("version")
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go
index de8c7ef3ed..774d5520fd 100644
--- a/routers/api/packages/rubygems/rubygems.go
+++ b/routers/api/packages/rubygems/rubygems.go
@@ -14,6 +14,7 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
@@ -177,7 +178,7 @@ func DownloadPackageFile(ctx *context.Context) {
return
}
- s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pvs[0],
&packages_service.PackageFileInfo{
@@ -309,7 +310,7 @@ func GetPackageInfo(ctx *context.Context) {
apiError(ctx, http.StatusNotFound, nil)
return
}
- infoContent, err := makePackageInfo(ctx, versions)
+ infoContent, err := makePackageInfo(ctx, versions, cache.NewEphemeralCache())
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -317,7 +318,7 @@ func GetPackageInfo(ctx *context.Context) {
ctx.PlainText(http.StatusOK, infoContent)
}
-// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems.
+// GetAllPackagesVersions returns a custom text-based format containing information about all versions of all rubygems.
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
func GetAllPackagesVersions(ctx *context.Context) {
packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems)
@@ -326,6 +327,7 @@ func GetAllPackagesVersions(ctx *context.Context) {
return
}
+ ephemeralCache := cache.NewEphemeralCache()
out := &strings.Builder{}
out.WriteString("---\n")
for _, pkg := range packages {
@@ -338,7 +340,7 @@ func GetAllPackagesVersions(ctx *context.Context) {
continue
}
- info, err := makePackageInfo(ctx, versions)
+ info, err := makePackageInfo(ctx, versions, ephemeralCache)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -348,7 +350,14 @@ func GetAllPackagesVersions(ctx *context.Context) {
_, _ = fmt.Fprintf(out, "%s ", pkg.Name)
for i, v := range versions {
sep := util.Iif(i == len(versions)-1, "", ",")
- _, _ = fmt.Fprintf(out, "%s%s", v.Version, sep)
+ pd, err := packages_model.GetPackageDescriptorWithCache(ctx, v, ephemeralCache)
+ if errors.Is(err, util.ErrNotExist) {
+ continue
+ } else if err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
+ return
+ }
+ writePackageVersionForList(pd.Metadata, v.Version, sep, out)
}
_, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info)))
}
@@ -356,6 +365,16 @@ func GetAllPackagesVersions(ctx *context.Context) {
ctx.PlainText(http.StatusOK, out.String())
}
+func writePackageVersionForList(metadata any, version, sep string, out *strings.Builder) {
+ if metadata, _ := metadata.(*rubygems_module.Metadata); metadata != nil && metadata.Platform != "" && metadata.Platform != "ruby" {
+ // VERSION_PLATFORM (see comment above in GetAllPackagesVersions)
+ _, _ = fmt.Fprintf(out, "%s_%s%s", version, metadata.Platform, sep)
+ } else {
+ // VERSION only
+ _, _ = fmt.Fprintf(out, "%s%s", version, sep)
+ }
+}
+
func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) {
out.WriteString(prefix)
if len(reqs) == 0 {
@@ -367,11 +386,21 @@ func writePackageVersionRequirements(prefix string, reqs []rubygems_module.Versi
}
}
-func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) {
+func writePackageVersionForDependency(version, platform string, out *strings.Builder) {
+ if platform != "" && platform != "ruby" {
+ // VERSION-PLATFORM (see comment below in makePackageVersionDependency)
+ _, _ = fmt.Fprintf(out, "%s-%s ", version, platform)
+ } else {
+ // VERSION only
+ _, _ = fmt.Fprintf(out, "%s ", version)
+ }
+}
+
+func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
// format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...]
// DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT]
// REQUIREMENT: KEY:VALUE (always contains "checksum")
- pd, err := packages_model.GetPackageDescriptor(ctx, version)
+ pd, err := packages_model.GetPackageDescriptorWithCache(ctx, version, c)
if err != nil {
return "", err
}
@@ -388,8 +417,7 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model.
}
buf := &strings.Builder{}
- buf.WriteString(version.Version)
- buf.WriteByte(' ')
+ writePackageVersionForDependency(version.Version, metadata.Platform, buf)
for i, dep := range metadata.RuntimeDependencies {
sep := util.Iif(i == 0, "", ",")
writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf)
@@ -404,10 +432,10 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model.
return buf.String(), nil
}
-func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) {
+func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
ret := "---\n"
for _, v := range versions {
- dep, err := makePackageVersionDependency(ctx, v)
+ dep, err := makePackageVersionDependency(ctx, v, c)
if err != nil {
return "", err
}
diff --git a/routers/api/packages/rubygems/rubygems_test.go b/routers/api/packages/rubygems/rubygems_test.go
new file mode 100644
index 0000000000..a07e12a7d3
--- /dev/null
+++ b/routers/api/packages/rubygems/rubygems_test.go
@@ -0,0 +1,41 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package rubygems
+
+import (
+ "strings"
+ "testing"
+
+ rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWritePackageVersion(t *testing.T) {
+ buf := &strings.Builder{}
+
+ writePackageVersionForList(nil, "1.0", " ", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForList(&rubygems_module.Metadata{Platform: "ruby"}, "1.0", " ", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForList(&rubygems_module.Metadata{Platform: "linux"}, "1.0", " ", buf)
+ assert.Equal(t, "1.0_linux ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForDependency("1.0", "", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForDependency("1.0", "ruby", buf)
+ assert.Equal(t, "1.0 ", buf.String())
+ buf.Reset()
+
+ writePackageVersionForDependency("1.0", "os", buf)
+ assert.Equal(t, "1.0-os ", buf.String())
+ buf.Reset()
+}
diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go
index 47439c4c3b..bf542f33a7 100644
--- a/routers/api/packages/swift/swift.go
+++ b/routers/api/packages/swift/swift.go
@@ -429,7 +429,7 @@ func DownloadPackageFile(ctx *context.Context) {
pf := pd.Files[0].File
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go
index 3afaa5de1f..9eb67e5397 100644
--- a/routers/api/packages/vagrant/vagrant.go
+++ b/routers/api/packages/vagrant/vagrant.go
@@ -218,7 +218,7 @@ func UploadPackageFile(ctx *context.Context) {
}
func DownloadPackageFile(ctx *context.Context) {
- s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+ s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
diff --git a/routers/api/v1/admin/action.go b/routers/api/v1/admin/action.go
new file mode 100644
index 0000000000..2fbb8e1a95
--- /dev/null
+++ b/routers/api/v1/admin/action.go
@@ -0,0 +1,93 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package admin
+
+import (
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
+)
+
+// ListWorkflowJobs Lists all jobs
+func ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /admin/actions/jobs admin listAdminWorkflowJobs
+ // ---
+ // summary: Lists all jobs
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJobsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListJobs(ctx, 0, 0, 0)
+}
+
+// ListWorkflowRuns Lists all runs
+func ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /admin/actions/runs admin listAdminWorkflowRuns
+ // ---
+ // summary: Lists all runs
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: event
+ // in: query
+ // description: workflow event name
+ // type: string
+ // required: false
+ // - name: branch
+ // in: query
+ // description: workflow branch
+ // type: string
+ // required: false
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: actor
+ // in: query
+ // description: triggered by user
+ // type: string
+ // required: false
+ // - name: head_sha
+ // in: query
+ // description: triggering sha of the workflow run
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowRunsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListRuns(ctx, 0, 0)
+}
diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go
index 8808a1587d..c3473372f2 100644
--- a/routers/api/v1/admin/org.go
+++ b/routers/api/v1/admin/org.go
@@ -29,7 +29,7 @@ func CreateOrg(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of the user that will own the created organization
+ // description: username of the user who will own the created organization
// type: string
// required: true
// - name: organization
@@ -101,7 +101,7 @@ func GetAllOrgs(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
OrderBy: db.SearchOrderByAlphabetically,
diff --git a/routers/api/v1/admin/repo.go b/routers/api/v1/admin/repo.go
index c119d5390a..12a78c9c4b 100644
--- a/routers/api/v1/admin/repo.go
+++ b/routers/api/v1/admin/repo.go
@@ -22,7 +22,7 @@ func CreateRepo(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of the user. This user will own the created repository
+ // description: username of the user who will own the created repository
// type: string
// required: true
// - name: repository
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 3ba77604ec..8a267cc418 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -175,7 +175,7 @@ func EditUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to edit
+ // description: username of the user whose data is to be edited
// type: string
// required: true
// - name: body
@@ -239,7 +239,7 @@ func EditUser(ctx *context.APIContext) {
Location: optional.FromPtr(form.Location),
Description: optional.FromPtr(form.Description),
IsActive: optional.FromPtr(form.Active),
- IsAdmin: optional.FromPtr(form.Admin),
+ IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
AllowGitHook: optional.FromPtr(form.AllowGitHook),
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
@@ -272,7 +272,7 @@ func DeleteUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to delete
+ // description: username of the user to delete
// type: string
// required: true
// - name: purge
@@ -328,7 +328,7 @@ func CreatePublicKey(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user who is to receive a public key
// type: string
// required: true
// - name: key
@@ -358,7 +358,7 @@ func DeleteUserPublicKey(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose public key is to be deleted
// type: string
// required: true
// - name: id
@@ -405,7 +405,7 @@ func SearchUsers(ctx *context.APIContext) {
// format: int64
// - name: login_name
// in: query
- // description: user's login name to search for
+ // description: identifier of the user, provided by the external authenticator
// type: string
// - name: page
// in: query
@@ -423,7 +423,7 @@ func SearchUsers(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
LoginName: ctx.FormTrim("login_name"),
@@ -456,7 +456,7 @@ func RenameUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: existing username of user
+ // description: current username of the user
// type: string
// required: true
// - name: body
diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go
index 6d9665a72b..ce32f455b0 100644
--- a/routers/api/v1/admin/user_badge.go
+++ b/routers/api/v1/admin/user_badge.go
@@ -22,7 +22,7 @@ func ListUserBadges(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose badges are to be listed
// type: string
// required: true
// responses:
@@ -53,7 +53,7 @@ func AddUserBadges(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user to whom a badge is to be added
// type: string
// required: true
// - name: body
@@ -87,7 +87,7 @@ func DeleteUserBadges(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose badge is to be deleted
// type: string
// required: true
// - name: body
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index b98863b418..4a4bf12657 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -228,7 +228,7 @@ func repoAssignment() func(ctx *context.APIContext) {
}
}
- if !ctx.Repo.Permission.HasAnyUnitAccess() {
+ if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() {
ctx.APIErrorNotFound()
return
}
@@ -455,15 +455,6 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
}
}
-// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
-func reqRepoBranchWriter(ctx *context.APIContext) {
- options, ok := web.GetForm(ctx).(api.FileOptionInterface)
- if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
- ctx.APIError(http.StatusForbidden, "user should have a permission to write to this branch")
- return
- }
-}
-
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
@@ -744,9 +735,17 @@ func mustEnableWiki(ctx *context.APIContext) {
}
}
+// FIXME: for consistency, maybe most mustNotBeArchived checks should be replaced with mustEnableEditor
func mustNotBeArchived(ctx *context.APIContext) {
if ctx.Repo.Repository.IsArchived {
- ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString()))
+ ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.FullName()))
+ return
+ }
+}
+
+func mustEnableEditor(ctx *context.APIContext) {
+ if !ctx.Repo.Repository.CanEnableEditor() {
+ ctx.APIError(http.StatusLocked, fmt.Errorf("%s is not allowed to edit", ctx.Repo.Repository.FullName()))
return
}
}
@@ -942,6 +941,8 @@ func Routes() *web.Router {
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
})
+ m.Get("/runs", reqToken(), reqChecker, act.ListWorkflowRuns)
+ m.Get("/jobs", reqToken(), reqChecker, act.ListWorkflowJobs)
})
}
@@ -971,7 +972,8 @@ func Routes() *web.Router {
// Misc (public accessible)
m.Group("", func() {
m.Get("/version", misc.Version)
- m.Get("/signing-key.gpg", misc.SigningKey)
+ m.Get("/signing-key.gpg", misc.SigningKeyGPG)
+ m.Get("/signing-key.pub", misc.SigningKeySSH)
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
@@ -1077,6 +1079,9 @@ func Routes() *web.Router {
m.Get("/{runner_id}", reqToken(), user.GetRunner)
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
})
+
+ m.Get("/runs", reqToken(), user.ListWorkflowRuns)
+ m.Get("/jobs", reqToken(), user.ListWorkflowJobs)
})
m.Get("/followers", user.ListMyFollowers)
@@ -1201,6 +1206,7 @@ func Routes() *web.Router {
}, context.ReferencesGitRepo(), reqToken(), reqRepoReader(unit.TypeActions))
m.Group("/actions/jobs", func() {
+ m.Get("/{job_id}", repo.GetWorkflowJob)
m.Get("/{job_id}/logs", repo.DownloadActionsRunJobLogs)
}, reqToken(), reqRepoReader(unit.TypeActions))
@@ -1241,7 +1247,7 @@ func Routes() *web.Router {
}, reqToken())
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
- m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
+ m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
m.Combo("/forks").Get(repo.ListForks).
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
@@ -1279,7 +1285,14 @@ func Routes() *web.Router {
}, reqToken(), reqAdmin())
m.Group("/actions", func() {
m.Get("/tasks", repo.ListActionTasks)
- m.Get("/runs/{run}/artifacts", repo.GetArtifactsOfRun)
+ m.Group("/runs", func() {
+ m.Group("/{run}", func() {
+ m.Get("", repo.GetWorkflowRun)
+ m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
+ m.Get("/jobs", repo.ListWorkflowRunJobs)
+ m.Get("/artifacts", repo.GetArtifactsOfRun)
+ })
+ })
m.Get("/artifacts", repo.GetArtifacts)
m.Group("/artifacts/{artifact_id}", func() {
m.Get("", repo.GetArtifact)
@@ -1410,21 +1423,29 @@ func Routes() *web.Router {
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
m.Get("/notes/{sha}", repo.GetNote)
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
- m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
m.Group("/contents", func() {
m.Get("", repo.GetContentsList)
m.Get("/*", repo.GetContents)
- m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
- m.Group("/*", func() {
- m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
- m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
- m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
- }, reqToken())
+ m.Group("", func() {
+ // "change file" operations, need permission to write to the target branch provided by the form
+ m.Post("", bind(api.ChangeFilesOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ChangeFiles)
+ m.Group("/*", func() {
+ m.Post("", bind(api.CreateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.CreateFile)
+ m.Put("", bind(api.UpdateFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.UpdateFile)
+ m.Delete("", bind(api.DeleteFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.DeleteFile)
+ })
+ m.Post("/diffpatch", bind(api.ApplyDiffPatchFileOptions{}), repo.ReqChangeRepoFileOptionsAndCheck, repo.ApplyDiffPatch)
+ }, mustEnableEditor, reqToken())
+ }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
+ m.Group("/contents-ext", func() {
+ m.Get("", repo.GetContentsExt)
+ m.Get("/*", repo.GetContentsExt)
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())
m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()).
Get(repo.GetFileContentsGet).
- Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // POST method requires "write" permission, so we also support "GET" method above
- m.Get("/signing-key.gpg", misc.SigningKey)
+ Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // the POST method requires "write" permission, so we also support "GET" method above
+ m.Get("/signing-key.gpg", misc.SigningKeyGPG)
+ m.Get("/signing-key.pub", misc.SigningKeySSH)
m.Group("/topics", func() {
m.Combo("").Get(repo.ListTopics).
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
@@ -1445,7 +1466,7 @@ func Routes() *web.Router {
m.Delete("", repo.DeleteAvatar)
}, reqAdmin(), reqToken())
- m.Get("/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
+ m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
}, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
@@ -1729,11 +1750,15 @@ func Routes() *web.Router {
Patch(bind(api.EditHookOption{}), admin.EditHook).
Delete(admin.DeleteHook)
})
- m.Group("/actions/runners", func() {
- m.Get("", admin.ListRunners)
- m.Post("/registration-token", admin.CreateRegistrationToken)
- m.Get("/{runner_id}", admin.GetRunner)
- m.Delete("/{runner_id}", admin.DeleteRunner)
+ m.Group("/actions", func() {
+ m.Group("/runners", func() {
+ m.Get("", admin.ListRunners)
+ m.Post("/registration-token", admin.CreateRegistrationToken)
+ m.Get("/{runner_id}", admin.GetRunner)
+ m.Delete("/{runner_id}", admin.DeleteRunner)
+ })
+ m.Get("/runs", admin.ListWorkflowRuns)
+ m.Get("/jobs", admin.ListWorkflowJobs)
})
m.Group("/runners", func() {
m.Get("/registration-token", admin.GetRegistrationToken)
diff --git a/routers/api/v1/misc/markup.go b/routers/api/v1/misc/markup.go
index 0cd4b8c5c5..909310b4c8 100644
--- a/routers/api/v1/misc/markup.go
+++ b/routers/api/v1/misc/markup.go
@@ -42,7 +42,7 @@ func Markup(ctx *context.APIContext) {
return
}
- mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+ mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck // form.Wiki is deprecated
common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, form.FilePath)
}
@@ -73,7 +73,7 @@ func Markdown(ctx *context.APIContext) {
return
}
- mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+ mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck // form.Wiki is deprecated
common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, "")
}
diff --git a/routers/api/v1/misc/signing.go b/routers/api/v1/misc/signing.go
index 667396e39c..db70e04b8f 100644
--- a/routers/api/v1/misc/signing.go
+++ b/routers/api/v1/misc/signing.go
@@ -4,14 +4,35 @@
package misc
import (
- "fmt"
-
+ "code.gitea.io/gitea/modules/git"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
)
-// SigningKey returns the public key of the default signing key if it exists
-func SigningKey(ctx *context.APIContext) {
+func getSigningKey(ctx *context.APIContext, expectedFormat string) {
+ // if the handler is in the repo's route group, get the repo's signing key
+ // otherwise, get the global signing key
+ path := ""
+ if ctx.Repo != nil && ctx.Repo.Repository != nil {
+ path = ctx.Repo.Repository.RepoPath()
+ }
+ content, format, err := asymkey_service.PublicSigningKey(ctx, path)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ if format == "" {
+ ctx.APIErrorNotFound("no signing key")
+ return
+ } else if format != expectedFormat {
+ ctx.APIErrorNotFound("signing key format is " + format)
+ return
+ }
+ _, _ = ctx.Write([]byte(content))
+}
+
+// SigningKeyGPG returns the public key of the default signing key if it exists
+func SigningKeyGPG(ctx *context.APIContext) {
// swagger:operation GET /signing-key.gpg miscellaneous getSigningKey
// ---
// summary: Get default signing-key.gpg
@@ -44,19 +65,42 @@ func SigningKey(ctx *context.APIContext) {
// description: "GPG armored public key"
// schema:
// type: string
+ getSigningKey(ctx, git.SigningKeyFormatOpenPGP)
+}
- path := ""
- if ctx.Repo != nil && ctx.Repo.Repository != nil {
- path = ctx.Repo.Repository.RepoPath()
- }
+// SigningKeySSH returns the public key of the default signing key if it exists
+func SigningKeySSH(ctx *context.APIContext) {
+ // swagger:operation GET /signing-key.pub miscellaneous getSigningKeySSH
+ // ---
+ // summary: Get default signing-key.pub
+ // produces:
+ // - text/plain
+ // responses:
+ // "200":
+ // description: "ssh public key"
+ // schema:
+ // type: string
- content, err := asymkey_service.PublicSigningKey(ctx, path)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- _, err = ctx.Write([]byte(content))
- if err != nil {
- ctx.APIErrorInternal(fmt.Errorf("Error writing key content %w", err))
- }
+ // swagger:operation GET /repos/{owner}/{repo}/signing-key.pub repository repoSigningKeySSH
+ // ---
+ // summary: Get signing-key.pub for given repository
+ // produces:
+ // - text/plain
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // description: "ssh public key"
+ // schema:
+ // type: string
+ getSigningKey(ctx, git.SigningKeyFormatSSH)
}
diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go
index 700a5ef8ea..3ae5e60585 100644
--- a/routers/api/v1/org/action.go
+++ b/routers/api/v1/org/action.go
@@ -384,13 +384,13 @@ func (Action) CreateVariable(ctx *context.APIContext) {
// "$ref": "#/definitions/CreateVariableOption"
// responses:
// "201":
- // description: response when creating an org-level variable
- // "204":
- // description: response when creating an org-level variable
+ // description: successfully created the org-level variable
// "400":
// "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
+ // "409":
+ // description: variable name already exists.
+ // "500":
+ // "$ref": "#/responses/error"
opt := web.GetForm(ctx).(*api.CreateVariableOption)
@@ -419,7 +419,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
return
}
- ctx.Status(http.StatusNoContent)
+ ctx.Status(http.StatusCreated)
}
// UpdateVariable update an org-level variable
@@ -570,6 +570,96 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
}
+func (Action) ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/jobs organization getOrgWorkflowJobs
+ // ---
+ // summary: Get org-level workflow jobs
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJobsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0)
+}
+
+func (Action) ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/runs organization getOrgWorkflowRuns
+ // ---
+ // summary: Get org-level workflow runs
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: event
+ // in: query
+ // description: workflow event name
+ // type: string
+ // required: false
+ // - name: branch
+ // in: query
+ // description: workflow branch
+ // type: string
+ // required: false
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: actor
+ // in: query
+ // description: triggered by user
+ // type: string
+ // required: false
+ // - name: head_sha
+ // in: query
+ // description: triggering sha of the workflow run
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowRunsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
+}
+
var _ actions_service.API = new(Action)
// Action implements actions_service.API
diff --git a/routers/api/v1/org/block.go b/routers/api/v1/org/block.go
index 69a5222a20..6b2f3dc615 100644
--- a/routers/api/v1/org/block.go
+++ b/routers/api/v1/org/block.go
@@ -47,7 +47,7 @@ func CheckUserBlock(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: user to check
+ // description: username of the user to check
// type: string
// required: true
// responses:
@@ -71,7 +71,7 @@ func BlockUser(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: user to block
+ // description: username of the user to block
// type: string
// required: true
// - name: note
@@ -101,7 +101,7 @@ func UnblockUser(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: user to unblock
+ // description: username of the user to unblock
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go
index a1875a7886..1c12b0cc94 100644
--- a/routers/api/v1/org/member.go
+++ b/routers/api/v1/org/member.go
@@ -133,7 +133,7 @@ func IsMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user to check for an organization membership
// type: string
// required: true
// responses:
@@ -186,7 +186,7 @@ func IsPublicMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user to check for a public organization membership
// type: string
// required: true
// responses:
@@ -240,7 +240,7 @@ func PublicizeMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user whose membership is to be publicized
// type: string
// required: true
// responses:
@@ -282,7 +282,7 @@ func ConcealMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user whose membership is to be concealed
// type: string
// required: true
// responses:
@@ -324,7 +324,7 @@ func DeleteMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user
+ // description: username of the user to remove from the organization
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index c9208f4757..05744ba155 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -26,12 +26,10 @@ import (
func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
listOptions := utils.GetListOptions(ctx)
- showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID)
-
opts := organization.FindOrgOptions{
- ListOptions: listOptions,
- UserID: u.ID,
- IncludePrivate: showPrivate,
+ ListOptions: listOptions,
+ UserID: u.ID,
+ IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
}
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
if err != nil {
@@ -84,7 +82,7 @@ func ListUserOrgs(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose organizations are to be listed
// type: string
// required: true
// - name: page
@@ -114,7 +112,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose permissions are to be obtained
// type: string
// required: true
// - name: org
@@ -201,7 +199,7 @@ func GetAll(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
- publicOrgs, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
ListOptions: listOptions,
Type: user_model.UserTypeOrganization,
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index 71c21f2dde..1a1710750a 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -426,7 +426,7 @@ func GetTeamMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the member to list
+ // description: username of the user whose data is to be listed
// type: string
// required: true
// responses:
@@ -467,7 +467,7 @@ func AddTeamMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user to add
+ // description: username of the user to add to a team
// type: string
// required: true
// responses:
@@ -509,7 +509,7 @@ func RemoveTeamMember(ctx *context.APIContext) {
// required: true
// - name: username
// in: path
- // description: username of the user to remove
+ // description: username of the user to remove from a team
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go
index 6aef529f98..a57db015f0 100644
--- a/routers/api/v1/repo/action.go
+++ b/routers/api/v1/repo/action.go
@@ -339,12 +339,12 @@ func (Action) CreateVariable(ctx *context.APIContext) {
// responses:
// "201":
// description: response when creating a repo-level variable
- // "204":
- // description: response when creating a repo-level variable
// "400":
// "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
+ // "409":
+ // description: variable name already exists.
+ // "500":
+ // "$ref": "#/responses/error"
opt := web.GetForm(ctx).(*api.CreateVariableOption)
@@ -373,7 +373,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
return
}
- ctx.Status(http.StatusNoContent)
+ ctx.Status(http.StatusCreated)
}
// UpdateVariable update a repo-level variable
@@ -650,6 +650,114 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
}
+// GetWorkflowRunJobs Lists all jobs for a workflow run.
+func (Action) ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
+ // ---
+ // summary: Lists all jobs for a repository
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJobsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+
+ shared.ListJobs(ctx, 0, repoID, 0)
+}
+
+// ListWorkflowRuns Lists all runs for a repository run.
+func (Action) ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runs repository getWorkflowRuns
+ // ---
+ // summary: Lists all runs for a repository run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: event
+ // in: query
+ // description: workflow event name
+ // type: string
+ // required: false
+ // - name: branch
+ // in: query
+ // description: workflow branch
+ // type: string
+ // required: false
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: actor
+ // in: query
+ // description: triggered by user
+ // type: string
+ // required: false
+ // - name: head_sha
+ // in: query
+ // description: triggering sha of the workflow run
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ArtifactsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+
+ shared.ListRuns(ctx, 0, repoID)
+}
+
var _ actions_service.API = new(Action)
// Action implements actions_service.API
@@ -756,7 +864,7 @@ func ActionsListRepositoryWorkflows(ctx *context.APIContext) {
// "500":
// "$ref": "#/responses/error"
- workflows, err := actions_service.ListActionWorkflows(ctx)
+ workflows, err := convert.ListActionWorkflows(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -802,7 +910,7 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
// "$ref": "#/responses/error"
workflowID := ctx.PathParam("workflow_id")
- workflow, err := actions_service.GetActionWorkflow(ctx, workflowID)
+ workflow, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.APIError(http.StatusNotFound, err)
@@ -992,6 +1100,157 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent)
}
+// GetWorkflowRun Gets a specific workflow run.
+func GetWorkflowRun(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
+ // ---
+ // summary: Gets a specific workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: run
+ // in: path
+ // description: id of the run
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowRun"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ runID := ctx.PathParamInt64("run")
+ job, _, err := db.GetByID[actions_model.ActionRun](ctx, runID)
+
+ if err != nil || job.RepoID != ctx.Repo.Repository.ID {
+ ctx.APIError(http.StatusNotFound, util.ErrNotExist)
+ }
+
+ convertedArtifact, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, job)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convertedArtifact)
+}
+
+// ListWorkflowRunJobs Lists all jobs for a workflow run.
+func ListWorkflowRunJobs(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/jobs repository listWorkflowRunJobs
+ // ---
+ // summary: Lists all jobs for a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: run
+ // in: path
+ // description: runid of the workflow run
+ // type: integer
+ // required: true
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJobsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repoID := ctx.Repo.Repository.ID
+
+ runID := ctx.PathParamInt64("run")
+
+ // Avoid the list all jobs functionality for this api route to be used with a runID == 0.
+ if runID <= 0 {
+ ctx.APIError(http.StatusBadRequest, util.NewInvalidArgumentErrorf("runID must be a positive integer"))
+ return
+ }
+
+ // runID is used as an additional filter next to repoID to ensure that we only list jobs for the specified repoID and runID.
+ // no additional checks for runID are needed here
+ shared.ListJobs(ctx, 0, repoID, runID)
+}
+
+// GetWorkflowJob Gets a specific workflow job for a workflow run.
+func GetWorkflowJob(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id} repository getWorkflowJob
+ // ---
+ // summary: Gets a specific workflow job for a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: job_id
+ // in: path
+ // description: id of the job
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJob"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ jobID := ctx.PathParamInt64("job_id")
+ job, _, err := db.GetByID[actions_model.ActionRunJob](ctx, jobID)
+
+ if err != nil || job.RepoID != ctx.Repo.Repository.ID {
+ ctx.APIError(http.StatusNotFound, util.ErrNotExist)
+ }
+
+ convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, job)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.JSON(http.StatusOK, convertedWorkflowJob)
+}
+
// GetArtifacts Lists all artifacts for a repository.
func GetArtifactsOfRun(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/artifacts repository getArtifactsOfRun
@@ -1061,6 +1320,58 @@ func GetArtifactsOfRun(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, &res)
}
+// DeleteActionRun Delete a workflow run
+func DeleteActionRun(ctx *context.APIContext) {
+ // swagger:operation DELETE /repos/{owner}/{repo}/actions/runs/{run} repository deleteActionRun
+ // ---
+ // summary: Delete a workflow run
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: name of the owner
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repository
+ // type: string
+ // required: true
+ // - name: run
+ // in: path
+ // description: runid of the workflow run
+ // type: integer
+ // required: true
+ // responses:
+ // "204":
+ // description: "No Content"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ runID := ctx.PathParamInt64("run")
+ run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIError(http.StatusNotFound, err)
+ return
+ } else if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ if !run.Status.IsDone() {
+ ctx.APIError(http.StatusBadRequest, "this workflow run is not done")
+ return
+ }
+
+ if err := actions_service.DeleteRun(ctx, run); err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
// GetArtifacts Lists all artifacts for a repository.
func GetArtifacts(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/artifacts repository getArtifacts
diff --git a/routers/api/v1/repo/blob.go b/routers/api/v1/repo/blob.go
index d1cb72f5f1..9a17fc1bbf 100644
--- a/routers/api/v1/repo/blob.go
+++ b/routers/api/v1/repo/blob.go
@@ -47,7 +47,7 @@ func GetBlob(ctx *context.APIContext) {
return
}
- if blob, err := files_service.GetBlobBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, sha); err != nil {
+ if blob, err := files_service.GetBlobBySHA(ctx.Repo.Repository, ctx.Repo.GitRepo, sha); err != nil {
ctx.APIError(http.StatusBadRequest, err)
} else {
ctx.JSON(http.StatusOK, blob)
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index fe82550fdd..9af958a5b7 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -224,9 +224,9 @@ func CreateBranch(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
return
}
- } else if len(opt.OldBranchName) > 0 { //nolint
- if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, opt.OldBranchName) { //nolint
- oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint
+ } else if len(opt.OldBranchName) > 0 { //nolint:staticcheck // deprecated field
+ if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, opt.OldBranchName) { //nolint:staticcheck // deprecated field
+ oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint:staticcheck // deprecated field
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -579,7 +579,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
ruleName := form.RuleName
if ruleName == "" {
- ruleName = form.BranchName //nolint
+ ruleName = form.BranchName //nolint:staticcheck // deprecated field
}
if len(ruleName) == 0 {
ctx.APIError(http.StatusBadRequest, "both rule_name and branch_name are empty")
@@ -1181,7 +1181,7 @@ func MergeUpstream(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.MergeUpstreamRequest)
- mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch)
+ mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch, form.FfOnly)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err)
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index d1652c1d51..c2c10cc695 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -93,7 +93,7 @@ func IsCollaborator(ctx *context.APIContext) {
// required: true
// - name: collaborator
// in: path
- // description: username of the collaborator
+ // description: username of the user to check for being a collaborator
// type: string
// required: true
// responses:
@@ -145,7 +145,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
// required: true
// - name: collaborator
// in: path
- // description: username of the collaborator to add
+ // description: username of the user to add or update as a collaborator
// type: string
// required: true
// - name: body
@@ -264,7 +264,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
// required: true
// - name: collaborator
// in: path
- // description: username of the collaborator
+ // description: username of the collaborator whose permissions are to be obtained
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index 20258064a0..6a93be624f 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -8,6 +8,7 @@ import (
"math"
"net/http"
"strconv"
+ "time"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
@@ -116,6 +117,16 @@ func GetAllCommits(ctx *context.APIContext) {
// in: query
// description: filepath of a file/dir
// type: string
+ // - name: since
+ // in: query
+ // description: Only commits after this date will be returned (ISO 8601 format)
+ // type: string
+ // format: date-time
+ // - name: until
+ // in: query
+ // description: Only commits before this date will be returned (ISO 8601 format)
+ // type: string
+ // format: date-time
// - name: stat
// in: query
// description: include diff stats for every commit (disable for speedup, default 'true')
@@ -148,6 +159,23 @@ func GetAllCommits(ctx *context.APIContext) {
// "409":
// "$ref": "#/responses/EmptyRepository"
+ since := ctx.FormString("since")
+ until := ctx.FormString("until")
+
+ // Validate since/until as ISO 8601 (RFC3339)
+ if since != "" {
+ if _, err := time.Parse(time.RFC3339, since); err != nil {
+ ctx.APIError(http.StatusUnprocessableEntity, "invalid 'since' format, expected ISO 8601 (RFC3339)")
+ return
+ }
+ }
+ if until != "" {
+ if _, err := time.Parse(time.RFC3339, until); err != nil {
+ ctx.APIError(http.StatusUnprocessableEntity, "invalid 'until' format, expected ISO 8601 (RFC3339)")
+ return
+ }
+ }
+
if ctx.Repo.Repository.IsEmpty {
ctx.JSON(http.StatusConflict, api.APIError{
Message: "Git Repository is empty.",
@@ -198,6 +226,8 @@ func GetAllCommits(ctx *context.APIContext) {
RepoPath: ctx.Repo.GitRepo.Path,
Not: not,
Revision: []string{baseCommit.ID.String()},
+ Since: since,
+ Until: until,
})
if err != nil {
ctx.APIErrorInternal(err)
@@ -205,7 +235,7 @@ func GetAllCommits(ctx *context.APIContext) {
}
// Query commits
- commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not)
+ commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not, since, until)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -221,6 +251,8 @@ func GetAllCommits(ctx *context.APIContext) {
Not: not,
Revision: []string{sha},
RelPath: []string{path},
+ Since: since,
+ Until: until,
})
if err != nil {
@@ -237,6 +269,8 @@ func GetAllCommits(ctx *context.APIContext) {
File: path,
Not: not,
Page: listOptions.Page,
+ Since: since,
+ Until: until,
})
if err != nil {
ctx.APIErrorInternal(err)
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index f40d39a251..69b5996222 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -62,7 +62,7 @@ func GetRawFile(ctx *context.APIContext) {
// required: true
// - name: ref
// in: query
- // description: "The name of the commit/branch/tag. Default the repository’s default branch"
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch"
// type: string
// required: false
// responses:
@@ -115,7 +115,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
// required: true
// - name: ref
// in: query
- // description: "The name of the commit/branch/tag. Default the repository’s default branch"
+ // description: "The name of the commit/branch/tag. Default to the repository’s default branch"
// type: string
// required: false
// responses:
@@ -139,27 +139,27 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
// LFS Pointer files are at most 1024 bytes - so any blob greater than 1024 bytes cannot be an LFS file
- if blob.Size() > 1024 {
+ if blob.Size() > lfs.MetaFileMaxSize {
// First handle caching for the blob
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return
}
- // OK not cached - serve!
+ // If not cached - serve!
if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
ctx.APIErrorInternal(err)
}
return
}
- // OK, now the blob is known to have at most 1024 bytes we can simply read this in one go (This saves reading it twice)
+ // OK, now the blob is known to have at most 1024 (lfs pointer max size) bytes,
+ // we can simply read this in one go (This saves reading it twice)
dataRc, err := blob.DataAsync()
if err != nil {
ctx.APIErrorInternal(err)
return
}
- // FIXME: code from #19689, what if the file is large ... OOM ...
buf, err := io.ReadAll(dataRc)
if err != nil {
_ = dataRc.Close()
@@ -181,7 +181,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
return
}
- // OK not cached - serve!
+ // If not cached - serve!
common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
return
}
@@ -405,13 +405,6 @@ func GetEditorconfig(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, def)
}
-// canWriteFiles returns true if repository is editable and user has proper access level.
-func canWriteFiles(ctx *context.APIContext, branch string) bool {
- return ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, branch) &&
- !ctx.Repo.Repository.IsMirror &&
- !ctx.Repo.Repository.IsArchived
-}
-
func base64Reader(s string) (io.ReadSeeker, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
@@ -420,6 +413,45 @@ func base64Reader(s string) (io.ReadSeeker, error) {
return bytes.NewReader(b), nil
}
+func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
+ commonOpts := web.GetForm(ctx).(api.FileOptionsInterface).GetFileOptions()
+ commonOpts.BranchName = util.IfZero(commonOpts.BranchName, ctx.Repo.Repository.DefaultBranch)
+ commonOpts.NewBranchName = util.IfZero(commonOpts.NewBranchName, commonOpts.BranchName)
+ if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, commonOpts.NewBranchName) && !ctx.IsUserSiteAdmin() {
+ ctx.APIError(http.StatusForbidden, "user should have a permission to write to the target branch")
+ return
+ }
+ changeFileOpts := &files_service.ChangeRepoFilesOptions{
+ Message: commonOpts.Message,
+ OldBranch: commonOpts.BranchName,
+ NewBranch: commonOpts.NewBranchName,
+ Committer: &files_service.IdentityOptions{
+ GitUserName: commonOpts.Committer.Name,
+ GitUserEmail: commonOpts.Committer.Email,
+ },
+ Author: &files_service.IdentityOptions{
+ GitUserName: commonOpts.Author.Name,
+ GitUserEmail: commonOpts.Author.Email,
+ },
+ Dates: &files_service.CommitDateOptions{
+ Author: commonOpts.Dates.Author,
+ Committer: commonOpts.Dates.Committer,
+ },
+ Signoff: commonOpts.Signoff,
+ }
+ if commonOpts.Dates.Author.IsZero() {
+ commonOpts.Dates.Author = time.Now()
+ }
+ if commonOpts.Dates.Committer.IsZero() {
+ commonOpts.Dates.Committer = time.Now()
+ }
+ ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
+}
+
+func getAPIChangeRepoFileOptions[T api.FileOptionsInterface](ctx *context.APIContext) (apiOpts T, opts *files_service.ChangeRepoFilesOptions) {
+ return web.GetForm(ctx).(T), ctx.Data["__APIChangeRepoFilesOptions"].(*files_service.ChangeRepoFilesOptions)
+}
+
// ChangeFiles handles API call for modifying multiple files
func ChangeFiles(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
@@ -456,20 +488,18 @@ func ChangeFiles(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
-
- apiOpts := web.GetForm(ctx).(*api.ChangeFilesOptions)
-
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.ChangeFilesOptions](ctx)
+ if ctx.Written() {
+ return
}
-
- var files []*files_service.ChangeRepoFile
for _, file := range apiOpts.Files {
contentReader, err := base64Reader(file.ContentBase64)
if err != nil {
ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
+ // FIXME: ChangeFileOperation.SHA is NOT required for update or delete if last commit is provided in the options
+ // But the LastCommitID is not provided in the API options, need to fully fix them in API
changeRepoFile := &files_service.ChangeRepoFile{
Operation: file.Operation,
TreePath: file.Path,
@@ -477,41 +507,15 @@ func ChangeFiles(ctx *context.APIContext) {
ContentReader: contentReader,
SHA: file.SHA,
}
- files = append(files, changeRepoFile)
- }
-
- opts := &files_service.ChangeRepoFilesOptions{
- Files: files,
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- GitUserName: apiOpts.Committer.Name,
- GitUserEmail: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- GitUserName: apiOpts.Author.Name,
- GitUserEmail: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
+ opts.Files = append(opts.Files, changeRepoFile)
}
if opts.Message == "" {
- opts.Message = changeFilesCommitMessage(ctx, files)
+ opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
- if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
- handleCreateOrUpdateFileError(ctx, err)
+ if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
+ handleChangeRepoFilesError(ctx, err)
} else {
ctx.JSON(http.StatusCreated, filesResponse)
}
@@ -559,56 +563,27 @@ func CreateFile(ctx *context.APIContext) {
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
-
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.CreateFileOptions](ctx)
+ if ctx.Written() {
+ return
}
-
contentReader, err := base64Reader(apiOpts.ContentBase64)
if err != nil {
ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- opts := &files_service.ChangeRepoFilesOptions{
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: "create",
- TreePath: ctx.PathParam("*"),
- ContentReader: contentReader,
- },
- },
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- GitUserName: apiOpts.Committer.Name,
- GitUserEmail: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- GitUserName: apiOpts.Author.Name,
- GitUserEmail: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
+ opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
+ Operation: "create",
+ TreePath: ctx.PathParam("*"),
+ ContentReader: contentReader,
+ })
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
- if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
- handleCreateOrUpdateFileError(ctx, err)
+ if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
+ handleChangeRepoFilesError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusCreated, fileResponse)
@@ -656,96 +631,55 @@ func UpdateFile(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
- if ctx.Repo.Repository.IsEmpty {
- ctx.APIError(http.StatusUnprocessableEntity, errors.New("repo is empty"))
- return
- }
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.UpdateFileOptions](ctx)
+ if ctx.Written() {
+ return
}
-
contentReader, err := base64Reader(apiOpts.ContentBase64)
if err != nil {
ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
-
- opts := &files_service.ChangeRepoFilesOptions{
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: "update",
- ContentReader: contentReader,
- SHA: apiOpts.SHA,
- FromTreePath: apiOpts.FromPath,
- TreePath: ctx.PathParam("*"),
- },
- },
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- GitUserName: apiOpts.Committer.Name,
- GitUserEmail: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- GitUserName: apiOpts.Author.Name,
- GitUserEmail: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
+ opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
+ Operation: "update",
+ ContentReader: contentReader,
+ SHA: apiOpts.SHA,
+ FromTreePath: apiOpts.FromPath,
+ TreePath: ctx.PathParam("*"),
+ })
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
- if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
- handleCreateOrUpdateFileError(ctx, err)
+ if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
+ handleChangeRepoFilesError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse)
}
}
-func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
+func handleChangeRepoFilesError(ctx *context.APIContext, err error) {
if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
ctx.APIError(http.StatusForbidden, err)
return
}
if git_model.IsErrBranchAlreadyExists(err) || files_service.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
- files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) {
+ files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) ||
+ files_service.IsErrCommitIDDoesNotMatch(err) || files_service.IsErrSHAOrCommitIDNotProvided(err) {
ctx.APIError(http.StatusUnprocessableEntity, err)
return
}
- if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
+ if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
ctx.APIError(http.StatusNotFound, err)
return
}
-
- ctx.APIErrorInternal(err)
-}
-
-// Called from both CreateFile or UpdateFile to handle both
-func createOrUpdateFiles(ctx *context.APIContext, opts *files_service.ChangeRepoFilesOptions) (*api.FilesResponse, error) {
- if !canWriteFiles(ctx, opts.OldBranch) {
- return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{
- UserID: ctx.Doer.ID,
- RepoName: ctx.Repo.Repository.LowerName,
- }
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIError(http.StatusNotFound, err)
+ return
}
-
- return files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts)
+ ctx.APIErrorInternal(err)
}
// format commit message if empty
@@ -759,7 +693,7 @@ func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.Ch
switch file.Operation {
case "create":
createFiles = append(createFiles, file.TreePath)
- case "update":
+ case "update", "upload", "rename": // upload and rename works like "update", there is no translation for them at the moment
updateFiles = append(updateFiles, file.TreePath)
case "delete":
deleteFiles = append(deleteFiles, file.TreePath)
@@ -817,74 +751,27 @@ func DeleteFile(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/error"
+ // "422":
+ // "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions)
- if !canWriteFiles(ctx, apiOpts.BranchName) {
- ctx.APIError(http.StatusForbidden, repo_model.ErrUserDoesNotHaveAccessToRepo{
- UserID: ctx.Doer.ID,
- RepoName: ctx.Repo.Repository.LowerName,
- })
+ apiOpts, opts := getAPIChangeRepoFileOptions[*api.DeleteFileOptions](ctx)
+ if ctx.Written() {
return
}
- if apiOpts.BranchName == "" {
- apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
- }
-
- opts := &files_service.ChangeRepoFilesOptions{
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: "delete",
- SHA: apiOpts.SHA,
- TreePath: ctx.PathParam("*"),
- },
- },
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files_service.IdentityOptions{
- GitUserName: apiOpts.Committer.Name,
- GitUserEmail: apiOpts.Committer.Email,
- },
- Author: &files_service.IdentityOptions{
- GitUserName: apiOpts.Author.Name,
- GitUserEmail: apiOpts.Author.Email,
- },
- Dates: &files_service.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
+ opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
+ Operation: "delete",
+ SHA: apiOpts.SHA,
+ TreePath: ctx.PathParam("*"),
+ })
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
- if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
- ctx.APIError(http.StatusNotFound, err)
- return
- } else if git_model.IsErrBranchAlreadyExists(err) ||
- files_service.IsErrFilenameInvalid(err) ||
- pull_service.IsErrSHADoesNotMatch(err) ||
- files_service.IsErrCommitIDDoesNotMatch(err) ||
- files_service.IsErrSHAOrCommitIDNotProvided(err) {
- ctx.APIError(http.StatusBadRequest, err)
- return
- } else if files_service.IsErrUserCannotCommit(err) {
- ctx.APIError(http.StatusForbidden, err)
- return
- }
- ctx.APIErrorInternal(err)
+ handleChangeRepoFilesError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse) // FIXME on APIv2: return http.StatusNoContent
@@ -902,11 +789,81 @@ func resolveRefCommit(ctx *context.APIContext, ref string, minCommitIDLen ...int
return refCommit
}
-// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
+func GetContentsExt(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/contents-ext/{filepath} repository repoGetContentsExt
+ // ---
+ // summary: The extended "contents" API, to get file metadata and/or content, or list a directory.
+ // description: It guarantees that only one of the response fields is set if the request succeeds.
+ // Users can pass "includes=file_content" or "includes=lfs_metadata" to retrieve more fields.
+ // "includes=file_content" only works for single file, if you need to retrieve file contents in batch,
+ // use "file-contents" API after listing the directory.
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // - name: filepath
+ // in: path
+ // description: path of the dir, file, symlink or submodule in the repo. Swagger requires path parameter to be "required",
+ // you can leave it empty or pass a single dot (".") to get the root directory.
+ // type: string
+ // required: true
+ // - name: ref
+ // in: query
+ // description: the name of the commit/branch/tag, default to the repository’s default branch.
+ // type: string
+ // required: false
+ // - name: includes
+ // in: query
+ // description: By default this API's response only contains file's metadata. Use comma-separated "includes" options to retrieve more fields.
+ // Option "file_content" will try to retrieve the file content, "lfs_metadata" will try to retrieve LFS metadata,
+ // "commit_metadata" will try to retrieve commit metadata, and "commit_message" will try to retrieve commit message.
+ // type: string
+ // required: false
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ContentsExtResponse"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ if treePath := ctx.PathParam("*"); treePath == "." || treePath == "/" {
+ ctx.SetPathParam("*", "") // workaround for swagger, it requires path parameter to be "required", but we need to list root directory
+ }
+ opts := files_service.GetContentsOrListOptions{TreePath: ctx.PathParam("*")}
+ for includeOpt := range strings.SplitSeq(ctx.FormString("includes"), ",") {
+ if includeOpt == "" {
+ continue
+ }
+ switch includeOpt {
+ case "file_content":
+ opts.IncludeSingleFileContent = true
+ case "lfs_metadata":
+ opts.IncludeLfsMetadata = true
+ case "commit_metadata":
+ opts.IncludeCommitMetadata = true
+ case "commit_message":
+ opts.IncludeCommitMessage = true
+ default:
+ ctx.APIError(http.StatusBadRequest, fmt.Sprintf("unknown include option %q", includeOpt))
+ return
+ }
+ }
+ ctx.JSON(http.StatusOK, getRepoContents(ctx, opts))
+}
+
func GetContents(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
// ---
- // summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
+ // summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir.
+ // description: This API follows GitHub's design, and it is not easy to use. Recommend users to use the "contents-ext" API instead.
// produces:
// - application/json
// parameters:
@@ -935,29 +892,38 @@ func GetContents(ctx *context.APIContext) {
// "$ref": "#/responses/ContentsResponse"
// "404":
// "$ref": "#/responses/notFound"
-
- treePath := ctx.PathParam("*")
- refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
+ ret := getRepoContents(ctx, files_service.GetContentsOrListOptions{
+ TreePath: ctx.PathParam("*"),
+ IncludeSingleFileContent: true,
+ IncludeCommitMetadata: true,
+ })
if ctx.Written() {
return
}
+ ctx.JSON(http.StatusOK, util.Iif[any](ret.FileContents != nil, ret.FileContents, ret.DirContents))
+}
- if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, refCommit, treePath); err != nil {
+func getRepoContents(ctx *context.APIContext, opts files_service.GetContentsOrListOptions) *api.ContentsExtResponse {
+ refCommit := resolveRefCommit(ctx, ctx.FormTrim("ref"))
+ if ctx.Written() {
+ return nil
+ }
+ ret, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, opts)
+ if err != nil {
if git.IsErrNotExist(err) {
ctx.APIErrorNotFound("GetContentsOrList", err)
- return
+ return nil
}
ctx.APIErrorInternal(err)
- } else {
- ctx.JSON(http.StatusOK, fileList)
}
+ return &ret
}
-// GetContentsList Get the metadata of all the entries of the root dir
func GetContentsList(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList
// ---
- // summary: Gets the metadata of all the entries of the root dir
+ // summary: Gets the metadata of all the entries of the root dir.
+ // description: This API follows GitHub's design, and it is not easy to use. Recommend users to use our "contents-ext" API instead.
// produces:
// - application/json
// parameters:
@@ -990,7 +956,7 @@ func GetFileContentsGet(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/file-contents repository repoGetFileContents
// ---
// summary: Get the metadata and contents of requested files
- // description: See the POST method. This GET method supports to use JSON encoded request body in query parameter.
+ // description: See the POST method. This GET method supports using JSON encoded request body in query parameter.
// produces:
// - application/json
// parameters:
@@ -1020,7 +986,7 @@ func GetFileContentsGet(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- // POST method requires "write" permission, so we also support this "GET" method
+ // The POST method requires "write" permission, so we also support this "GET" method
handleGetFileContents(ctx)
}
@@ -1064,7 +1030,7 @@ func GetFileContentsPost(ctx *context.APIContext) {
// This is actually a "read" request, but we need to accept a "files" list, then POST method seems easy to use.
// But the permission system requires that the caller must have "write" permission to use POST method.
- // At the moment there is no other way to get around the permission check, so there is a "GET" workaround method above.
+ // At the moment, there is no other way to get around the permission check, so there is a "GET" workaround method above.
handleGetFileContents(ctx)
}
@@ -1081,6 +1047,6 @@ func handleGetFileContents(ctx *context.APIContext) {
if ctx.Written() {
return
}
- filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, refCommit, opts.Files)
+ filesResponse := files_service.GetContentsListFromTreePaths(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, opts.Files)
ctx.JSON(http.StatusOK, util.SliceNilAsEmpty(filesResponse))
}
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index e678db5262..d4a5872fd1 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -152,7 +152,7 @@ func SearchIssues(ctx *context.APIContext) {
)
{
// find repos user can access (for issue search)
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
Private: false,
AllPublic: true,
TopicOnly: false,
@@ -895,6 +895,15 @@ func EditIssue(ctx *context.APIContext) {
issue.MilestoneID != *form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone
+ if issue.MilestoneID > 0 {
+ issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, *form.Milestone)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ } else {
+ issue.Milestone = nil
+ }
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
ctx.APIErrorInternal(err)
return
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index 0c572a06a8..cc342a9313 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -609,15 +609,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return
}
- oldContent := comment.Content
- comment.Content = form.Body
- if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
- if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.APIError(http.StatusForbidden, err)
- } else {
- ctx.APIErrorInternal(err)
+ if form.Body != comment.Content {
+ oldContent := comment.Content
+ comment.Content = form.Body
+ if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
+ if errors.Is(err, user_model.ErrBlockedUser) {
+ ctx.APIError(http.StatusForbidden, err)
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return
}
- return
}
ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go
index 2048c76ea0..1b58beb7b6 100644
--- a/routers/api/v1/repo/issue_dependency.go
+++ b/routers/api/v1/repo/issue_dependency.go
@@ -77,10 +77,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit == 0 {
limit = setting.API.DefaultPagingNum
@@ -328,10 +325,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit <= 1 {
limit = setting.API.DefaultPagingNum
diff --git a/routers/api/v1/repo/issue_stopwatch.go b/routers/api/v1/repo/issue_stopwatch.go
index b18e172b37..0f28b9757d 100644
--- a/routers/api/v1/repo/issue_stopwatch.go
+++ b/routers/api/v1/repo/issue_stopwatch.go
@@ -4,7 +4,6 @@
package repo
import (
- "errors"
"net/http"
issues_model "code.gitea.io/gitea/models/issues"
@@ -49,14 +48,17 @@ func StartIssueStopwatch(ctx *context.APIContext) {
// "409":
// description: Cannot start a stopwatch again if it already exists
- issue, err := prepareIssueStopwatch(ctx, false)
- if err != nil {
+ issue := prepareIssueForStopwatch(ctx)
+ if ctx.Written() {
return
}
- if err := issues_model.CreateIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
+ if ok, err := issues_model.CreateIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
ctx.APIErrorInternal(err)
return
+ } else if !ok {
+ ctx.APIError(http.StatusConflict, "cannot start a stopwatch again if it already exists")
+ return
}
ctx.Status(http.StatusCreated)
@@ -96,18 +98,20 @@ func StopIssueStopwatch(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
// "409":
- // description: Cannot stop a non existent stopwatch
+ // description: Cannot stop a non-existent stopwatch
- issue, err := prepareIssueStopwatch(ctx, true)
- if err != nil {
+ issue := prepareIssueForStopwatch(ctx)
+ if ctx.Written() {
return
}
- if err := issues_model.FinishIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
+ if ok, err := issues_model.FinishIssueStopwatch(ctx, ctx.Doer, issue); err != nil {
ctx.APIErrorInternal(err)
return
+ } else if !ok {
+ ctx.APIError(http.StatusConflict, "cannot stop a non-existent stopwatch")
+ return
}
-
ctx.Status(http.StatusCreated)
}
@@ -145,22 +149,25 @@ func DeleteIssueStopwatch(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
// "409":
- // description: Cannot cancel a non existent stopwatch
+ // description: Cannot cancel a non-existent stopwatch
- issue, err := prepareIssueStopwatch(ctx, true)
- if err != nil {
+ issue := prepareIssueForStopwatch(ctx)
+ if ctx.Written() {
return
}
- if err := issues_model.CancelStopwatch(ctx, ctx.Doer, issue); err != nil {
+ if ok, err := issues_model.CancelStopwatch(ctx, ctx.Doer, issue); err != nil {
ctx.APIErrorInternal(err)
return
+ } else if !ok {
+ ctx.APIError(http.StatusConflict, "cannot cancel a non-existent stopwatch")
+ return
}
ctx.Status(http.StatusNoContent)
}
-func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_model.Issue, error) {
+func prepareIssueForStopwatch(ctx *context.APIContext) *issues_model.Issue {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
@@ -168,32 +175,19 @@ func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_m
} else {
ctx.APIErrorInternal(err)
}
-
- return nil, err
+ return nil
}
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(http.StatusForbidden)
- return nil, errors.New("Unable to write to PRs")
+ return nil
}
if !ctx.Repo.CanUseTimetracker(ctx, issue, ctx.Doer) {
ctx.Status(http.StatusForbidden)
- return nil, errors.New("Cannot use time tracker")
- }
-
- if issues_model.StopwatchExists(ctx, ctx.Doer.ID, issue.ID) != shouldExist {
- if shouldExist {
- ctx.APIError(http.StatusConflict, "cannot stop/cancel a non existent stopwatch")
- err = errors.New("cannot stop/cancel a non existent stopwatch")
- } else {
- ctx.APIError(http.StatusConflict, "cannot start a stopwatch again if it already exists")
- err = errors.New("cannot start a stopwatch again if it already exists")
- }
- return nil, err
+ return nil
}
-
- return issue, nil
+ return issue
}
// GetStopwatches get all stopwatches
diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go
index 21e549496d..c89f228a06 100644
--- a/routers/api/v1/repo/issue_subscription.go
+++ b/routers/api/v1/repo/issue_subscription.go
@@ -43,7 +43,7 @@ func AddIssueSubscription(ctx *context.APIContext) {
// required: true
// - name: user
// in: path
- // description: user to subscribe
+ // description: username of the user to subscribe the issue to
// type: string
// required: true
// responses:
@@ -87,7 +87,7 @@ func DelIssueSubscription(ctx *context.APIContext) {
// required: true
// - name: user
// in: path
- // description: user witch unsubscribe
+ // description: username of the user to unsubscribe from an issue
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go
index dd6abf94c6..171da272cc 100644
--- a/routers/api/v1/repo/issue_tracked_time.go
+++ b/routers/api/v1/repo/issue_tracked_time.go
@@ -405,7 +405,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
// required: true
// - name: user
// in: path
- // description: username of user
+ // description: username of the user whose tracked times are to be listed
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go
index f2e0cad86c..c1e0b47d33 100644
--- a/routers/api/v1/repo/migrate.go
+++ b/routers/api/v1/repo/migrate.go
@@ -203,7 +203,7 @@ func Migrate(ctx *context.APIContext) {
}
if repo != nil {
- if errDelete := repo_service.DeleteRepositoryDirectly(ctx, ctx.Doer, repo.ID); errDelete != nil {
+ if errDelete := repo_service.DeleteRepositoryDirectly(ctx, repo.ID); errDelete != nil {
log.Error("DeleteRepository: %v", errDelete)
}
}
diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go
index bcf498bf7e..e9f5cf5d90 100644
--- a/routers/api/v1/repo/patch.go
+++ b/routers/api/v1/repo/patch.go
@@ -5,15 +5,10 @@ package repo
import (
"net/http"
- "time"
- git_model "code.gitea.io/gitea/models/git"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
- pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/services/repository/files"
)
@@ -49,63 +44,22 @@ func ApplyDiffPatch(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// "423":
// "$ref": "#/responses/repoArchivedError"
- apiOpts := web.GetForm(ctx).(*api.ApplyDiffPatchFileOptions)
-
+ apiOpts, changeRepoFileOpts := getAPIChangeRepoFileOptions[*api.ApplyDiffPatchFileOptions](ctx)
opts := &files.ApplyDiffPatchOptions{
- Content: apiOpts.Content,
- SHA: apiOpts.SHA,
- Message: apiOpts.Message,
- OldBranch: apiOpts.BranchName,
- NewBranch: apiOpts.NewBranchName,
- Committer: &files.IdentityOptions{
- GitUserName: apiOpts.Committer.Name,
- GitUserEmail: apiOpts.Committer.Email,
- },
- Author: &files.IdentityOptions{
- GitUserName: apiOpts.Author.Name,
- GitUserEmail: apiOpts.Author.Email,
- },
- Dates: &files.CommitDateOptions{
- Author: apiOpts.Dates.Author,
- Committer: apiOpts.Dates.Committer,
- },
- Signoff: apiOpts.Signoff,
- }
- if opts.Dates.Author.IsZero() {
- opts.Dates.Author = time.Now()
- }
- if opts.Dates.Committer.IsZero() {
- opts.Dates.Committer = time.Now()
- }
-
- if opts.Message == "" {
- opts.Message = "apply-patch"
- }
+ Content: apiOpts.Content,
+ Message: util.IfZero(apiOpts.Message, "apply-patch"),
- if !canWriteFiles(ctx, apiOpts.BranchName) {
- ctx.APIErrorInternal(repo_model.ErrUserDoesNotHaveAccessToRepo{
- UserID: ctx.Doer.ID,
- RepoName: ctx.Repo.Repository.LowerName,
- })
- return
+ OldBranch: changeRepoFileOpts.OldBranch,
+ NewBranch: changeRepoFileOpts.NewBranch,
+ Committer: changeRepoFileOpts.Committer,
+ Author: changeRepoFileOpts.Author,
+ Dates: changeRepoFileOpts.Dates,
+ Signoff: changeRepoFileOpts.Signoff,
}
fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts)
if err != nil {
- if files.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
- ctx.APIError(http.StatusForbidden, err)
- return
- }
- if git_model.IsErrBranchAlreadyExists(err) || files.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) ||
- files.IsErrFilePathInvalid(err) || files.IsErrRepoFileAlreadyExists(err) {
- ctx.APIError(http.StatusUnprocessableEntity, err)
- return
- }
- if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
- ctx.APIError(http.StatusNotFound, err)
- return
- }
- ctx.APIErrorInternal(err)
+ handleChangeRepoFilesError(ctx, err)
} else {
ctx.JSON(http.StatusCreated, fileResponse)
}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index c0ab381bc8..2c194f9253 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -73,7 +74,7 @@ func ListPullRequests(ctx *context.APIContext) {
// in: query
// description: Type of sort
// type: string
- // enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
+ // enum: [oldest, recentupdate, recentclose, leastupdate, mostcomment, leastcomment, priority]
// - name: milestone
// in: query
// description: ID of the milestone
@@ -706,6 +707,11 @@ func EditPullRequest(ctx *context.APIContext) {
issue.MilestoneID != form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = form.Milestone
+ issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
ctx.APIErrorInternal(err)
return
@@ -1296,7 +1302,7 @@ func UpdatePullRequest(ctx *context.APIContext) {
// default merge commit message
message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
- if err = pull_service.Update(ctx, pr, ctx.Doer, message, rebase); err != nil {
+ if err = pull_service.Update(graceful.GetManager().ShutdownContext(), pr, ctx.Doer, message, rebase); err != nil {
if pull_service.IsErrMergeConflicts(err) {
ctx.APIError(http.StatusConflict, "merge failed because of conflict")
return
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index b6f5a3ac9e..272b395dfb 100644
--- a/routers/api/v1/repo/release.go
+++ b/routers/api/v1/repo/release.go
@@ -247,7 +247,9 @@ func CreateRelease(ctx *context.APIContext) {
IsTag: false,
Repo: ctx.Repo.Repository,
}
- if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); err != nil {
+ // GitHub doesn't have "tag_message", GitLab has: https://docs.gitlab.com/api/releases/#create-a-release
+ // It doesn't need to be the same as the "release note"
+ if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, form.TagMessage); err != nil {
if repo_model.IsErrReleaseAlreadyExist(err) {
ctx.APIError(http.StatusConflict, err)
} else if release_service.IsErrProtectedTagName(err) {
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 7caf004b4a..8acc912796 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -134,7 +134,7 @@ func Search(ctx *context.APIContext) {
private = false
}
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
ListOptions: utils.GetListOptions(ctx),
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go
index 756adcf3a3..40007ea1e5 100644
--- a/routers/api/v1/repo/status.go
+++ b/routers/api/v1/repo/status.go
@@ -258,19 +258,24 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
repo := ctx.Repo.Repository
- statuses, count, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx))
+ statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx))
if err != nil {
ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return
}
+ count, err := git_model.CountLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String())
+ if err != nil {
+ ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
+ return
+ }
+ ctx.SetTotalCountHeader(count)
+
if len(statuses) == 0 {
ctx.JSON(http.StatusOK, &api.CombinedStatus{})
return
}
combiStatus := convert.ToCombinedStatus(ctx, statuses, convert.ToRepo(ctx, repo, ctx.Repo.Permission))
-
- ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, combiStatus)
}
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index d5840b4149..8e24ffa465 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -298,10 +298,7 @@ func ListWikiPages(ctx *context.APIContext) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
limit := ctx.FormInt("limit")
if limit <= 1 {
limit = setting.API.DefaultPagingNum
@@ -434,10 +431,7 @@ func ListPageRevisions(ctx *context.APIContext) {
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
// get Commit Count
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
@@ -505,7 +499,7 @@ func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string {
if blob.Size() > setting.API.DefaultMaxBlobSize {
return ""
}
- content, err := blob.GetBlobContentBase64()
+ content, err := blob.GetBlobContentBase64(nil)
if err != nil {
ctx.APIErrorInternal(err)
return ""
diff --git a/routers/api/v1/shared/action.go b/routers/api/v1/shared/action.go
new file mode 100644
index 0000000000..c97e9419fd
--- /dev/null
+++ b/routers/api/v1/shared/action.go
@@ -0,0 +1,187 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package shared
+
+import (
+ "fmt"
+ "net/http"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/webhook"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
+)
+
+// ListJobs lists jobs for api route validated ownerID and repoID
+// ownerID == 0 and repoID == 0 means all jobs
+// ownerID == 0 and repoID != 0 means all jobs for the given repo
+// ownerID != 0 and repoID == 0 means all jobs for the given user/org
+// ownerID != 0 and repoID != 0 undefined behavior
+// runID == 0 means all jobs
+// runID is used as an additional filter together with ownerID and repoID to only return jobs for the given run
+// Access rights are checked at the API route level
+func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+ opts := actions_model.FindRunJobOptions{
+ OwnerID: ownerID,
+ RepoID: repoID,
+ RunID: runID,
+ ListOptions: utils.GetListOptions(ctx),
+ }
+ for _, status := range ctx.FormStrings("status") {
+ values, err := convertToInternal(status)
+ if err != nil {
+ ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
+ return
+ }
+ opts.Statuses = append(opts.Statuses, values...)
+ }
+
+ jobs, total, err := db.FindAndCount[actions_model.ActionRunJob](ctx, opts)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ res := new(api.ActionWorkflowJobsResponse)
+ res.TotalCount = total
+
+ res.Entries = make([]*api.ActionWorkflowJob, len(jobs))
+
+ isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
+ for i := range jobs {
+ var repository *repo_model.Repository
+ if isRepoLevel {
+ repository = ctx.Repo.Repository
+ } else {
+ repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
+
+ convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i])
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ res.Entries[i] = convertedWorkflowJob
+ }
+
+ ctx.JSON(http.StatusOK, &res)
+}
+
+func convertToInternal(s string) ([]actions_model.Status, error) {
+ switch s {
+ case "pending", "waiting", "requested", "action_required":
+ return []actions_model.Status{actions_model.StatusBlocked}, nil
+ case "queued":
+ return []actions_model.Status{actions_model.StatusWaiting}, nil
+ case "in_progress":
+ return []actions_model.Status{actions_model.StatusRunning}, nil
+ case "completed":
+ return []actions_model.Status{
+ actions_model.StatusSuccess,
+ actions_model.StatusFailure,
+ actions_model.StatusSkipped,
+ actions_model.StatusCancelled,
+ }, nil
+ case "failure":
+ return []actions_model.Status{actions_model.StatusFailure}, nil
+ case "success":
+ return []actions_model.Status{actions_model.StatusSuccess}, nil
+ case "skipped", "neutral":
+ return []actions_model.Status{actions_model.StatusSkipped}, nil
+ case "cancelled", "timed_out":
+ return []actions_model.Status{actions_model.StatusCancelled}, nil
+ default:
+ return nil, fmt.Errorf("invalid status %s", s)
+ }
+}
+
+// ListRuns lists jobs for api route validated ownerID and repoID
+// ownerID == 0 and repoID == 0 means all runs
+// ownerID == 0 and repoID != 0 means all runs for the given repo
+// ownerID != 0 and repoID == 0 means all runs for the given user/org
+// ownerID != 0 and repoID != 0 undefined behavior
+// Access rights are checked at the API route level
+func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+ opts := actions_model.FindRunOptions{
+ OwnerID: ownerID,
+ RepoID: repoID,
+ ListOptions: utils.GetListOptions(ctx),
+ }
+
+ if event := ctx.FormString("event"); event != "" {
+ opts.TriggerEvent = webhook.HookEventType(event)
+ }
+ if branch := ctx.FormString("branch"); branch != "" {
+ opts.Ref = string(git.RefNameFromBranch(branch))
+ }
+ for _, status := range ctx.FormStrings("status") {
+ values, err := convertToInternal(status)
+ if err != nil {
+ ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status))
+ return
+ }
+ opts.Status = append(opts.Status, values...)
+ }
+ if actor := ctx.FormString("actor"); actor != "" {
+ user, err := user_model.GetUserByName(ctx, actor)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ opts.TriggerUserID = user.ID
+ }
+ if headSHA := ctx.FormString("head_sha"); headSHA != "" {
+ opts.CommitSHA = headSHA
+ }
+
+ runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+
+ res := new(api.ActionWorkflowRunsResponse)
+ res.TotalCount = total
+
+ res.Entries = make([]*api.ActionWorkflowRun, len(runs))
+ isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID
+ for i := range runs {
+ var repository *repo_model.Repository
+ if isRepoLevel {
+ repository = ctx.Repo.Repository
+ } else {
+ repository, err = repo_model.GetRepositoryByID(ctx, runs[i].RepoID)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
+
+ convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i])
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ res.Entries[i] = convertedRun
+ }
+
+ ctx.JSON(http.StatusOK, &res)
+}
diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go
index d42f330d1c..e9834aff9f 100644
--- a/routers/api/v1/shared/runners.go
+++ b/routers/api/v1/shared/runners.go
@@ -67,6 +67,28 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
ctx.JSON(http.StatusOK, &res)
}
+func getRunnerByID(ctx *context.APIContext, ownerID, repoID, runnerID int64) (*actions_model.ActionRunner, bool) {
+ if ownerID != 0 && repoID != 0 {
+ setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
+ }
+
+ runner, err := actions_model.GetRunnerByID(ctx, runnerID)
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.APIErrorNotFound("Runner not found")
+ } else {
+ ctx.APIErrorInternal(err)
+ }
+ return nil, false
+ }
+
+ if !runner.EditableInContext(ownerID, repoID) {
+ ctx.APIErrorNotFound("No permission to access this runner")
+ return nil, false
+ }
+ return runner, true
+}
+
// GetRunner get the runner for api route validated ownerID and repoID
// ownerID == 0 and repoID == 0 means any runner including global runners
// ownerID == 0 and repoID != 0 means any runner for the given repo
@@ -77,13 +99,8 @@ func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
if ownerID != 0 && repoID != 0 {
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
}
- runner, err := actions_model.GetRunnerByID(ctx, runnerID)
- if err != nil {
- ctx.APIErrorNotFound(err)
- return
- }
- if !runner.EditableInContext(ownerID, repoID) {
- ctx.APIErrorNotFound("No permission to get this runner")
+ runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
+ if !ok {
return
}
ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
@@ -96,20 +113,12 @@ func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
// ownerID != 0 and repoID != 0 undefined behavior
// Access rights are checked at the API route level
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
- if ownerID != 0 && repoID != 0 {
- setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
- }
- runner, err := actions_model.GetRunnerByID(ctx, runnerID)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if !runner.EditableInContext(ownerID, repoID) {
- ctx.APIErrorNotFound("No permission to delete this runner")
+ runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
+ if !ok {
return
}
- err = actions_model.DeleteRunner(ctx, runner.ID)
+ err := actions_model.DeleteRunner(ctx, runner.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go
index df0c8a805a..9e20c0533b 100644
--- a/routers/api/v1/swagger/repo.go
+++ b/routers/api/v1/swagger/repo.go
@@ -331,6 +331,12 @@ type swaggerContentsListResponse struct {
Body []api.ContentsResponse `json:"body"`
}
+// swagger:response ContentsExtResponse
+type swaggerContentsExtResponse struct {
+ // in:body
+ Body api.ContentsExtResponse `json:"body"`
+}
+
// FileDeleteResponse
// swagger:response FileDeleteResponse
type swaggerFileDeleteResponse struct {
@@ -443,6 +449,34 @@ type swaggerRepoTasksList struct {
Body api.ActionTaskResponse `json:"body"`
}
+// WorkflowRunsList
+// swagger:response WorkflowRunsList
+type swaggerActionWorkflowRunsResponse struct {
+ // in:body
+ Body api.ActionWorkflowRunsResponse `json:"body"`
+}
+
+// WorkflowRun
+// swagger:response WorkflowRun
+type swaggerWorkflowRun struct {
+ // in:body
+ Body api.ActionWorkflowRun `json:"body"`
+}
+
+// WorkflowJobsList
+// swagger:response WorkflowJobsList
+type swaggerActionWorkflowJobsResponse struct {
+ // in:body
+ Body api.ActionWorkflowJobsResponse `json:"body"`
+}
+
+// WorkflowJob
+// swagger:response WorkflowJob
+type swaggerWorkflowJob struct {
+ // in:body
+ Body api.ActionWorkflowJob `json:"body"`
+}
+
// ArtifactsList
// swagger:response ArtifactsList
type swaggerRepoArtifactsList struct {
diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go
index 04097fcc95..e934d02aa7 100644
--- a/routers/api/v1/user/action.go
+++ b/routers/api/v1/user/action.go
@@ -12,6 +12,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/routers/api/v1/utils"
actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/context"
@@ -127,13 +128,11 @@ func CreateVariable(ctx *context.APIContext) {
// "$ref": "#/definitions/CreateVariableOption"
// responses:
// "201":
- // description: response when creating a variable
- // "204":
- // description: response when creating a variable
+ // description: successfully created the user-level variable
// "400":
// "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
+ // "409":
+ // description: variable name already exists.
opt := web.GetForm(ctx).(*api.CreateVariableOption)
@@ -162,7 +161,7 @@ func CreateVariable(ctx *context.APIContext) {
return
}
- ctx.Status(http.StatusNoContent)
+ ctx.Status(http.StatusCreated)
}
// UpdateVariable update a user-level variable which is created by current doer
@@ -358,3 +357,86 @@ func ListVariables(ctx *context.APIContext) {
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, variables)
}
+
+// ListWorkflowRuns lists workflow runs
+func ListWorkflowRuns(ctx *context.APIContext) {
+ // swagger:operation GET /user/actions/runs user getUserWorkflowRuns
+ // ---
+ // summary: Get workflow runs
+ // parameters:
+ // - name: event
+ // in: query
+ // description: workflow event name
+ // type: string
+ // required: false
+ // - name: branch
+ // in: query
+ // description: workflow branch
+ // type: string
+ // required: false
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: actor
+ // in: query
+ // description: triggered by user
+ // type: string
+ // required: false
+ // - name: head_sha
+ // in: query
+ // description: triggering sha of the workflow run
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowRunsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ shared.ListRuns(ctx, ctx.Doer.ID, 0)
+}
+
+// ListWorkflowJobs lists workflow jobs
+func ListWorkflowJobs(ctx *context.APIContext) {
+ // swagger:operation GET /user/actions/jobs user getUserWorkflowJobs
+ // ---
+ // summary: Get workflow jobs
+ // parameters:
+ // - name: status
+ // in: query
+ // description: workflow status (pending, queued, in_progress, failure, success, skipped)
+ // type: string
+ // required: false
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/WorkflowJobsList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListJobs(ctx, ctx.Doer.ID, 0, 0)
+}
diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go
index 7201010161..6f1053e7ac 100644
--- a/routers/api/v1/user/app.go
+++ b/routers/api/v1/user/app.go
@@ -30,7 +30,7 @@ func ListAccessTokens(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of to user whose access tokens are to be listed
// type: string
// required: true
// - name: page
@@ -83,7 +83,7 @@ func CreateAccessToken(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose token is to be created
// required: true
// type: string
// - name: body
@@ -149,7 +149,7 @@ func DeleteAccessToken(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose token is to be deleted
// type: string
// required: true
// - name: token
diff --git a/routers/api/v1/user/block.go b/routers/api/v1/user/block.go
index 7231e9add7..8365188f60 100644
--- a/routers/api/v1/user/block.go
+++ b/routers/api/v1/user/block.go
@@ -37,7 +37,7 @@ func CheckUserBlock(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: user to check
+ // description: username of the user to check
// type: string
// required: true
// responses:
@@ -56,7 +56,7 @@ func BlockUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: user to block
+ // description: username of the user to block
// type: string
// required: true
// - name: note
@@ -81,7 +81,7 @@ func UnblockUser(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: user to unblock
+ // description: username of the user to unblock
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go
index 0d0c0be7e0..339b994af4 100644
--- a/routers/api/v1/user/follower.go
+++ b/routers/api/v1/user/follower.go
@@ -67,7 +67,7 @@ func ListFollowers(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose followers are to be listed
// type: string
// required: true
// - name: page
@@ -131,7 +131,7 @@ func ListFollowing(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose followed users are to be listed
// type: string
// required: true
// - name: page
@@ -167,7 +167,7 @@ func CheckMyFollowing(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of followed user
+ // description: username of the user to check for authenticated followers
// type: string
// required: true
// responses:
@@ -187,12 +187,12 @@ func CheckFollowing(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of following user
+ // description: username of the following user
// type: string
// required: true
// - name: target
// in: path
- // description: username of followed user
+ // description: username of the followed user
// type: string
// required: true
// responses:
@@ -216,7 +216,7 @@ func Follow(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to follow
+ // description: username of the user to follow
// type: string
// required: true
// responses:
@@ -246,7 +246,7 @@ func Unfollow(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to unfollow
+ // description: username of the user to unfollow
// type: string
// required: true
// responses:
diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go
index b76bd8a1ee..9ec4d2c938 100644
--- a/routers/api/v1/user/gpg_key.go
+++ b/routers/api/v1/user/gpg_key.go
@@ -53,7 +53,7 @@ func ListGPGKeys(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose GPG key list is to be obtained
// type: string
// required: true
// - name: page
diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go
index 628f5d6cac..aa69245e49 100644
--- a/routers/api/v1/user/key.go
+++ b/routers/api/v1/user/key.go
@@ -136,7 +136,7 @@ func ListPublicKeys(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose public keys are to be listed
// type: string
// required: true
// - name: fingerprint
diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go
index 6aabc4fb90..6d0129681e 100644
--- a/routers/api/v1/user/repo.go
+++ b/routers/api/v1/user/repo.go
@@ -19,7 +19,7 @@ import (
func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
opts := utils.GetListOptions(ctx)
- repos, count, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: u,
Private: private,
ListOptions: opts,
@@ -62,7 +62,7 @@ func ListUserRepos(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose owned repos are to be listed
// type: string
// required: true
// - name: page
@@ -103,7 +103,7 @@ func ListMyRepos(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/RepositoryList"
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
ListOptions: utils.GetListOptions(ctx),
Actor: ctx.Doer,
OwnerID: ctx.Doer.ID,
diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go
index 4b0cb45d67..ee5d63063b 100644
--- a/routers/api/v1/user/star.go
+++ b/routers/api/v1/user/star.go
@@ -50,7 +50,7 @@ func GetStarredRepos(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose starred repos are to be listed
// type: string
// required: true
// - name: page
diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go
index 757a548518..6de1125c40 100644
--- a/routers/api/v1/user/user.go
+++ b/routers/api/v1/user/user.go
@@ -73,7 +73,7 @@ func Search(ctx *context.APIContext) {
if ctx.PublicOnly {
visible = []structs.VisibleType{structs.VisibleTypePublic}
}
- users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ users, maxResults, err = user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: uid,
@@ -110,7 +110,7 @@ func GetInfo(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to get
+ // description: username of the user whose data is to be listed
// type: string
// required: true
// responses:
@@ -151,7 +151,7 @@ func GetUserHeatmapData(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user to get
+ // description: username of the user whose heatmap is to be obtained
// type: string
// required: true
// responses:
@@ -177,7 +177,7 @@ func ListUserActivityFeeds(ctx *context.APIContext) {
// parameters:
// - name: username
// in: path
- // description: username of user
+ // description: username of the user whose activity feeds are to be listed
// type: string
// required: true
// - name: only-performed-by
diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go
index 76d7c81793..844eac2c67 100644
--- a/routers/api/v1/user/watch.go
+++ b/routers/api/v1/user/watch.go
@@ -49,7 +49,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
// - name: username
// type: string
// in: path
- // description: username of the user
+ // description: username of the user whose watched repos are to be listed
// required: true
// - name: page
// in: query
diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go
index 532d157e35..6f598f14c8 100644
--- a/routers/api/v1/utils/hook.go
+++ b/routers/api/v1/utils/hook.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/validation"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook"
@@ -92,6 +93,10 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
return false
}
+ if !validation.IsValidURL(form.Config["url"]) {
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
+ return false
+ }
return true
}
@@ -154,6 +159,42 @@ func pullHook(events []string, event string) bool {
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
}
+func updateHookEvents(events []string) webhook_module.HookEvents {
+ if len(events) == 0 {
+ events = []string{"push"}
+ }
+ hookEvents := make(webhook_module.HookEvents)
+ hookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(events, string(webhook_module.HookEventCreate), true)
+ hookEvents[webhook_module.HookEventPush] = util.SliceContainsString(events, string(webhook_module.HookEventPush), true)
+ hookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(events, string(webhook_module.HookEventDelete), true)
+ hookEvents[webhook_module.HookEventFork] = util.SliceContainsString(events, string(webhook_module.HookEventFork), true)
+ hookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(events, string(webhook_module.HookEventRepository), true)
+ hookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(events, string(webhook_module.HookEventWiki), true)
+ hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
+ hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
+ hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
+ hookEvents[webhook_module.HookEventWorkflowRun] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowRun), true)
+ hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
+
+ // Issues
+ hookEvents[webhook_module.HookEventIssues] = issuesHook(events, "issues_only")
+ hookEvents[webhook_module.HookEventIssueAssign] = issuesHook(events, string(webhook_module.HookEventIssueAssign))
+ hookEvents[webhook_module.HookEventIssueLabel] = issuesHook(events, string(webhook_module.HookEventIssueLabel))
+ hookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(events, string(webhook_module.HookEventIssueMilestone))
+ hookEvents[webhook_module.HookEventIssueComment] = issuesHook(events, string(webhook_module.HookEventIssueComment))
+
+ // Pull requests
+ hookEvents[webhook_module.HookEventPullRequest] = pullHook(events, "pull_request_only")
+ hookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(events, string(webhook_module.HookEventPullRequestAssign))
+ hookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(events, string(webhook_module.HookEventPullRequestLabel))
+ hookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(events, string(webhook_module.HookEventPullRequestMilestone))
+ hookEvents[webhook_module.HookEventPullRequestComment] = pullHook(events, string(webhook_module.HookEventPullRequestComment))
+ hookEvents[webhook_module.HookEventPullRequestReview] = pullHook(events, "pull_request_review")
+ hookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(events, string(webhook_module.HookEventPullRequestReviewRequest))
+ hookEvents[webhook_module.HookEventPullRequestSync] = pullHook(events, string(webhook_module.HookEventPullRequestSync))
+ return hookEvents
+}
+
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
// an error, write to `ctx` accordingly. Return (webhook, ok)
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
@@ -162,9 +203,6 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
return nil, false
}
- if len(form.Events) == 0 {
- form.Events = []string{"push"}
- }
if form.Config["is_system_webhook"] != "" {
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
if err != nil {
@@ -183,31 +221,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
IsSystemWebhook: isSystemWebhook,
HookEvent: &webhook_module.HookEvent{
ChooseEvents: true,
- HookEvents: webhook_module.HookEvents{
- webhook_module.HookEventCreate: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
- webhook_module.HookEventDelete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
- webhook_module.HookEventFork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
- webhook_module.HookEventIssues: issuesHook(form.Events, "issues_only"),
- webhook_module.HookEventIssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
- webhook_module.HookEventIssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
- webhook_module.HookEventIssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
- webhook_module.HookEventIssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
- webhook_module.HookEventPush: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
- webhook_module.HookEventPullRequest: pullHook(form.Events, "pull_request_only"),
- webhook_module.HookEventPullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
- webhook_module.HookEventPullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
- webhook_module.HookEventPullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
- webhook_module.HookEventPullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
- webhook_module.HookEventPullRequestReview: pullHook(form.Events, "pull_request_review"),
- webhook_module.HookEventPullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
- webhook_module.HookEventPullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
- webhook_module.HookEventWiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
- webhook_module.HookEventRepository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
- webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
- webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
- webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
- webhook_module.HookEventWorkflowJob: util.SliceContainsString(form.Events, string(webhook_module.HookEventWorkflowJob), true),
- },
+ HookEvents: updateHookEvents(form.Events),
BranchFilter: form.BranchFilter,
},
IsActive: form.Active,
@@ -324,6 +338,10 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
if form.Config != nil {
if url, ok := form.Config["url"]; ok {
+ if !validation.IsValidURL(url) {
+ ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
+ return false
+ }
w.URL = url
}
if ct, ok := form.Config["content_type"]; ok {
@@ -352,19 +370,10 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
}
// Update events
- if len(form.Events) == 0 {
- form.Events = []string{"push"}
- }
+ w.HookEvents = updateHookEvents(form.Events)
w.PushOnly = false
w.SendEverything = false
w.ChooseEvents = true
- w.HookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
- w.HookEvents[webhook_module.HookEventPush] = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
- w.HookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
- w.HookEvents[webhook_module.HookEventFork] = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
- w.HookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
- w.HookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true)
- w.HookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
w.BranchFilter = form.BranchFilter
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
@@ -373,23 +382,6 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
return false
}
- // Issues
- w.HookEvents[webhook_module.HookEventIssues] = issuesHook(form.Events, "issues_only")
- w.HookEvents[webhook_module.HookEventIssueAssign] = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
- w.HookEvents[webhook_module.HookEventIssueLabel] = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
- w.HookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
- w.HookEvents[webhook_module.HookEventIssueComment] = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
-
- // Pull requests
- w.HookEvents[webhook_module.HookEventPullRequest] = pullHook(form.Events, "pull_request_only")
- w.HookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
- w.HookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
- w.HookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
- w.HookEvents[webhook_module.HookEventPullRequestComment] = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
- w.HookEvents[webhook_module.HookEventPullRequestReview] = pullHook(form.Events, "pull_request_review")
- w.HookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
- w.HookEvents[webhook_module.HookEventPullRequestSync] = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
-
if err := w.UpdateEvent(); err != nil {
ctx.APIErrorInternal(err)
return false
diff --git a/routers/api/v1/utils/hook_test.go b/routers/api/v1/utils/hook_test.go
new file mode 100644
index 0000000000..e5e8ce07ce
--- /dev/null
+++ b/routers/api/v1/utils/hook_test.go
@@ -0,0 +1,82 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package utils
+
+import (
+ "net/http"
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/contexttest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestTestHookValidation(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+
+ t.Run("Test Validation", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "gitea",
+ Config: map[string]string{
+ "content_type": "json",
+ "url": "https://example.com/webhook",
+ },
+ })
+ assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet
+ })
+
+ t.Run("Test Validation with invalid URL", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "gitea",
+ Config: map[string]string{
+ "content_type": "json",
+ "url": "example.com/webhook",
+ },
+ })
+ assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
+ })
+
+ t.Run("Test Validation with invalid webhook type", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "unknown",
+ Config: map[string]string{
+ "content_type": "json",
+ "url": "example.com/webhook",
+ },
+ })
+ assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
+ })
+
+ t.Run("Test Validation with empty content type", func(t *testing.T) {
+ ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
+ contexttest.LoadRepo(t, ctx, 1)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+
+ checkCreateHookOption(ctx, &structs.CreateHookOption{
+ Type: "unknown",
+ Config: map[string]string{
+ "url": "https://example.com/webhook",
+ },
+ })
+ assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
+ })
+}
diff --git a/routers/api/v1/utils/main_test.go b/routers/api/v1/utils/main_test.go
new file mode 100644
index 0000000000..4eace1f369
--- /dev/null
+++ b/routers/api/v1/utils/main_test.go
@@ -0,0 +1,21 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package utils
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/setting"
+ webhook_service "code.gitea.io/gitea/services/webhook"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m, &unittest.TestOptions{
+ SetUp: func() error {
+ setting.LoadQueueSettings()
+ return webhook_service.Init()
+ },
+ })
+}
diff --git a/routers/common/markup.go b/routers/common/markup.go
index 4c77ff33ed..00b2dd07c6 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -76,7 +76,11 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
})
rctx = rctx.WithMarkupType(markdown.MarkupName)
case "comment":
- rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName})
+ rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{
+ DeprecatedOwnerName: repoOwnerName,
+ DeprecatedRepoName: repoName,
+ FootnoteContextID: "preview",
+ })
rctx = rctx.WithMarkupType(markdown.MarkupName)
case "wiki":
rctx = renderhelper.NewRenderContextRepoWiki(ctx, repoModel, renderhelper.RepoWikiOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName})
diff --git a/routers/common/pagetmpl.go b/routers/common/pagetmpl.go
index 52c9fceba3..c48596d48b 100644
--- a/routers/common/pagetmpl.go
+++ b/routers/common/pagetmpl.go
@@ -6,6 +6,7 @@ package common
import (
goctx "context"
"errors"
+ "sync"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
@@ -22,8 +23,7 @@ type StopwatchTmplInfo struct {
Seconds int64
}
-func getActiveStopwatch(goCtx goctx.Context) *StopwatchTmplInfo {
- ctx := context.GetWebContext(goCtx)
+func getActiveStopwatch(ctx *context.Context) *StopwatchTmplInfo {
if ctx.Doer == nil {
return nil
}
@@ -48,8 +48,7 @@ func getActiveStopwatch(goCtx goctx.Context) *StopwatchTmplInfo {
}
}
-func notificationUnreadCount(goCtx goctx.Context) int64 {
- ctx := context.GetWebContext(goCtx)
+func notificationUnreadCount(ctx *context.Context) int64 {
if ctx.Doer == nil {
return 0
}
@@ -66,10 +65,19 @@ func notificationUnreadCount(goCtx goctx.Context) int64 {
return count
}
-func PageTmplFunctions(ctx *context.Context) {
- if ctx.IsSigned {
- // defer the function call to the last moment when the tmpl renders
- ctx.Data["NotificationUnreadCount"] = notificationUnreadCount
- ctx.Data["GetActiveStopwatch"] = getActiveStopwatch
- }
+type pageGlobalDataType struct {
+ IsSigned bool
+ IsSiteAdmin bool
+
+ GetNotificationUnreadCount func() int64
+ GetActiveStopwatch func() *StopwatchTmplInfo
+}
+
+func PageGlobalData(ctx *context.Context) {
+ var data pageGlobalDataType
+ data.IsSigned = ctx.Doer != nil
+ data.IsSiteAdmin = ctx.Doer != nil && ctx.Doer.IsAdmin
+ data.GetNotificationUnreadCount = sync.OnceValue(func() int64 { return notificationUnreadCount(ctx) })
+ data.GetActiveStopwatch = sync.OnceValue(func() *StopwatchTmplInfo { return getActiveStopwatch(ctx) })
+ ctx.Data["PageGlobalData"] = data
}
diff --git a/routers/install/install.go b/routers/install/install.go
index 2962f3948f..dc8f209f3b 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "slices"
"strconv"
"strings"
"time"
@@ -98,14 +99,7 @@ func Install(ctx *context.Context) {
form.SSLMode = setting.Database.SSLMode
curDBType := setting.Database.Type.String()
- var isCurDBTypeSupported bool
- for _, dbType := range setting.SupportedDatabaseTypes {
- if dbType == curDBType {
- isCurDBTypeSupported = true
- break
- }
- }
- if !isCurDBTypeSupported {
+ if !slices.Contains(setting.SupportedDatabaseTypes, curDBType) {
curDBType = "mysql"
}
ctx.Data["CurDbType"] = curDBType
@@ -606,6 +600,8 @@ func SubmitInstall(ctx *context.Context) {
// InstallDone shows the "post-install" page, makes it easier to develop the page.
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
-func InstallDone(ctx *context.Context) { //nolint
+func InstallDone(ctx *context.Context) { //nolint:revive // export stutter
+ hasUsers, _ := user_model.HasUsers(ctx)
+ ctx.Data["IsAccountCreated"] = hasUsers.HasAnyUser
ctx.HTML(http.StatusOK, tplPostInstall)
}
diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go
index 8b1e849e7a..e8bef7d6c1 100644
--- a/routers/private/hook_post_receive.go
+++ b/routers/private/hook_post_receive.go
@@ -207,25 +207,19 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
return
}
- cols := make([]string, 0, 2)
-
- if isPrivate.Has() {
+ // FIXME: these options are not quite right, for example: changing visibility should do more works than just setting the is_private flag
+ // These options should only be used for "push-to-create"
+ if isPrivate.Has() && repo.IsPrivate != isPrivate.Value() {
+ // TODO: it needs to do more work
repo.IsPrivate = isPrivate.Value()
- cols = append(cols, "is_private")
+ if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
+ ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Failed to change visibility"})
+ }
}
-
- if isTemplate.Has() {
+ if isTemplate.Has() && repo.IsTemplate != isTemplate.Value() {
repo.IsTemplate = isTemplate.Value()
- cols = append(cols, "is_template")
- }
-
- if len(cols) > 0 {
- if err := repo_model.UpdateRepositoryCols(ctx, repo, cols...); err != nil {
- log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
- ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
- Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
- })
- return
+ if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_template"); err != nil {
+ ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Failed to change template status"})
}
}
}
diff --git a/routers/private/hook_verification_test.go b/routers/private/hook_verification_test.go
index f6c2e1087f..8653e34daa 100644
--- a/routers/private/hook_verification_test.go
+++ b/routers/private/hook_verification_test.go
@@ -18,7 +18,9 @@ func TestVerifyCommits(t *testing.T) {
unittest.PrepareTestEnv(t)
gitRepo, err := git.OpenRepository(t.Context(), testReposDir+"repo1_hook_verification")
- defer gitRepo.Close()
+ if err != nil {
+ defer gitRepo.Close()
+ }
assert.NoError(t, err)
objectFormat, err := gitRepo.GetObjectFormat()
diff --git a/routers/private/serv.go b/routers/private/serv.go
index 37fbc0730c..b879be0dc2 100644
--- a/routers/private/serv.go
+++ b/routers/private/serv.go
@@ -81,6 +81,7 @@ func ServCommand(ctx *context.PrivateContext) {
ownerName := ctx.PathParam("owner")
repoName := ctx.PathParam("repo")
mode := perm.AccessMode(ctx.FormInt("mode"))
+ verb := ctx.FormString("verb")
// Set the basic parts of the results to return
results := private.ServCommandResults{
@@ -295,8 +296,11 @@ func ServCommand(ctx *context.PrivateContext) {
return
}
} else {
- // Because of the special ref "refs/for" we will need to delay write permission check
- if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
+ // Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
+ // AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
+ // The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
+ // Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
+ if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == git.CmdVerbReceivePack {
mode = perm.AccessModeRead
}
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 80d554b6e3..0f6f31b884 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -177,7 +177,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
customURLMapping = nil
}
var scopes []string
- for _, s := range strings.Split(form.Oauth2Scopes, ",") {
+ for s := range strings.SplitSeq(form.Oauth2Scopes, ",") {
s = strings.TrimSpace(s)
if s != "" {
scopes = append(scopes, s)
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
index 520f14e89f..0e5b23db6d 100644
--- a/routers/web/admin/config.go
+++ b/routers/web/admin/config.go
@@ -61,7 +61,7 @@ func TestCache(ctx *context.Context) {
func shadowPasswordKV(cfgItem, splitter string) string {
fields := strings.Split(cfgItem, splitter)
- for i := 0; i < len(fields); i++ {
+ for i := range fields {
if strings.HasPrefix(fields[i], "password=") {
fields[i] = "password=******"
break
@@ -200,7 +200,7 @@ func ChangeConfig(ctx *context.Context) {
value := ctx.FormString("value")
cfg := setting.Config()
- marshalBool := func(v string) (string, error) { //nolint:unparam
+ marshalBool := func(v string) (string, error) { //nolint:unparam // error is always nil
if b, _ := strconv.ParseBool(v); b {
return "true", nil
}
diff --git a/routers/web/admin/diagnosis.go b/routers/web/admin/diagnosis.go
index d040dbe0ba..5395529d66 100644
--- a/routers/web/admin/diagnosis.go
+++ b/routers/web/admin/diagnosis.go
@@ -16,13 +16,7 @@ import (
)
func MonitorDiagnosis(ctx *context.Context) {
- seconds := ctx.FormInt64("seconds")
- if seconds <= 1 {
- seconds = 1
- }
- if seconds > 300 {
- seconds = 300
- }
+ seconds := min(max(ctx.FormInt64("seconds"), 1), 300)
httplib.ServeSetHeaders(ctx.Resp, &httplib.ServeHeaderOptions{
ContentType: "application/zip",
diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go
index 21a8ab0d17..e9d6abbe92 100644
--- a/routers/web/admin/notice.go
+++ b/routers/web/admin/notice.go
@@ -26,10 +26,7 @@ func Notices(ctx *context.Context) {
ctx.Data["PageIsAdminNotices"] = true
total := system_model.CountNotices(ctx)
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
notices, err := system_model.Notices(ctx, page, setting.UI.Admin.NoticePagingNum)
if err != nil {
diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go
index 35e61efa17..e34f203aaf 100644
--- a/routers/web/admin/orgs.go
+++ b/routers/web/admin/orgs.go
@@ -27,7 +27,7 @@ func Organizations(ctx *context.Context) {
ctx.SetFormString("sort", UserSearchDefaultAdminSort)
}
- explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
+ explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
IncludeReserved: true, // administrator needs to list all accounts include reserved
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index 5122342259..1904bfee11 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -24,10 +24,7 @@ const (
// Packages shows all packages
func Packages(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
query := ctx.FormTrim("q")
packageType := ctx.FormTrim("type")
sort := ctx.FormTrim("sort")
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index e42cbb316c..27577cd35b 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/explore"
@@ -64,7 +65,7 @@ func Users(ctx *context.Context) {
"SortType": sortType,
}
- explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
+ explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{
@@ -268,7 +269,7 @@ func ViewUser(ctx *context.Context) {
return
}
- repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptionsAll,
OwnerID: u.ID,
OrderBy: db.SearchOrderByAlphabetically,
@@ -292,9 +293,9 @@ func ViewUser(ctx *context.Context) {
ctx.Data["EmailsTotal"] = len(emails)
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
- ListOptions: db.ListOptionsAll,
- UserID: u.ID,
- IncludePrivate: true,
+ ListOptions: db.ListOptionsAll,
+ UserID: u.ID,
+ IncludeVisibility: structs.VisibleTypePrivate,
})
if err != nil {
ctx.ServerError("FindOrgs", err)
@@ -431,7 +432,7 @@ func EditUserPost(ctx *context.Context) {
Website: optional.Some(form.Website),
Location: optional.Some(form.Location),
IsActive: optional.Some(form.Active),
- IsAdmin: optional.Some(form.Admin),
+ IsAdmin: user_service.UpdateOptionFieldFromValue(form.Admin),
AllowGitHook: optional.Some(form.AllowGitHook),
AllowImportLocal: optional.Some(form.AllowImportLocal),
MaxRepoCreation: optional.Some(form.MaxRepoCreation),
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 69b9d285b7..94f75f69ff 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -421,9 +421,11 @@ func SignOut(ctx *context.Context) {
// SignUp render the register page
func SignUp(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_up")
-
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
+ hasUsers, _ := user_model.HasUsers(ctx)
+ ctx.Data["IsFirstTimeRegistration"] = !hasUsers.HasAnyUser
+
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
ctx.ServerError("UserSignUp", err)
@@ -610,10 +612,16 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any,
// sends a confirmation email if required.
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
// Auto-set admin for the only user.
- if user_model.CountUsers(ctx, nil) == 1 {
+ hasUsers, err := user_model.HasUsers(ctx)
+ if err != nil {
+ ctx.ServerError("HasUsers", err)
+ return false
+ }
+ if hasUsers.HasOnlyOneUser {
+ // the only user is the one just created, will set it as admin
opts := &user_service.UpdateOptions{
IsActive: optional.Some(true),
- IsAdmin: optional.Some(true),
+ IsAdmin: user_service.UpdateOptionFieldFromValue(true),
SetLastLogin: true,
}
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 96c1dcf358..a13b987aab 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -193,8 +193,8 @@ func SignInOAuthCallback(ctx *context.Context) {
source := authSource.Cfg.(*oauth2.Source)
isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser)
- u.IsAdmin = isAdmin.ValueOrDefault(false)
- u.IsRestricted = isRestricted.ValueOrDefault(false)
+ u.IsAdmin = isAdmin.ValueOrDefault(user_service.UpdateOptionField[bool]{FieldValue: false}).FieldValue
+ u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted)
if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
// error already handled
@@ -258,11 +258,11 @@ func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[
return claimValueToStringSet(groupClaims)
}
-func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin, isRestricted optional.Option[bool]) {
+func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin optional.Option[user_service.UpdateOptionField[bool]], isRestricted optional.Option[bool]) {
groups := getClaimedGroups(source, gothUser)
if source.AdminGroup != "" {
- isAdmin = optional.Some(groups.Contains(source.AdminGroup))
+ isAdmin = user_service.UpdateOptionFieldFromSync(groups.Contains(source.AdminGroup))
}
if source.RestrictedGroup != "" {
isRestricted = optional.Some(groups.Contains(source.RestrictedGroup))
diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go
index 1804b5b193..dc9f34fd44 100644
--- a/routers/web/auth/oauth2_provider.go
+++ b/routers/web/auth/oauth2_provider.go
@@ -4,18 +4,16 @@
package auth
import (
- "errors"
"fmt"
"html"
"html/template"
"net/http"
"net/url"
"strconv"
- "strings"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/auth/httpauth"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -108,9 +106,8 @@ func InfoOAuth(ctx *context.Context) {
var accessTokenScope auth.AccessTokenScope
if auHead := ctx.Req.Header.Get("Authorization"); auHead != "" {
- auths := strings.Fields(auHead)
- if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") {
- accessTokenScope, _ = auth_service.GetOAuthAccessTokenScopeAndUserID(ctx, auths[1])
+ if parsed, ok := httpauth.ParseAuthorizationHeader(auHead); ok && parsed.BearerToken != nil {
+ accessTokenScope, _ = auth_service.GetOAuthAccessTokenScopeAndUserID(ctx, parsed.BearerToken.Token)
}
}
@@ -127,18 +124,12 @@ func InfoOAuth(ctx *context.Context) {
ctx.JSON(http.StatusOK, response)
}
-func parseBasicAuth(ctx *context.Context) (username, password string, err error) {
- authHeader := ctx.Req.Header.Get("Authorization")
- if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") {
- return base.BasicAuthDecode(authData)
- }
- return "", "", errors.New("invalid basic authentication")
-}
-
// IntrospectOAuth introspects an oauth token
func IntrospectOAuth(ctx *context.Context) {
clientIDValid := false
- if clientID, clientSecret, err := parseBasicAuth(ctx); err == nil {
+ authHeader := ctx.Req.Header.Get("Authorization")
+ if parsed, ok := httpauth.ParseAuthorizationHeader(authHeader); ok && parsed.BasicAuth != nil {
+ clientID, clientSecret := parsed.BasicAuth.Username, parsed.BasicAuth.Password
app, err := auth.GetOAuth2ApplicationByClientID(ctx, clientID)
if err != nil && !auth.IsErrOauthClientIDInvalid(err) {
// this is likely a database error; log it and respond without details
@@ -465,16 +456,16 @@ func AccessTokenOAuth(ctx *context.Context) {
form := *web.GetForm(ctx).(*forms.AccessTokenForm)
// if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header
if form.ClientID == "" || form.ClientSecret == "" {
- authHeader := ctx.Req.Header.Get("Authorization")
- if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") {
- clientID, clientSecret, err := base.BasicAuthDecode(authData)
- if err != nil {
+ if authHeader := ctx.Req.Header.Get("Authorization"); authHeader != "" {
+ parsed, ok := httpauth.ParseAuthorizationHeader(authHeader)
+ if !ok || parsed.BasicAuth == nil {
handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "cannot parse basic auth header",
})
return
}
+ clientID, clientSecret := parsed.BasicAuth.Username, parsed.BasicAuth.Password
// validate that any fields present in the form match the Basic auth header
if form.ClientID != "" && form.ClientID != clientID {
handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index 8c2d3276a8..2ef4a86022 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -349,10 +349,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
context.VerifyCaptcha(ctx, tplSignUpOID, form)
}
- length := setting.MinPasswordLength
- if length < 256 {
- length = 256
- }
+ length := max(setting.MinPasswordLength, 256)
password, err := util.CryptoRandomString(int64(length))
if err != nil {
ctx.RenderWithErr(err.Error(), tplSignUpOID, form)
diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go
index 765931a730..a22d376579 100644
--- a/routers/web/devtest/devtest.go
+++ b/routers/web/devtest/devtest.go
@@ -132,7 +132,7 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
selectedStyle := ctx.FormString("style", badge.DefaultStyle)
var badges []badge.Badge
badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green"))
- for r := rune(0); r < 256; r++ {
+ for r := range rune(256) {
if unicode.IsPrint(r) {
s := strings.Repeat(string(r), 15)
badges = append(badges, badge.GenerateBadge(s, util.TruncateRunes(s, 7), "green"))
diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go
index 8f6518a4fc..3bb50ef397 100644
--- a/routers/web/explore/code.go
+++ b/routers/web/explore/code.go
@@ -5,6 +5,7 @@ package explore
import (
"net/http"
+ "slices"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
@@ -93,14 +94,7 @@ func Code(ctx *context.Context) {
loadRepoIDs := make([]int64, 0, len(searchResults))
for _, result := range searchResults {
- var find bool
- for _, id := range loadRepoIDs {
- if id == result.RepoID {
- find = true
- break
- }
- }
- if !find {
+ if !slices.Contains(loadRepoIDs, result.RepoID) {
loadRepoIDs = append(loadRepoIDs, result.RepoID)
}
}
diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go
index 7bb71acfd7..f8f7f5c18c 100644
--- a/routers/web/explore/org.go
+++ b/routers/web/explore/org.go
@@ -44,7 +44,7 @@ func Organizations(ctx *context.Context) {
ctx.SetFormString("sort", sortOrder)
}
- RenderUserSearch(ctx, &user_model.SearchUserOptions{
+ RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go
index cf3128314b..f0d7d0ce7d 100644
--- a/routers/web/explore/repo.go
+++ b/routers/web/explore/repo.go
@@ -94,7 +94,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
private := ctx.FormOptionalBool("private")
ctx.Data["IsPrivate"] = private
- repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: opts.PageSize,
@@ -151,6 +151,7 @@ func Repos(ctx *context.Context) {
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
+ ctx.Data["ShowRepoOwnerOnList"] = true
ctx.Data["PageIsExploreRepositories"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go
index e1e1ec1cfd..af48e6fb79 100644
--- a/routers/web/explore/user.go
+++ b/routers/web/explore/user.go
@@ -32,7 +32,7 @@ func isKeywordValid(keyword string) bool {
}
// RenderUserSearch render user search page
-func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName templates.TplName) {
+func RenderUserSearch(ctx *context.Context, opts user_model.SearchUserOptions, tplName templates.TplName) {
// Sitemap index for sitemap paths
opts.Page = int(ctx.PathParamInt64("idx"))
isSitemap := ctx.PathParam("idx") != ""
@@ -151,7 +151,7 @@ func Users(ctx *context.Context) {
ctx.SetFormString("sort", sortOrder)
}
- RenderUserSearch(ctx, &user_model.SearchUserOptions{
+ RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go
index 4a6fe9603d..094fd987ac 100644
--- a/routers/web/feed/branch.go
+++ b/routers/web/feed/branch.go
@@ -15,7 +15,7 @@ import (
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
- commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "")
+ commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "", "", "")
if err != nil {
ctx.ServerError("ShowBranchFeed", err)
return
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index b04855fa6a..7c59132841 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -201,7 +201,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
switch act.OpType {
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
push := templates.ActionContent2Commits(act)
-
+ _ = act.LoadRepo(ctx)
for _, commit := range push.Commits {
if len(desc) != 0 {
desc += "\n\n"
@@ -209,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
commit.Sha1,
- renderUtils.RenderCommitMessage(commit.Message, nil),
+ renderUtils.RenderCommitMessage(commit.Message, act.Repo),
)
}
diff --git a/routers/web/home.go b/routers/web/home.go
index 208cc36dfb..4b15ee83c2 100644
--- a/routers/web/home.go
+++ b/routers/web/home.go
@@ -68,7 +68,7 @@ func Home(ctx *context.Context) {
func HomeSitemap(ctx *context.Context) {
m := sitemap.NewSitemapIndex()
if !setting.Service.Explore.DisableUsersPage {
- _, cnt, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ _, cnt, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: 1},
IsActive: optional.Some(true),
@@ -86,7 +86,7 @@ func HomeSitemap(ctx *context.Context) {
}
}
- _, cnt, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ _, cnt, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: 1,
},
diff --git a/routers/web/misc/markup.go b/routers/web/misc/markup.go
index 0c7ec6c2eb..f90cf3ffed 100644
--- a/routers/web/misc/markup.go
+++ b/routers/web/misc/markup.go
@@ -15,6 +15,6 @@ import (
// Markup render markup document to HTML
func Markup(ctx *context.Context) {
form := web.GetForm(ctx).(*api.MarkupOption)
- mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck
+ mode := util.Iif(form.Wiki, "wiki", form.Mode) //nolint:staticcheck // form.Wiki is deprecated
common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, form.FilePath)
}
diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go
index d42afafe9e..59b97c1717 100644
--- a/routers/web/misc/misc.go
+++ b/routers/web/misc/misc.go
@@ -20,7 +20,7 @@ func SSHInfo(rw http.ResponseWriter, req *http.Request) {
return
}
rw.Header().Set("content-type", "text/json;charset=UTF-8")
- _, err := rw.Write([]byte(`{"type":"gitea","version":1}`))
+ _, err := rw.Write([]byte(`{"type":"agit","version":1}`))
if err != nil {
log.Error("fail to write result: err: %v", err)
rw.WriteHeader(http.StatusInternalServerError)
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index 8981af1691..63ae6c683b 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -115,7 +115,7 @@ func home(ctx *context.Context, viewRepositories bool) {
ctx.Data["PageIsViewOverview"] = isViewOverview
ctx.Data["ShowOrgProfileReadmeSelector"] = isViewOverview && prepareResult.ProfilePublicReadmeBlob != nil && prepareResult.ProfilePrivateReadmeBlob != nil
- repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
Page: page,
diff --git a/routers/web/org/members.go b/routers/web/org/members.go
index 2cbe75989a..61022d3f09 100644
--- a/routers/web/org/members.go
+++ b/routers/web/org/members.go
@@ -28,10 +28,7 @@ func Members(ctx *context.Context) {
ctx.Data["Title"] = org.FullName
ctx.Data["PageIsOrgMembers"] = true
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
opts := &organization.FindOrgMembersOpts{
Doer: ctx.Doer,
diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go
index 456ed3f01e..2a4aa7f557 100644
--- a/routers/web/org/org_labels.go
+++ b/routers/web/org/org_labels.go
@@ -4,13 +4,15 @@
package org
import (
- "net/http"
+ "errors"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/label"
repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ shared_label "code.gitea.io/gitea/routers/web/shared/label"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
@@ -32,14 +34,8 @@ func RetrieveLabels(ctx *context.Context) {
// NewLabel create new label for organization
func NewLabel(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.CreateLabelForm)
- ctx.Data["Title"] = ctx.Tr("repo.labels")
- ctx.Data["PageIsLabels"] = true
- ctx.Data["PageIsOrgSettings"] = true
-
- if ctx.HasError() {
- ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
- ctx.Redirect(ctx.Org.OrgLink + "/settings/labels")
+ form := shared_label.GetLabelEditForm(ctx)
+ if ctx.Written() {
return
}
@@ -55,20 +51,22 @@ func NewLabel(ctx *context.Context) {
ctx.ServerError("NewLabel", err)
return
}
- ctx.Redirect(ctx.Org.OrgLink + "/settings/labels")
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/labels")
}
// UpdateLabel update a label's name and color
func UpdateLabel(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.CreateLabelForm)
+ form := shared_label.GetLabelEditForm(ctx)
+ if ctx.Written() {
+ return
+ }
+
l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, form.ID)
- if err != nil {
- switch {
- case issues_model.IsErrOrgLabelNotExist(err):
- ctx.HTTPError(http.StatusNotFound)
- default:
- ctx.ServerError("UpdateLabel", err)
- }
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.JSONErrorNotFound()
+ return
+ } else if err != nil {
+ ctx.ServerError("GetLabelInOrgByID", err)
return
}
@@ -82,7 +80,7 @@ func UpdateLabel(ctx *context.Context) {
ctx.ServerError("UpdateLabel", err)
return
}
- ctx.Redirect(ctx.Org.OrgLink + "/settings/labels")
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/labels")
}
// DeleteLabel delete a label
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index f423e9cb36..059cce8281 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -53,10 +53,7 @@ func Projects(ctx *context.Context) {
isShowClosed := strings.ToLower(ctx.FormTrim("state")) == "closed"
keyword := ctx.FormTrim("q")
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
var projectType project_model.Type
if ctx.ContextUser.IsOrganization() {
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index 82c3bce722..2bc1e8bc43 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -18,6 +18,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
@@ -31,8 +32,6 @@ import (
const (
// tplSettingsOptions template path for render settings
tplSettingsOptions templates.TplName = "org/settings/options"
- // tplSettingsDelete template path for render delete repository
- tplSettingsDelete templates.TplName = "org/settings/delete"
// tplSettingsHooks template path for render hook settings
tplSettingsHooks templates.TplName = "org/settings/hooks"
// tplSettingsLabels template path for render labels settings
@@ -71,26 +70,6 @@ func SettingsPost(ctx *context.Context) {
org := ctx.Org.Organization
- if org.Name != form.Name {
- if err := user_service.RenameUser(ctx, org.AsUser(), form.Name); err != nil {
- if user_model.IsErrUserAlreadyExist(err) {
- ctx.Data["Err_Name"] = true
- ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
- } else if db.IsErrNameReserved(err) {
- ctx.Data["Err_Name"] = true
- ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
- } else if db.IsErrNamePatternNotAllowed(err) {
- ctx.Data["Err_Name"] = true
- ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
- } else {
- ctx.ServerError("RenameUser", err)
- }
- return
- }
-
- ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(org.Name)
- }
-
if form.Email != "" {
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
ctx.Data["Err_Email"] = true
@@ -120,7 +99,7 @@ func SettingsPost(ctx *context.Context) {
// update forks visibility
if visibilityChanged {
- repos, _, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
+ repos, _, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: org.AsUser(), Private: true, ListOptions: db.ListOptions{Page: 1, PageSize: org.NumRepos},
})
if err != nil {
@@ -163,42 +142,27 @@ func SettingsDeleteAvatar(ctx *context.Context) {
ctx.JSONRedirect(ctx.Org.OrgLink + "/settings")
}
-// SettingsDelete response for deleting an organization
-func SettingsDelete(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("org.settings")
- ctx.Data["PageIsOrgSettings"] = true
- ctx.Data["PageIsSettingsDelete"] = true
-
- if ctx.Req.Method == http.MethodPost {
- if ctx.Org.Organization.Name != ctx.FormString("org_name") {
- ctx.Data["Err_OrgName"] = true
- ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_org_name"), tplSettingsDelete, nil)
- return
- }
-
- if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
- if repo_model.IsErrUserOwnRepos(err) {
- ctx.Flash.Error(ctx.Tr("form.org_still_own_repo"))
- ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
- } else if packages_model.IsErrUserOwnPackages(err) {
- ctx.Flash.Error(ctx.Tr("form.org_still_own_packages"))
- ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
- } else {
- ctx.ServerError("DeleteOrganization", err)
- }
- } else {
- log.Trace("Organization deleted: %s", ctx.Org.Organization.Name)
- ctx.Redirect(setting.AppSubURL + "/")
- }
+// SettingsDeleteOrgPost response for deleting an organization
+func SettingsDeleteOrgPost(ctx *context.Context) {
+ if ctx.Org.Organization.Name != ctx.FormString("org_name") {
+ ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
return
}
- if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
- ctx.ServerError("RenderUserOrgHeader", err)
+ if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false /* no purge */); err != nil {
+ if repo_model.IsErrUserOwnRepos(err) {
+ ctx.JSONError(ctx.Tr("form.org_still_own_repo"))
+ } else if packages_model.IsErrUserOwnPackages(err) {
+ ctx.JSONError(ctx.Tr("form.org_still_own_packages"))
+ } else {
+ log.Error("DeleteOrganization: %v", err)
+ ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.delete_failed"))))
+ }
return
}
- ctx.HTML(http.StatusOK, tplSettingsDelete)
+ ctx.Flash.Success(ctx.Tr("org.settings.delete_successful", ctx.Org.Organization.Name))
+ ctx.JSONRedirect(setting.AppSubURL + "/")
}
// Webhooks render webhook list page
@@ -250,3 +214,40 @@ func Labels(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplSettingsLabels)
}
+
+// SettingsRenamePost response for renaming organization
+func SettingsRenamePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*forms.RenameOrgForm)
+ if ctx.HasError() {
+ ctx.JSONError(ctx.GetErrMsg())
+ return
+ }
+
+ oldOrgName, newOrgName := ctx.Org.Organization.Name, form.NewOrgName
+
+ if form.OrgName != oldOrgName {
+ ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
+ return
+ }
+ if newOrgName == oldOrgName {
+ ctx.JSONError(ctx.Tr("org.settings.rename_no_change"))
+ return
+ }
+
+ if err := user_service.RenameUser(ctx, ctx.Org.Organization.AsUser(), newOrgName); err != nil {
+ if user_model.IsErrUserAlreadyExist(err) {
+ ctx.JSONError(ctx.Tr("org.form.name_been_taken", newOrgName))
+ } else if db.IsErrNameReserved(err) {
+ ctx.JSONError(ctx.Tr("org.form.name_reserved", newOrgName))
+ } else if db.IsErrNamePatternNotAllowed(err) {
+ ctx.JSONError(ctx.Tr("org.form.name_pattern_not_allowed", newOrgName))
+ } else {
+ log.Error("RenameOrganization: %v", err)
+ ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed"))))
+ }
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("org.settings.rename_success", oldOrgName, newOrgName))
+ ctx.JSONRedirect(setting.AppSubURL + "/org/" + url.PathEscape(newOrgName) + "/settings")
+}
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index 676c6d0c63..0ec7cfddc5 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -283,11 +283,22 @@ func NewTeam(ctx *context.Context) {
}
// FIXME: TEAM-UNIT-PERMISSION: this design is not right, when a new unit is added in the future,
-// admin team won't inherit the correct admin permission for the new unit.
+// The existing teams won't inherit the correct admin permission for the new unit.
+// The full history is like this:
+// 1. There was only "team", no "team unit", so "team.authorize" was used to determine the team permission.
+// 2. Later, "team unit" was introduced, then the usage of "team.authorize" became inconsistent, and causes various bugs.
+// - Sometimes, "team.authorize" is used to determine the team permission, e.g. admin, owner
+// - Sometimes, "team unit" is used not really used and "team unit" is used.
+// - Some functions like `GetTeamsWithAccessToAnyRepoUnit` use both.
+//
+// 3. After introducing "team unit" and more unclear changes, it becomes difficult to maintain team permissions.
+// - Org owner need to click the permission for each unit, but can't just set a common "write" permission for all units.
+//
+// Ideally, "team.authorize=write" should mean the team has write access to all units including newly (future) added ones.
func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_model.Type]perm.AccessMode {
unitPerms := make(map[unit_model.Type]perm.AccessMode)
for _, ut := range unit_model.AllRepoUnitTypes {
- // Default accessmode is none
+ // Default access mode is none
unitPerms[ut] = perm.AccessModeNone
v, ok := forms[fmt.Sprintf("unit_%d", ut)]
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index 5014ff52e3..202da407d2 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -126,7 +126,7 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (
var curWorkflow *model.Workflow
- entries, err := actions.ListWorkflows(commit)
+ _, entries, err := actions.ListWorkflows(commit)
if err != nil {
ctx.ServerError("ListWorkflows", err)
return nil
@@ -317,6 +317,8 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
+
+ ctx.Data["CanWriteRepoUnitActions"] = ctx.Repo.CanWrite(unit.TypeActions)
}
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
@@ -375,10 +377,8 @@ func workflowDispatchConfig(w *model.Workflow) *WorkflowDispatch {
if !decodeNode(w.RawOn, &val) {
return nil
}
- for _, v := range val {
- if v == "workflow_dispatch" {
- return &WorkflowDispatch{}
- }
+ if slices.Contains(val, "workflow_dispatch") {
+ return &WorkflowDispatch{}
}
case yaml.MappingNode:
var val map[string]yaml.Node
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 2ec6389263..7e1b923fa4 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -64,6 +64,36 @@ func View(ctx *context_module.Context) {
ctx.HTML(http.StatusOK, tplViewActions)
}
+func ViewWorkflowFile(ctx *context_module.Context) {
+ runIndex := getRunIndex(ctx)
+ run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
+ if err != nil {
+ ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
+ return errors.Is(err, util.ErrNotExist)
+ }, err)
+ return
+ }
+ commit, err := ctx.Repo.GitRepo.GetCommit(run.CommitSHA)
+ if err != nil {
+ ctx.NotFoundOrServerError("GetCommit", func(err error) bool {
+ return errors.Is(err, util.ErrNotExist)
+ }, err)
+ return
+ }
+ rpath, entries, err := actions.ListWorkflows(commit)
+ if err != nil {
+ ctx.ServerError("ListWorkflows", err)
+ return
+ }
+ for _, entry := range entries {
+ if entry.Name() == run.WorkflowID {
+ ctx.Redirect(fmt.Sprintf("%s/src/commit/%s/%s/%s", ctx.Repo.RepoLink, url.PathEscape(run.CommitSHA), util.PathEscapeSegments(rpath), util.PathEscapeSegments(run.WorkflowID)))
+ return
+ }
+ }
+ ctx.NotFound(nil)
+}
+
type LogCursor struct {
Step int `json:"step"`
Cursor int64 `json:"cursor"`
@@ -200,13 +230,9 @@ func ViewPost(ctx *context_module.Context) {
}
}
- // TODO: "ComposeCommentMetas" (usually for comment) is not quite right, but it is still the same as what template "RenderCommitMessage" does.
- // need to be refactored together in the future
- metas := ctx.Repo.Repository.ComposeCommentMetas(ctx)
-
// the title for the "run" is from the commit message
resp.State.Run.Title = run.Title
- resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, metas)
+ resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, ctx.Repo.Repository)
resp.State.Run.Link = run.Link()
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
@@ -278,7 +304,7 @@ func ViewPost(ctx *context_module.Context) {
if task != nil {
steps, logs, err := convertToViewModel(ctx, req.LogCursors, task)
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("convertToViewModel", err)
return
}
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, steps...)
@@ -382,7 +408,7 @@ func Rerun(ctx *context_module.Context) {
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetRunByIndex", err)
return
}
@@ -400,7 +426,7 @@ func Rerun(ctx *context_module.Context) {
run.Started = 0
run.Stopped = 0
if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "previous_duration"); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("UpdateRun", err)
return
}
}
@@ -415,7 +441,7 @@ func Rerun(ctx *context_module.Context) {
// if the job has needs, it should be set to "blocked" status to wait for other jobs
shouldBlock := len(j.Needs) > 0
if err := rerunJob(ctx, j, shouldBlock); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("RerunJob", err)
return
}
}
@@ -429,7 +455,7 @@ func Rerun(ctx *context_module.Context) {
// jobs other than the specified one should be set to "blocked" status
shouldBlock := j.JobID != job.JobID
if err := rerunJob(ctx, j, shouldBlock); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("RerunJob", err)
return
}
}
@@ -459,7 +485,7 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shou
}
actions_service.CreateCommitStatus(ctx, job)
- _ = job.LoadAttributes(ctx)
+ actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
return nil
@@ -521,7 +547,7 @@ func Cancel(ctx *context_module.Context) {
}
return nil
}); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("StopTask", err)
return
}
@@ -531,7 +557,11 @@ func Cancel(ctx *context_module.Context) {
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
-
+ if len(updatedjobs) > 0 {
+ job := updatedjobs[0]
+ actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
+ notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
+ }
ctx.JSON(http.StatusOK, struct{}{})
}
@@ -567,12 +597,18 @@ func Approve(ctx *context_module.Context) {
}
return nil
}); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("UpdateRunJob", err)
return
}
actions_service.CreateCommitStatus(ctx, jobs...)
+ if len(updatedjobs) > 0 {
+ job := updatedjobs[0]
+ actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
+ notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
+ }
+
for _, job := range updatedjobs {
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
@@ -581,6 +617,33 @@ func Approve(ctx *context_module.Context) {
ctx.JSON(http.StatusOK, struct{}{})
}
+func Delete(ctx *context_module.Context) {
+ runIndex := getRunIndex(ctx)
+ repoID := ctx.Repo.Repository.ID
+
+ run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.JSONErrorNotFound()
+ return
+ }
+ ctx.ServerError("GetRunByIndex", err)
+ return
+ }
+
+ if !run.Status.IsDone() {
+ ctx.JSONError(ctx.Tr("actions.runs.not_done"))
+ return
+ }
+
+ if err := actions_service.DeleteRun(ctx, run); err != nil {
+ ctx.ServerError("DeleteRun", err)
+ return
+ }
+
+ ctx.JSONOK()
+}
+
// getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs.
// Any error will be written to the ctx.
// It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0.
@@ -588,20 +651,20 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.HTTPError(http.StatusNotFound, err.Error())
+ ctx.NotFound(nil)
return nil, nil
}
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetRunByIndex", err)
return nil, nil
}
run.Repo = ctx.Repo.Repository
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetRunJobsByRunID", err)
return nil, nil
}
if len(jobs) == 0 {
- ctx.HTTPError(http.StatusNotFound)
+ ctx.NotFound(nil)
return nil, nil
}
@@ -627,7 +690,7 @@ func ArtifactsDeleteView(ctx *context_module.Context) {
return
}
if err = actions_model.SetArtifactNeedDelete(ctx, run.ID, artifactName); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("SetArtifactNeedDelete", err)
return
}
ctx.JSON(http.StatusOK, struct{}{})
@@ -643,7 +706,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
ctx.HTTPError(http.StatusNotFound, err.Error())
return
}
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("GetRunByIndex", err)
return
}
@@ -652,7 +715,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
ArtifactName: artifactName,
})
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("FindArtifacts", err)
return
}
if len(artifacts) == 0 {
@@ -673,7 +736,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
if len(artifacts) == 1 && actions.IsArtifactV4(artifacts[0]) {
err := actions.DownloadArtifactV4(ctx.Base, artifacts[0])
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("DownloadArtifactV4", err)
return
}
return
@@ -686,7 +749,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
for _, art := range artifacts {
f, err := storage.ActionsArtifacts.Open(art.StoragePath)
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("ActionsArtifacts.Open", err)
return
}
@@ -694,7 +757,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
if art.ContentEncoding == "gzip" {
r, err = gzip.NewReader(f)
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("gzip.NewReader", err)
return
}
} else {
@@ -704,11 +767,11 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
w, err := writer.Create(art.ArtifactPath)
if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("writer.Create", err)
return
}
if _, err := io.Copy(w, r); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ ctx.ServerError("io.Copy", err)
return
}
}
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 5d963eff66..96d1d87836 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -45,10 +45,7 @@ func Branches(ctx *context.Context) {
ctx.Data["PageIsViewCode"] = true
ctx.Data["PageIsBranches"] = true
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
pageSize := setting.Git.BranchesRangeSize
kw := ctx.FormString("q")
@@ -261,10 +258,10 @@ func CreateBranch(ctx *context.Context) {
func MergeUpstream(ctx *context.Context) {
branchName := ctx.FormString("branch")
- _, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
+ _, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName, false)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
- ctx.JSONError(ctx.Tr("error.not_found"))
+ ctx.JSONErrorNotFound()
return
} else if pull_service.IsErrMergeConflicts(err) {
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go
deleted file mode 100644
index 690b830bc2..0000000000
--- a/routers/web/repo/cherry_pick.go
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package repo
-
-import (
- "bytes"
- "errors"
- "net/http"
- "strings"
-
- git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/templates"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/services/context"
- "code.gitea.io/gitea/services/forms"
- "code.gitea.io/gitea/services/repository/files"
-)
-
-var tplCherryPick templates.TplName = "repo/editor/cherry_pick"
-
-// CherryPick handles cherrypick GETs
-func CherryPick(ctx *context.Context) {
- ctx.Data["SHA"] = ctx.PathParam("sha")
- cherryPickCommit, err := ctx.Repo.GitRepo.GetCommit(ctx.PathParam("sha"))
- if err != nil {
- if git.IsErrNotExist(err) {
- ctx.NotFound(err)
- return
- }
- ctx.ServerError("GetCommit", err)
- return
- }
-
- if ctx.FormString("cherry-pick-type") == "revert" {
- ctx.Data["CherryPickType"] = "revert"
- ctx.Data["commit_summary"] = "revert " + ctx.PathParam("sha")
- ctx.Data["commit_message"] = "revert " + cherryPickCommit.Message()
- } else {
- ctx.Data["CherryPickType"] = "cherry-pick"
- splits := strings.SplitN(cherryPickCommit.Message(), "\n", 2)
- ctx.Data["commit_summary"] = splits[0]
- ctx.Data["commit_message"] = splits[1]
- }
-
- canCommit := renderCommitRights(ctx)
- ctx.Data["TreePath"] = ""
-
- if canCommit {
- ctx.Data["commit_choice"] = frmCommitChoiceDirect
- } else {
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- }
- ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
- ctx.Data["last_commit"] = ctx.Repo.CommitID
- ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
-
- ctx.HTML(http.StatusOK, tplCherryPick)
-}
-
-// CherryPickPost handles cherrypick POSTs
-func CherryPickPost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.CherryPickForm)
-
- sha := ctx.PathParam("sha")
- ctx.Data["SHA"] = sha
- if form.Revert {
- ctx.Data["CherryPickType"] = "revert"
- } else {
- ctx.Data["CherryPickType"] = "cherry-pick"
- }
-
- canCommit := renderCommitRights(ctx)
- branchName := ctx.Repo.BranchName
- if form.CommitChoice == frmCommitChoiceNewBranch {
- branchName = form.NewBranchName
- }
- ctx.Data["commit_summary"] = form.CommitSummary
- ctx.Data["commit_message"] = form.CommitMessage
- ctx.Data["commit_choice"] = form.CommitChoice
- ctx.Data["new_branch_name"] = form.NewBranchName
- ctx.Data["last_commit"] = ctx.Repo.CommitID
- ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
-
- if ctx.HasError() {
- ctx.HTML(http.StatusOK, tplCherryPick)
- return
- }
-
- // Cannot commit to a an existing branch if user doesn't have rights
- if branchName == ctx.Repo.BranchName && !canCommit {
- ctx.Data["Err_NewBranchName"] = true
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplCherryPick, &form)
- return
- }
-
- message := strings.TrimSpace(form.CommitSummary)
- if message == "" {
- if form.Revert {
- message = ctx.Locale.TrString("repo.commit.revert-header", sha)
- } else {
- message = ctx.Locale.TrString("repo.commit.cherry-pick-header", sha)
- }
- }
-
- form.CommitMessage = strings.TrimSpace(form.CommitMessage)
- if len(form.CommitMessage) > 0 {
- message += "\n\n" + form.CommitMessage
- }
-
- gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, form.CommitEmail)
- if !valid {
- ctx.Data["Err_CommitEmail"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_email"), tplCherryPick, &form)
- return
- }
- opts := &files.ApplyDiffPatchOptions{
- LastCommitID: form.LastCommit,
- OldBranch: ctx.Repo.BranchName,
- NewBranch: branchName,
- Message: message,
- Author: gitCommitter,
- Committer: gitCommitter,
- }
-
- // First lets try the simple plain read-tree -m approach
- opts.Content = sha
- if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, form.Revert, opts); err != nil {
- if git_model.IsErrBranchAlreadyExists(err) {
- // User has specified a branch that already exists
- branchErr := err.(git_model.ErrBranchAlreadyExists)
- ctx.Data["Err_NewBranchName"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
- return
- } else if files.IsErrCommitIDDoesNotMatch(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form)
- return
- }
- // Drop through to the apply technique
-
- buf := &bytes.Buffer{}
- if form.Revert {
- if err := git.GetReverseRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, buf); err != nil {
- if git.IsErrNotExist(err) {
- ctx.NotFound(errors.New("commit " + ctx.PathParam("sha") + " does not exist."))
- return
- }
- ctx.ServerError("GetRawDiff", err)
- return
- }
- } else {
- if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, git.RawDiffType("patch"), buf); err != nil {
- if git.IsErrNotExist(err) {
- ctx.NotFound(errors.New("commit " + ctx.PathParam("sha") + " does not exist."))
- return
- }
- ctx.ServerError("GetRawDiff", err)
- return
- }
- }
-
- opts.Content = buf.String()
- ctx.Data["FileContent"] = opts.Content
-
- if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
- if git_model.IsErrBranchAlreadyExists(err) {
- // User has specified a branch that already exists
- branchErr := err.(git_model.ErrBranchAlreadyExists)
- ctx.Data["Err_NewBranchName"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
- return
- } else if files.IsErrCommitIDDoesNotMatch(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form)
- return
- }
- ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_apply_patch", err), tplPatchFile, &form)
- return
- }
- }
-
- if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
- ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
- } else {
- ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName))
- }
-}
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index ae5baa9c47..8f5c6a42e6 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -67,10 +67,7 @@ func Commits(ctx *context.Context) {
commitsCount := ctx.Repo.CommitsCount
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
pageSize := ctx.FormInt("limit")
if pageSize <= 0 {
@@ -78,7 +75,7 @@ func Commits(ctx *context.Context) {
}
// Both `git log branchName` and `git log commitId` work.
- commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "")
+ commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "", "", "")
if err != nil {
ctx.ServerError("CommitsByRange", err)
return
@@ -169,10 +166,13 @@ func Graph(ctx *context.Context) {
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
+ divOnly := ctx.FormBool("div-only")
+ queryParams := ctx.Req.URL.Query()
+ queryParams.Del("div-only")
paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
- paginator.AddParamFromRequest(ctx.Req)
+ paginator.AddParamFromQuery(queryParams)
ctx.Data["Page"] = paginator
- if ctx.FormBool("div-only") {
+ if divOnly {
ctx.HTML(http.StatusOK, tplGraphDiv)
return
}
@@ -230,10 +230,7 @@ func FileHistory(ctx *context.Context) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
@@ -377,7 +374,7 @@ func Diff(ctx *context.Context) {
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
}
- statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
+ statuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
@@ -457,6 +454,9 @@ func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_m
}
if !ctx.Repo.CanRead(unit_model.TypeActions) {
for _, commit := range commits {
+ if commit.Status == nil {
+ continue
+ }
commit.Status.HideActionsURL(ctx)
git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses)
}
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 8b99dd95da..de34a9375c 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -575,7 +575,13 @@ func PrepareCompareDiff(
ctx.Data["CommitRepoLink"] = ci.HeadRepo.Link()
ctx.Data["AfterCommitID"] = headCommitID
- ctx.Data["ExpandNewPrForm"] = ctx.FormBool("expand")
+
+ // follow GitHub's behavior: autofill the form and expand
+ newPrFormTitle := ctx.FormTrim("title")
+ newPrFormBody := ctx.FormTrim("body")
+ ctx.Data["ExpandNewPrForm"] = ctx.FormBool("expand") || ctx.FormBool("quick_pull") || newPrFormTitle != "" || newPrFormBody != ""
+ ctx.Data["TitleQuery"] = newPrFormTitle
+ ctx.Data["BodyQuery"] = newPrFormBody
if (headCommitID == ci.CompareInfo.MergeBase && !ci.DirectComparison) ||
headCommitID == ci.CompareInfo.BaseCommitID {
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 03e5b830a0..2a5ac10282 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -4,6 +4,7 @@
package repo
import (
+ "bytes"
"fmt"
"io"
"net/http"
@@ -11,18 +12,17 @@ import (
"strings"
git_model "code.gitea.io/gitea/models/git"
- repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/routers/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
@@ -34,869 +34,422 @@ const (
tplEditDiffPreview templates.TplName = "repo/editor/diff_preview"
tplDeleteFile templates.TplName = "repo/editor/delete"
tplUploadFile templates.TplName = "repo/editor/upload"
+ tplPatchFile templates.TplName = "repo/editor/patch"
+ tplCherryPick templates.TplName = "repo/editor/cherry_pick"
- frmCommitChoiceDirect string = "direct"
- frmCommitChoiceNewBranch string = "commit-to-new-branch"
+ editorCommitChoiceDirect string = "direct"
+ editorCommitChoiceNewBranch string = "commit-to-new-branch"
)
-func canCreateBasePullRequest(ctx *context.Context) bool {
- baseRepo := ctx.Repo.Repository.BaseRepo
- return baseRepo != nil && baseRepo.UnitEnabled(ctx, unit.TypePullRequests)
-}
-
-func renderCommitRights(ctx *context.Context) bool {
- canCommitToBranch, err := ctx.Repo.CanCommitToBranch(ctx, ctx.Doer)
- if err != nil {
- log.Error("CanCommitToBranch: %v", err)
- }
- ctx.Data["CanCommitToBranch"] = canCommitToBranch
- ctx.Data["CanCreatePullRequest"] = ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) || canCreateBasePullRequest(ctx)
-
- return canCommitToBranch.CanCommitToBranch
-}
-
-// redirectForCommitChoice redirects after committing the edit to a branch
-func redirectForCommitChoice(ctx *context.Context, commitChoice, newBranchName, treePath string) {
- if commitChoice == frmCommitChoiceNewBranch {
- // Redirect to a pull request when possible
- redirectToPullRequest := false
- repo := ctx.Repo.Repository
- baseBranch := ctx.Repo.BranchName
- headBranch := newBranchName
- if repo.UnitEnabled(ctx, unit.TypePullRequests) {
- redirectToPullRequest = true
- } else if canCreateBasePullRequest(ctx) {
- redirectToPullRequest = true
- baseBranch = repo.BaseRepo.DefaultBranch
- headBranch = repo.Owner.Name + "/" + repo.Name + ":" + headBranch
- repo = repo.BaseRepo
- }
-
- if redirectToPullRequest {
- ctx.Redirect(repo.Link() + "/compare/" + util.PathEscapeSegments(baseBranch) + "..." + util.PathEscapeSegments(headBranch))
- return
+func prepareEditorCommitFormOptions(ctx *context.Context, editorAction string) *context.CommitFormOptions {
+ cleanedTreePath := files_service.CleanGitTreePath(ctx.Repo.TreePath)
+ if cleanedTreePath != ctx.Repo.TreePath {
+ redirectTo := fmt.Sprintf("%s/%s/%s/%s", ctx.Repo.RepoLink, editorAction, util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(cleanedTreePath))
+ if ctx.Req.URL.RawQuery != "" {
+ redirectTo += "?" + ctx.Req.URL.RawQuery
}
+ ctx.Redirect(redirectTo)
+ return nil
}
- returnURI := ctx.FormString("return_uri")
-
- ctx.RedirectToCurrentSite(
- returnURI,
- ctx.Repo.RepoLink+"/src/branch/"+util.PathEscapeSegments(newBranchName)+"/"+util.PathEscapeSegments(treePath),
- )
-}
+ commitFormOptions, err := context.PrepareCommitFormOptions(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Permission, ctx.Repo.RefFullName)
+ if err != nil {
+ ctx.ServerError("PrepareCommitFormOptions", err)
+ return nil
+ }
-// getParentTreeFields returns list of parent tree names and corresponding tree paths
-// based on given tree path.
-func getParentTreeFields(treePath string) (treeNames, treePaths []string) {
- if len(treePath) == 0 {
- return treeNames, treePaths
+ if commitFormOptions.NeedFork {
+ ForkToEdit(ctx)
+ return nil
}
- treeNames = strings.Split(treePath, "/")
- treePaths = make([]string, len(treeNames))
- for i := range treeNames {
- treePaths[i] = strings.Join(treeNames[:i+1], "/")
+ if commitFormOptions.WillSubmitToFork && !commitFormOptions.TargetRepo.CanEnableEditor() {
+ ctx.Data["NotFoundPrompt"] = ctx.Locale.Tr("repo.editor.fork_not_editable")
+ ctx.NotFound(nil)
}
- return treeNames, treePaths
-}
-func editFileCommon(ctx *context.Context, isNewFile bool) {
- ctx.Data["PageIsEdit"] = true
- ctx.Data["IsNewFile"] = isNewFile
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
+ ctx.Data["TreePath"] = ctx.Repo.TreePath
+ ctx.Data["CommitFormOptions"] = commitFormOptions
+
+ // for online editor
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["IsEditingFileOnly"] = ctx.FormString("return_uri") != ""
ctx.Data["ReturnURI"] = ctx.FormString("return_uri")
-}
-
-func editFile(ctx *context.Context, isNewFile bool) {
- editFileCommon(ctx, isNewFile)
- canCommit := renderCommitRights(ctx)
- treePath := cleanUploadFileName(ctx.Repo.TreePath)
- if treePath != ctx.Repo.TreePath {
- if isNewFile {
- ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_new", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
- } else {
- ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_edit", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
- }
- return
- }
-
- // Check if the filename (and additional path) is specified in the querystring
- // (filename is a misnomer, but kept for compatibility with GitHub)
- filePath, fileName := path.Split(ctx.Req.URL.Query().Get("filename"))
- filePath = strings.Trim(filePath, "/")
- treeNames, treePaths := getParentTreeFields(path.Join(ctx.Repo.TreePath, filePath))
-
- if !isNewFile {
- entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
- if err != nil {
- HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
- return
- }
-
- // No way to edit a directory online.
- if entry.IsDir() {
- ctx.NotFound(nil)
- return
- }
-
- blob := entry.Blob()
- if blob.Size() >= setting.UI.MaxDisplayFileSize {
- ctx.NotFound(err)
- return
- }
-
- buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
- if err != nil {
- if git.IsErrNotExist(err) {
- ctx.NotFound(err)
- } else {
- ctx.ServerError("getFileReader", err)
- }
- return
- }
-
- defer dataRc.Close()
-
- ctx.Data["FileSize"] = blob.Size()
-
- // Only some file types are editable online as text.
- if !fInfo.st.IsRepresentableAsText() || fInfo.isLFSFile {
- ctx.NotFound(nil)
- return
- }
-
- d, _ := io.ReadAll(dataRc)
-
- buf = append(buf, d...)
- if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
- log.Error("ToUTF8: %v", err)
- ctx.Data["FileContent"] = string(buf)
- } else {
- ctx.Data["FileContent"] = content
- }
- } else {
- // Append filename from query, or empty string to allow username the new file.
- treeNames = append(treeNames, fileName)
- }
-
- ctx.Data["TreeNames"] = treeNames
- ctx.Data["TreePaths"] = treePaths
+ // form fields
ctx.Data["commit_summary"] = ""
ctx.Data["commit_message"] = ""
- ctx.Data["commit_choice"] = util.Iif(canCommit, frmCommitChoiceDirect, frmCommitChoiceNewBranch)
- ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
+ ctx.Data["commit_choice"] = util.Iif(commitFormOptions.CanCommitToBranch, editorCommitChoiceDirect, editorCommitChoiceNewBranch)
+ ctx.Data["new_branch_name"] = getUniquePatchBranchName(ctx, ctx.Doer.LowerName, commitFormOptions.TargetRepo)
ctx.Data["last_commit"] = ctx.Repo.CommitID
-
- ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath)
-
- ctx.HTML(http.StatusOK, tplEditFile)
+ return commitFormOptions
}
-// GetEditorConfig returns a editorconfig JSON string for given treePath or "null"
-func GetEditorConfig(ctx *context.Context, treePath string) string {
- ec, _, err := ctx.Repo.GetEditorconfig()
- if err == nil {
- def, err := ec.GetDefinitionForFilename(treePath)
- if err == nil {
- jsonStr, _ := json.Marshal(def)
- return string(jsonStr)
- }
- }
- return "null"
+func prepareTreePathFieldsAndPaths(ctx *context.Context, treePath string) {
+ // show the tree path fields in the "breadcrumb" and help users to edit the target tree path
+ ctx.Data["TreeNames"], ctx.Data["TreePaths"] = getParentTreeFields(strings.TrimPrefix(treePath, "/"))
}
-// EditFile render edit file page
-func EditFile(ctx *context.Context) {
- editFile(ctx, false)
+type preparedEditorCommitForm[T any] struct {
+ form T
+ commonForm *forms.CommitCommonForm
+ CommitFormOptions *context.CommitFormOptions
+ OldBranchName string
+ NewBranchName string
+ GitCommitter *files_service.IdentityOptions
}
-// NewFile render create file page
-func NewFile(ctx *context.Context) {
- editFile(ctx, true)
+func (f *preparedEditorCommitForm[T]) GetCommitMessage(defaultCommitMessage string) string {
+ commitMessage := util.IfZero(strings.TrimSpace(f.commonForm.CommitSummary), defaultCommitMessage)
+ if body := strings.TrimSpace(f.commonForm.CommitMessage); body != "" {
+ commitMessage += "\n\n" + body
+ }
+ return commitMessage
}
-func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile bool) {
- editFileCommon(ctx, isNewFile)
- ctx.Data["PageHasPosted"] = true
-
- canCommit := renderCommitRights(ctx)
- treeNames, treePaths := getParentTreeFields(form.TreePath)
- branchName := ctx.Repo.BranchName
- if form.CommitChoice == frmCommitChoiceNewBranch {
- branchName = form.NewBranchName
- }
-
- ctx.Data["TreePath"] = form.TreePath
- ctx.Data["TreeNames"] = treeNames
- ctx.Data["TreePaths"] = treePaths
- ctx.Data["FileContent"] = form.Content
- ctx.Data["commit_summary"] = form.CommitSummary
- ctx.Data["commit_message"] = form.CommitMessage
- ctx.Data["commit_choice"] = form.CommitChoice
- ctx.Data["new_branch_name"] = form.NewBranchName
- ctx.Data["last_commit"] = ctx.Repo.CommitID
- ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, form.TreePath)
-
+func prepareEditorCommitSubmittedForm[T forms.CommitCommonFormInterface](ctx *context.Context) *preparedEditorCommitForm[T] {
+ form := web.GetForm(ctx).(T)
if ctx.HasError() {
- ctx.HTML(http.StatusOK, tplEditFile)
- return
+ ctx.JSONError(ctx.GetErrMsg())
+ return nil
}
- // Cannot commit to an existing branch if user doesn't have rights
- if branchName == ctx.Repo.BranchName && !canCommit {
- ctx.Data["Err_NewBranchName"] = true
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
- return
- }
+ commonForm := form.GetCommitCommonForm()
+ commonForm.TreePath = files_service.CleanGitTreePath(commonForm.TreePath)
- // CommitSummary is optional in the web form, if empty, give it a default message based on add or update
- // `message` will be both the summary and message combined
- message := strings.TrimSpace(form.CommitSummary)
- if len(message) == 0 {
- if isNewFile {
- message = ctx.Locale.TrString("repo.editor.add", form.TreePath)
- } else {
- message = ctx.Locale.TrString("repo.editor.update", form.TreePath)
- }
+ commitFormOptions, err := context.PrepareCommitFormOptions(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Permission, ctx.Repo.RefFullName)
+ if err != nil {
+ ctx.ServerError("PrepareCommitFormOptions", err)
+ return nil
}
- form.CommitMessage = strings.TrimSpace(form.CommitMessage)
- if len(form.CommitMessage) > 0 {
- message += "\n\n" + form.CommitMessage
+ if commitFormOptions.NeedFork {
+ // It shouldn't happen, because we should have done the checks in the "GET" request. But just in case.
+ ctx.JSONError(ctx.Locale.TrString("error.not_found"))
+ return nil
}
- gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, form.CommitEmail)
- if !valid {
- ctx.Data["Err_CommitEmail"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_email"), tplEditFile, &form)
- return
+ // check commit behavior
+ fromBaseBranch := ctx.FormString("from_base_branch")
+ commitToNewBranch := commonForm.CommitChoice == editorCommitChoiceNewBranch || fromBaseBranch != ""
+ targetBranchName := util.Iif(commitToNewBranch, commonForm.NewBranchName, ctx.Repo.BranchName)
+ if targetBranchName == ctx.Repo.BranchName && !commitFormOptions.CanCommitToBranch {
+ ctx.JSONError(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", targetBranchName))
+ return nil
}
- operation := "update"
- if isNewFile {
- operation = "create"
+ if !issues.CanMaintainerWriteToBranch(ctx, ctx.Repo.Permission, targetBranchName, ctx.Doer) {
+ ctx.NotFound(nil)
+ return nil
}
- if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
- LastCommitID: form.LastCommit,
- OldBranch: ctx.Repo.BranchName,
- NewBranch: branchName,
- Message: message,
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: operation,
- FromTreePath: ctx.Repo.TreePath,
- TreePath: form.TreePath,
- ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")),
- },
- },
- Signoff: form.Signoff,
- Author: gitCommitter,
- Committer: gitCommitter,
- }); err != nil {
- // This is where we handle all the errors thrown by files_service.ChangeRepoFiles
- if git.IsErrNotExist(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Repo.TreePath), tplEditFile, &form)
- } else if git_model.IsErrLFSFileLocked(err) {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(git_model.ErrLFSFileLocked).Path, err.(git_model.ErrLFSFileLocked).UserName), tplEditFile, &form)
- } else if files_service.IsErrFilenameInvalid(err) {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplEditFile, &form)
- } else if files_service.IsErrFilePathInvalid(err) {
- ctx.Data["Err_TreePath"] = true
- if fileErr, ok := err.(files_service.ErrFilePathInvalid); ok {
- switch fileErr.Type {
- case git.EntryModeSymlink:
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form)
- case git.EntryModeTree:
- ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
- case git.EntryModeBlob:
- ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
- default:
- ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", fileErr.Path), tplEditFile, &form)
- }
- } else {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
- }
- } else if files_service.IsErrRepoFileAlreadyExists(err) {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form)
- } else if git.IsErrBranchNotExist(err) {
- // For when a user adds/updates a file to a branch that no longer exists
- if branchErr, ok := err.(git.ErrBranchNotExist); ok {
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplEditFile, &form)
- } else {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
- }
- } else if git_model.IsErrBranchAlreadyExists(err) {
- // For when a user specifies a new branch that already exists
- ctx.Data["Err_NewBranchName"] = true
- if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
- } else {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
- }
- } else if files_service.IsErrCommitIDDoesNotMatch(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.commit_id_not_matching"), tplEditFile, &form)
- } else if git.IsErrPushOutOfDate(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.push_out_of_date"), tplEditFile, &form)
- } else if git.IsErrPushRejected(err) {
- errPushRej := err.(*git.ErrPushRejected)
- if len(errPushRej.Message) == 0 {
- ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form)
+ // Committer user info
+ gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, commonForm.CommitEmail)
+ if !valid {
+ ctx.JSONError(ctx.Tr("repo.editor.invalid_commit_email"))
+ return nil
+ }
+
+ if commitToNewBranch {
+ // if target branch exists, we should stop
+ targetBranchExists, err := git_model.IsBranchExist(ctx, commitFormOptions.TargetRepo.ID, targetBranchName)
+ if err != nil {
+ ctx.ServerError("IsBranchExist", err)
+ return nil
+ } else if targetBranchExists {
+ if fromBaseBranch != "" {
+ ctx.JSONError(ctx.Tr("repo.editor.fork_branch_exists", targetBranchName))
} else {
- flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
- "Message": ctx.Tr("repo.editor.push_rejected"),
- "Summary": ctx.Tr("repo.editor.push_rejected_summary"),
- "Details": utils.SanitizeFlashErrorString(errPushRej.Message),
- })
- if err != nil {
- ctx.ServerError("editFilePost.HTMLString", err)
- return
- }
- ctx.RenderWithErr(flashError, tplEditFile, &form)
+ ctx.JSONError(ctx.Tr("repo.editor.branch_already_exists", targetBranchName))
}
- } else {
- flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
- "Message": ctx.Tr("repo.editor.fail_to_update_file", form.TreePath),
- "Summary": ctx.Tr("repo.editor.fail_to_update_file_summary"),
- "Details": utils.SanitizeFlashErrorString(err.Error()),
- })
- if err != nil {
- ctx.ServerError("editFilePost.HTMLString", err)
- return
- }
- ctx.RenderWithErr(flashError, tplEditFile, &form)
+ return nil
}
}
- if ctx.Repo.Repository.IsEmpty {
- if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
- _ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
+ oldBranchName := ctx.Repo.BranchName
+ if fromBaseBranch != "" {
+ err = editorPushBranchToForkedRepository(ctx, ctx.Doer, ctx.Repo.Repository.BaseRepo, fromBaseBranch, commitFormOptions.TargetRepo, targetBranchName)
+ if err != nil {
+ log.Error("Unable to editorPushBranchToForkedRepository: %v", err)
+ ctx.JSONError(ctx.Tr("repo.editor.fork_failed_to_push_branch", targetBranchName))
+ return nil
}
+ // we have pushed the base branch as the new branch, now we need to commit the changes directly to the new branch
+ oldBranchName = targetBranchName
}
- redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
-}
-
-// EditFilePost response for editing file
-func EditFilePost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.EditRepoFileForm)
- editFilePost(ctx, *form, false)
-}
-
-// NewFilePost response for creating file
-func NewFilePost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.EditRepoFileForm)
- editFilePost(ctx, *form, true)
-}
-
-// DiffPreviewPost render preview diff page
-func DiffPreviewPost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.EditPreviewDiffForm)
- treePath := cleanUploadFileName(ctx.Repo.TreePath)
- if len(treePath) == 0 {
- ctx.HTTPError(http.StatusInternalServerError, "file name to diff is invalid")
- return
+ return &preparedEditorCommitForm[T]{
+ form: form,
+ commonForm: commonForm,
+ CommitFormOptions: commitFormOptions,
+ OldBranchName: oldBranchName,
+ NewBranchName: targetBranchName,
+ GitCommitter: gitCommitter,
}
-
- entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
- if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, "GetTreeEntryByPath: "+err.Error())
- return
- } else if entry.IsDir() {
- ctx.HTTPError(http.StatusUnprocessableEntity)
- return
- }
-
- diff, err := files_service.GetDiffPreview(ctx, ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
- if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, "GetDiffPreview: "+err.Error())
- return
- }
-
- if len(diff.Files) != 0 {
- ctx.Data["File"] = diff.Files[0]
- }
-
- ctx.HTML(http.StatusOK, tplEditDiffPreview)
}
-// DeleteFile render delete file page
-func DeleteFile(ctx *context.Context) {
- ctx.Data["PageIsDelete"] = true
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
- treePath := cleanUploadFileName(ctx.Repo.TreePath)
-
- if treePath != ctx.Repo.TreePath {
- ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_delete", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
+// redirectForCommitChoice redirects after committing the edit to a branch
+func redirectForCommitChoice[T any](ctx *context.Context, parsed *preparedEditorCommitForm[T], treePath string) {
+ // when editing a file in a PR, it should return to the origin location
+ if returnURI := ctx.FormString("return_uri"); returnURI != "" && httplib.IsCurrentGiteaSiteURL(ctx, returnURI) {
+ ctx.JSONRedirect(returnURI)
return
}
- ctx.Data["TreePath"] = treePath
- canCommit := renderCommitRights(ctx)
-
- ctx.Data["commit_summary"] = ""
- ctx.Data["commit_message"] = ""
- ctx.Data["last_commit"] = ctx.Repo.CommitID
- if canCommit {
- ctx.Data["commit_choice"] = frmCommitChoiceDirect
- } else {
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
+ if parsed.commonForm.CommitChoice == editorCommitChoiceNewBranch {
+ // Redirect to a pull request when possible
+ redirectToPullRequest := false
+ repo, baseBranch, headBranch := ctx.Repo.Repository, parsed.OldBranchName, parsed.NewBranchName
+ if ctx.Repo.Repository.IsFork && parsed.CommitFormOptions.CanCreateBasePullRequest {
+ redirectToPullRequest = true
+ baseBranch = repo.BaseRepo.DefaultBranch
+ headBranch = repo.Owner.Name + "/" + repo.Name + ":" + headBranch
+ repo = repo.BaseRepo
+ } else if repo.UnitEnabled(ctx, unit.TypePullRequests) {
+ redirectToPullRequest = true
+ }
+ if redirectToPullRequest {
+ ctx.JSONRedirect(repo.Link() + "/compare/" + util.PathEscapeSegments(baseBranch) + "..." + util.PathEscapeSegments(headBranch))
+ return
+ }
}
- ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
- ctx.HTML(http.StatusOK, tplDeleteFile)
+ // redirect to the newly updated file
+ redirectTo := util.URLJoin(ctx.Repo.RepoLink, "src/branch", util.PathEscapeSegments(parsed.NewBranchName), util.PathEscapeSegments(treePath))
+ ctx.JSONRedirect(redirectTo)
}
-// DeleteFilePost response for deleting file
-func DeleteFilePost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.DeleteRepoFileForm)
- canCommit := renderCommitRights(ctx)
- branchName := ctx.Repo.BranchName
- if form.CommitChoice == frmCommitChoiceNewBranch {
- branchName = form.NewBranchName
- }
-
- ctx.Data["PageIsDelete"] = true
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
- ctx.Data["TreePath"] = ctx.Repo.TreePath
- ctx.Data["commit_summary"] = form.CommitSummary
- ctx.Data["commit_message"] = form.CommitMessage
- ctx.Data["commit_choice"] = form.CommitChoice
- ctx.Data["new_branch_name"] = form.NewBranchName
- ctx.Data["last_commit"] = ctx.Repo.CommitID
-
- if ctx.HasError() {
- ctx.HTML(http.StatusOK, tplDeleteFile)
- return
- }
-
- if branchName == ctx.Repo.BranchName && !canCommit {
- ctx.Data["Err_NewBranchName"] = true
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplDeleteFile, &form)
- return
- }
-
- message := strings.TrimSpace(form.CommitSummary)
- if len(message) == 0 {
- message = ctx.Locale.TrString("repo.editor.delete", ctx.Repo.TreePath)
- }
- form.CommitMessage = strings.TrimSpace(form.CommitMessage)
- if len(form.CommitMessage) > 0 {
- message += "\n\n" + form.CommitMessage
+func editFileOpenExisting(ctx *context.Context) (prefetch []byte, dataRc io.ReadCloser, fInfo *fileInfo) {
+ entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
+ if err != nil {
+ HandleGitError(ctx, "GetTreeEntryByPath", err)
+ return nil, nil, nil
}
- gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, form.CommitEmail)
- if !valid {
- ctx.Data["Err_CommitEmail"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_email"), tplDeleteFile, &form)
- return
+ // No way to edit a directory online.
+ if entry.IsDir() {
+ ctx.NotFound(nil)
+ return nil, nil, nil
}
- if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
- LastCommitID: form.LastCommit,
- OldBranch: ctx.Repo.BranchName,
- NewBranch: branchName,
- Files: []*files_service.ChangeRepoFile{
- {
- Operation: "delete",
- TreePath: ctx.Repo.TreePath,
- },
- },
- Message: message,
- Signoff: form.Signoff,
- Author: gitCommitter,
- Committer: gitCommitter,
- }); err != nil {
- // This is where we handle all the errors thrown by repofiles.DeleteRepoFile
- if git.IsErrNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_deleting_no_longer_exists", ctx.Repo.TreePath), tplDeleteFile, &form)
- } else if files_service.IsErrFilenameInvalid(err) {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", ctx.Repo.TreePath), tplDeleteFile, &form)
- } else if files_service.IsErrFilePathInvalid(err) {
- ctx.Data["Err_TreePath"] = true
- if fileErr, ok := err.(files_service.ErrFilePathInvalid); ok {
- switch fileErr.Type {
- case git.EntryModeSymlink:
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplDeleteFile, &form)
- case git.EntryModeTree:
- ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplDeleteFile, &form)
- case git.EntryModeBlob:
- ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplDeleteFile, &form)
- default:
- ctx.ServerError("DeleteRepoFile", err)
- }
- } else {
- ctx.ServerError("DeleteRepoFile", err)
- }
- } else if git.IsErrBranchNotExist(err) {
- // For when a user deletes a file to a branch that no longer exists
- if branchErr, ok := err.(git.ErrBranchNotExist); ok {
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplDeleteFile, &form)
- } else {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
- }
- } else if git_model.IsErrBranchAlreadyExists(err) {
- // For when a user specifies a new branch that already exists
- if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form)
- } else {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
- }
- } else if files_service.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form)
- } else if git.IsErrPushRejected(err) {
- errPushRej := err.(*git.ErrPushRejected)
- if len(errPushRej.Message) == 0 {
- ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplDeleteFile, &form)
- } else {
- flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
- "Message": ctx.Tr("repo.editor.push_rejected"),
- "Summary": ctx.Tr("repo.editor.push_rejected_summary"),
- "Details": utils.SanitizeFlashErrorString(errPushRej.Message),
- })
- if err != nil {
- ctx.ServerError("DeleteFilePost.HTMLString", err)
- return
- }
- ctx.RenderWithErr(flashError, tplDeleteFile, &form)
- }
+ blob := entry.Blob()
+ buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
+ if err != nil {
+ if git.IsErrNotExist(err) {
+ ctx.NotFound(err)
} else {
- ctx.ServerError("DeleteRepoFile", err)
+ ctx.ServerError("getFileReader", err)
}
- return
+ return nil, nil, nil
}
- ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
- treePath := path.Dir(ctx.Repo.TreePath)
- if treePath == "." {
- treePath = "" // the file deleted was in the root, so we return the user to the root directory
- }
- if len(treePath) > 0 {
- // Need to get the latest commit since it changed
- commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
- if err == nil && commit != nil {
- // We have the comment, now find what directory we can return the user to
- // (must have entries)
- treePath = GetClosestParentWithFiles(treePath, commit)
- } else {
- treePath = "" // otherwise return them to the root of the repo
+ if fInfo.isLFSFile() {
+ lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
+ if err != nil {
+ _ = dataRc.Close()
+ ctx.ServerError("GetTreePathLock", err)
+ return nil, nil, nil
+ } else if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
+ _ = dataRc.Close()
+ ctx.NotFound(nil)
+ return nil, nil, nil
}
}
- redirectForCommitChoice(ctx, form.CommitChoice, branchName, treePath)
+ return buf, dataRc, fInfo
}
-// UploadFile render upload file page
-func UploadFile(ctx *context.Context) {
- ctx.Data["PageIsUpload"] = true
- upload.AddUploadContext(ctx, "repo")
- canCommit := renderCommitRights(ctx)
- treePath := cleanUploadFileName(ctx.Repo.TreePath)
- if treePath != ctx.Repo.TreePath {
- ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_upload", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
- return
- }
- ctx.Repo.TreePath = treePath
-
- treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
- if len(treeNames) == 0 {
- // We must at least have one element for user to input.
- treeNames = []string{""}
- }
-
- ctx.Data["TreeNames"] = treeNames
- ctx.Data["TreePaths"] = treePaths
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
- ctx.Data["commit_summary"] = ""
- ctx.Data["commit_message"] = ""
- if canCommit {
- ctx.Data["commit_choice"] = frmCommitChoiceDirect
- } else {
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- }
- ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
-
- ctx.HTML(http.StatusOK, tplUploadFile)
-}
-
-// UploadFilePost response for uploading file
-func UploadFilePost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.UploadRepoFileForm)
- ctx.Data["PageIsUpload"] = true
- upload.AddUploadContext(ctx, "repo")
- canCommit := renderCommitRights(ctx)
-
- oldBranchName := ctx.Repo.BranchName
- branchName := oldBranchName
-
- if form.CommitChoice == frmCommitChoiceNewBranch {
- branchName = form.NewBranchName
- }
-
- form.TreePath = cleanUploadFileName(form.TreePath)
+func EditFile(ctx *context.Context) {
+ editorAction := ctx.PathParam("editor_action")
+ isNewFile := editorAction == "_new"
+ ctx.Data["IsNewFile"] = isNewFile
- treeNames, treePaths := getParentTreeFields(form.TreePath)
- if len(treeNames) == 0 {
- // We must at least have one element for user to input.
- treeNames = []string{""}
+ // Check if the filename (and additional path) is specified in the querystring
+ // (filename is a misnomer, but kept for compatibility with GitHub)
+ urlQuery := ctx.Req.URL.Query()
+ queryFilename := urlQuery.Get("filename")
+ if queryFilename != "" {
+ newTreePath := path.Join(ctx.Repo.TreePath, queryFilename)
+ redirectTo := fmt.Sprintf("%s/%s/%s/%s", ctx.Repo.RepoLink, editorAction, util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(newTreePath))
+ urlQuery.Del("filename")
+ if newQueryParams := urlQuery.Encode(); newQueryParams != "" {
+ redirectTo += "?" + newQueryParams
+ }
+ ctx.Redirect(redirectTo)
+ return
}
- ctx.Data["TreePath"] = form.TreePath
- ctx.Data["TreeNames"] = treeNames
- ctx.Data["TreePaths"] = treePaths
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName)
- ctx.Data["commit_summary"] = form.CommitSummary
- ctx.Data["commit_message"] = form.CommitMessage
- ctx.Data["commit_choice"] = form.CommitChoice
- ctx.Data["new_branch_name"] = branchName
+ // on the "New File" page, we should add an empty path field to make end users could input a new name
+ prepareTreePathFieldsAndPaths(ctx, util.Iif(isNewFile, ctx.Repo.TreePath+"/", ctx.Repo.TreePath))
- if ctx.HasError() {
- ctx.HTML(http.StatusOK, tplUploadFile)
+ prepareEditorCommitFormOptions(ctx, editorAction)
+ if ctx.Written() {
return
}
- if oldBranchName != branchName {
- if exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName); err == nil && exist {
- ctx.Data["Err_NewBranchName"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplUploadFile, &form)
+ if !isNewFile {
+ prefetch, dataRc, fInfo := editFileOpenExisting(ctx)
+ if ctx.Written() {
return
}
- } else if !canCommit {
- ctx.Data["Err_NewBranchName"] = true
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplUploadFile, &form)
- return
- }
+ defer dataRc.Close()
- if !ctx.Repo.Repository.IsEmpty {
- var newTreePath string
- for _, part := range treeNames {
- newTreePath = path.Join(newTreePath, part)
- entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath)
+ ctx.Data["FileSize"] = fInfo.fileSize
+
+ // Only some file types are editable online as text.
+ if fInfo.isLFSFile() {
+ ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
+ } else if !fInfo.st.IsRepresentableAsText() {
+ ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
+ } else if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
+ ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_too_large_file")
+ }
+
+ if ctx.Data["NotEditableReason"] == nil {
+ buf, err := io.ReadAll(io.MultiReader(bytes.NewReader(prefetch), dataRc))
if err != nil {
- if git.IsErrNotExist(err) {
- break // Means there is no item with that name, so we're good
- }
- ctx.ServerError("Repo.Commit.GetTreeEntryByPath", err)
+ ctx.ServerError("ReadAll", err)
return
}
-
- // User can only upload files to a directory, the directory name shouldn't be an existing file.
- if !entry.IsDir() {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form)
- return
+ if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
+ ctx.Data["FileContent"] = string(buf)
+ } else {
+ ctx.Data["FileContent"] = content
}
}
}
- message := strings.TrimSpace(form.CommitSummary)
- if len(message) == 0 {
- dir := form.TreePath
- if dir == "" {
- dir = "/"
- }
- message = ctx.Locale.TrString("repo.editor.upload_files_to_dir", dir)
- }
-
- form.CommitMessage = strings.TrimSpace(form.CommitMessage)
- if len(form.CommitMessage) > 0 {
- message += "\n\n" + form.CommitMessage
- }
+ ctx.Data["EditorconfigJson"] = getContextRepoEditorConfig(ctx, ctx.Repo.TreePath)
+ ctx.HTML(http.StatusOK, tplEditFile)
+}
- gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, form.CommitEmail)
- if !valid {
- ctx.Data["Err_CommitEmail"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_email"), tplUploadFile, &form)
+func EditFilePost(ctx *context.Context) {
+ editorAction := ctx.PathParam("editor_action")
+ isNewFile := editorAction == "_new"
+ parsed := prepareEditorCommitSubmittedForm[*forms.EditRepoFileForm](ctx)
+ if ctx.Written() {
return
}
- if err := files_service.UploadRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.UploadRepoFileOptions{
- LastCommitID: ctx.Repo.CommitID,
- OldBranch: oldBranchName,
- NewBranch: branchName,
- TreePath: form.TreePath,
- Message: message,
- Files: form.Files,
- Signoff: form.Signoff,
- Author: gitCommitter,
- Committer: gitCommitter,
- }); err != nil {
- if git_model.IsErrLFSFileLocked(err) {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(git_model.ErrLFSFileLocked).Path, err.(git_model.ErrLFSFileLocked).UserName), tplUploadFile, &form)
- } else if files_service.IsErrFilenameInvalid(err) {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplUploadFile, &form)
- } else if files_service.IsErrFilePathInvalid(err) {
- ctx.Data["Err_TreePath"] = true
- fileErr := err.(files_service.ErrFilePathInvalid)
- switch fileErr.Type {
- case git.EntryModeSymlink:
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplUploadFile, &form)
- case git.EntryModeTree:
- ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplUploadFile, &form)
- case git.EntryModeBlob:
- ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplUploadFile, &form)
- default:
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
- }
- } else if files_service.IsErrRepoFileAlreadyExists(err) {
- ctx.Data["Err_TreePath"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplUploadFile, &form)
- } else if git.IsErrBranchNotExist(err) {
- branchErr := err.(git.ErrBranchNotExist)
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form)
- } else if git_model.IsErrBranchAlreadyExists(err) {
- // For when a user specifies a new branch that already exists
- ctx.Data["Err_NewBranchName"] = true
- branchErr := err.(git_model.ErrBranchAlreadyExists)
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
- } else if git.IsErrPushOutOfDate(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form)
- } else if git.IsErrPushRejected(err) {
- errPushRej := err.(*git.ErrPushRejected)
- if len(errPushRej.Message) == 0 {
- ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplUploadFile, &form)
- } else {
- flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
- "Message": ctx.Tr("repo.editor.push_rejected"),
- "Summary": ctx.Tr("repo.editor.push_rejected_summary"),
- "Details": utils.SanitizeFlashErrorString(errPushRej.Message),
- })
- if err != nil {
- ctx.ServerError("UploadFilePost.HTMLString", err)
- return
- }
- ctx.RenderWithErr(flashError, tplUploadFile, &form)
- }
- } else {
- // os.ErrNotExist - upload file missing in the intervening time?!
- log.Error("Error during upload to repo: %-v to filepath: %s on %s from %s: %v", ctx.Repo.Repository, form.TreePath, oldBranchName, form.NewBranchName, err)
- ctx.RenderWithErr(ctx.Tr("repo.editor.unable_to_upload_files", form.TreePath, err), tplUploadFile, &form)
- }
+ defaultCommitMessage := util.Iif(isNewFile, ctx.Locale.TrString("repo.editor.add", parsed.form.TreePath), ctx.Locale.TrString("repo.editor.update", parsed.form.TreePath))
+
+ var operation string
+ if isNewFile {
+ operation = "create"
+ } else if parsed.form.Content.Has() {
+ // The form content only has data if the file is representable as text, is not too large and not in lfs.
+ operation = "update"
+ } else if ctx.Repo.TreePath != parsed.form.TreePath {
+ // If it doesn't have data, the only possible operation is a "rename"
+ operation = "rename"
+ } else {
+ // It should never happen, just in case
+ ctx.JSONError(ctx.Tr("error.occurred"))
return
}
- if ctx.Repo.Repository.IsEmpty {
- if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
- _ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
- }
+ _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
+ LastCommitID: parsed.form.LastCommit,
+ OldBranch: parsed.OldBranchName,
+ NewBranch: parsed.NewBranchName,
+ Message: parsed.GetCommitMessage(defaultCommitMessage),
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: operation,
+ FromTreePath: ctx.Repo.TreePath,
+ TreePath: parsed.form.TreePath,
+ ContentReader: strings.NewReader(strings.ReplaceAll(parsed.form.Content.Value(), "\r", "")),
+ },
+ },
+ Signoff: parsed.form.Signoff,
+ Author: parsed.GitCommitter,
+ Committer: parsed.GitCommitter,
+ })
+ if err != nil {
+ editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
+ return
}
- redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
+ redirectForCommitChoice(ctx, parsed, parsed.form.TreePath)
}
-func cleanUploadFileName(name string) string {
- // Rebase the filename
- name = util.PathJoinRel(name)
- // Git disallows any filenames to have a .git directory in them.
- for _, part := range strings.Split(name, "/") {
- if strings.ToLower(part) == ".git" {
- return ""
- }
+// DeleteFile render delete file page
+func DeleteFile(ctx *context.Context) {
+ prepareEditorCommitFormOptions(ctx, "_delete")
+ if ctx.Written() {
+ return
}
- return name
+ ctx.Data["PageIsDelete"] = true
+ ctx.HTML(http.StatusOK, tplDeleteFile)
}
-// UploadFileToServer upload file to server file dir not git
-func UploadFileToServer(ctx *context.Context) {
- file, header, err := ctx.Req.FormFile("file")
- if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err))
+// DeleteFilePost response for deleting file
+func DeleteFilePost(ctx *context.Context) {
+ parsed := prepareEditorCommitSubmittedForm[*forms.DeleteRepoFileForm](ctx)
+ if ctx.Written() {
return
}
- defer file.Close()
-
- buf := make([]byte, 1024)
- n, _ := util.ReadAtMost(file, buf)
- if n > 0 {
- buf = buf[:n]
- }
- err = upload.Verify(buf, header.Filename, setting.Repository.Upload.AllowedTypes)
+ treePath := ctx.Repo.TreePath
+ _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
+ LastCommitID: parsed.form.LastCommit,
+ OldBranch: parsed.OldBranchName,
+ NewBranch: parsed.NewBranchName,
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "delete",
+ TreePath: treePath,
+ },
+ },
+ Message: parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath)),
+ Signoff: parsed.form.Signoff,
+ Author: parsed.GitCommitter,
+ Committer: parsed.GitCommitter,
+ })
if err != nil {
- ctx.HTTPError(http.StatusBadRequest, err.Error())
+ editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
return
}
- name := cleanUploadFileName(header.Filename)
- if len(name) == 0 {
- ctx.HTTPError(http.StatusInternalServerError, "Upload file name is invalid")
- return
- }
+ ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
+ redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath)
+ redirectForCommitChoice(ctx, parsed, redirectTreePath)
+}
- upload, err := repo_model.NewUpload(ctx, name, buf, file)
- if err != nil {
- ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("NewUpload: %v", err))
+func UploadFile(ctx *context.Context) {
+ ctx.Data["PageIsUpload"] = true
+ prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath)
+ opts := prepareEditorCommitFormOptions(ctx, "_upload")
+ if ctx.Written() {
return
}
+ upload.AddUploadContextForRepo(ctx, opts.TargetRepo)
- log.Trace("New file uploaded: %s", upload.UUID)
- ctx.JSON(http.StatusOK, map[string]string{
- "uuid": upload.UUID,
- })
+ ctx.HTML(http.StatusOK, tplUploadFile)
}
-// RemoveUploadFileFromServer remove file from server file dir
-func RemoveUploadFileFromServer(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.RemoveUploadFileForm)
- if len(form.File) == 0 {
- ctx.Status(http.StatusNoContent)
+func UploadFilePost(ctx *context.Context) {
+ parsed := prepareEditorCommitSubmittedForm[*forms.UploadRepoFileForm](ctx)
+ if ctx.Written() {
return
}
- if err := repo_model.DeleteUploadByUUID(ctx, form.File); err != nil {
- ctx.HTTPError(http.StatusInternalServerError, fmt.Sprintf("DeleteUploadByUUID: %v", err))
+ defaultCommitMessage := ctx.Locale.TrString("repo.editor.upload_files_to_dir", util.IfZero(parsed.form.TreePath, "/"))
+ err := files_service.UploadRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.UploadRepoFileOptions{
+ LastCommitID: parsed.form.LastCommit,
+ OldBranch: parsed.OldBranchName,
+ NewBranch: parsed.NewBranchName,
+ TreePath: parsed.form.TreePath,
+ Message: parsed.GetCommitMessage(defaultCommitMessage),
+ Files: parsed.form.Files,
+ Signoff: parsed.form.Signoff,
+ Author: parsed.GitCommitter,
+ Committer: parsed.GitCommitter,
+ })
+ if err != nil {
+ editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
return
}
-
- log.Trace("Upload file removed: %s", form.File)
- ctx.Status(http.StatusNoContent)
-}
-
-// GetUniquePatchBranchName Gets a unique branch name for a new patch branch
-// It will be in the form of <username>-patch-<num> where <num> is the first branch of this format
-// that doesn't already exist. If we exceed 1000 tries or an error is thrown, we just return "" so the user has to
-// type in the branch name themselves (will be an empty field)
-func GetUniquePatchBranchName(ctx *context.Context) string {
- prefix := ctx.Doer.LowerName + "-patch-"
- for i := 1; i <= 1000; i++ {
- branchName := fmt.Sprintf("%s%d", prefix, i)
- if exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName); err != nil {
- log.Error("GetUniquePatchBranchName: %v", err)
- return ""
- } else if !exist {
- return branchName
- }
- }
- return ""
-}
-
-// GetClosestParentWithFiles Recursively gets the path of parent in a tree that has files (used when file in a tree is
-// deleted). Returns "" for the root if no parents other than the root have files. If the given treePath isn't a
-// SubTree or it has no entries, we go up one dir and see if we can return the user to that listing.
-func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
- if len(treePath) == 0 || treePath == "." {
- return ""
- }
- // see if the tree has entries
- if tree, err := commit.SubTree(treePath); err != nil {
- // failed to get tree, going up a dir
- return GetClosestParentWithFiles(path.Dir(treePath), commit)
- } else if entries, err := tree.ListEntries(); err != nil || len(entries) == 0 {
- // no files in this dir, going up a dir
- return GetClosestParentWithFiles(path.Dir(treePath), commit)
- }
- return treePath
+ redirectForCommitChoice(ctx, parsed, parsed.form.TreePath)
}
diff --git a/routers/web/repo/editor_apply_patch.go b/routers/web/repo/editor_apply_patch.go
new file mode 100644
index 0000000000..bd2811cc5f
--- /dev/null
+++ b/routers/web/repo/editor_apply_patch.go
@@ -0,0 +1,51 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "net/http"
+ "strings"
+
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/forms"
+ "code.gitea.io/gitea/services/repository/files"
+)
+
+func NewDiffPatch(ctx *context.Context) {
+ prepareEditorCommitFormOptions(ctx, "_diffpatch")
+ if ctx.Written() {
+ return
+ }
+
+ ctx.Data["PageIsPatch"] = true
+ ctx.HTML(http.StatusOK, tplPatchFile)
+}
+
+// NewDiffPatchPost response for sending patch page
+func NewDiffPatchPost(ctx *context.Context) {
+ parsed := prepareEditorCommitSubmittedForm[*forms.EditRepoFileForm](ctx)
+ if ctx.Written() {
+ return
+ }
+
+ defaultCommitMessage := ctx.Locale.TrString("repo.editor.patch")
+ _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
+ LastCommitID: parsed.form.LastCommit,
+ OldBranch: parsed.OldBranchName,
+ NewBranch: parsed.NewBranchName,
+ Message: parsed.GetCommitMessage(defaultCommitMessage),
+ Content: strings.ReplaceAll(parsed.form.Content.Value(), "\r\n", "\n"),
+ Author: parsed.GitCommitter,
+ Committer: parsed.GitCommitter,
+ })
+ if err != nil {
+ err = util.ErrorWrapLocale(err, "repo.editor.fail_to_apply_patch")
+ }
+ if err != nil {
+ editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
+ return
+ }
+ redirectForCommitChoice(ctx, parsed, parsed.form.TreePath)
+}
diff --git a/routers/web/repo/editor_cherry_pick.go b/routers/web/repo/editor_cherry_pick.go
new file mode 100644
index 0000000000..10c2741b1c
--- /dev/null
+++ b/routers/web/repo/editor_cherry_pick.go
@@ -0,0 +1,86 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "bytes"
+ "net/http"
+ "strings"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/forms"
+ "code.gitea.io/gitea/services/repository/files"
+)
+
+func CherryPick(ctx *context.Context) {
+ prepareEditorCommitFormOptions(ctx, "_cherrypick")
+ if ctx.Written() {
+ return
+ }
+
+ fromCommitID := ctx.PathParam("sha")
+ ctx.Data["FromCommitID"] = fromCommitID
+ cherryPickCommit, err := ctx.Repo.GitRepo.GetCommit(fromCommitID)
+ if err != nil {
+ HandleGitError(ctx, "GetCommit", err)
+ return
+ }
+
+ if ctx.FormString("cherry-pick-type") == "revert" {
+ ctx.Data["CherryPickType"] = "revert"
+ ctx.Data["commit_summary"] = "revert " + ctx.PathParam("sha")
+ ctx.Data["commit_message"] = "revert " + cherryPickCommit.Message()
+ } else {
+ ctx.Data["CherryPickType"] = "cherry-pick"
+ splits := strings.SplitN(cherryPickCommit.Message(), "\n", 2)
+ ctx.Data["commit_summary"] = splits[0]
+ ctx.Data["commit_message"] = splits[1]
+ }
+
+ ctx.HTML(http.StatusOK, tplCherryPick)
+}
+
+func CherryPickPost(ctx *context.Context) {
+ fromCommitID := ctx.PathParam("sha")
+ parsed := prepareEditorCommitSubmittedForm[*forms.CherryPickForm](ctx)
+ if ctx.Written() {
+ return
+ }
+
+ defaultCommitMessage := util.Iif(parsed.form.Revert, ctx.Locale.TrString("repo.commit.revert-header", fromCommitID), ctx.Locale.TrString("repo.commit.cherry-pick-header", fromCommitID))
+ opts := &files.ApplyDiffPatchOptions{
+ LastCommitID: parsed.form.LastCommit,
+ OldBranch: parsed.OldBranchName,
+ NewBranch: parsed.NewBranchName,
+ Message: parsed.GetCommitMessage(defaultCommitMessage),
+ Author: parsed.GitCommitter,
+ Committer: parsed.GitCommitter,
+ }
+
+ // First try the simple plain read-tree -m approach
+ opts.Content = fromCommitID
+ if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, parsed.form.Revert, opts); err != nil {
+ // Drop through to the "apply" method
+ buf := &bytes.Buffer{}
+ if parsed.form.Revert {
+ err = git.GetReverseRawDiff(ctx, ctx.Repo.Repository.RepoPath(), fromCommitID, buf)
+ } else {
+ err = git.GetRawDiff(ctx.Repo.GitRepo, fromCommitID, "patch", buf)
+ }
+ if err == nil {
+ opts.Content = buf.String()
+ _, err = files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts)
+ if err != nil {
+ err = util.ErrorWrapLocale(err, "repo.editor.fail_to_apply_patch")
+ }
+ }
+ if err != nil {
+ editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
+ return
+ }
+ }
+ redirectForCommitChoice(ctx, parsed, parsed.form.TreePath)
+}
diff --git a/routers/web/repo/editor_error.go b/routers/web/repo/editor_error.go
new file mode 100644
index 0000000000..245226a039
--- /dev/null
+++ b/routers/web/repo/editor_error.go
@@ -0,0 +1,82 @@
+// Copyright 2025 Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "errors"
+
+ git_model "code.gitea.io/gitea/models/git"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/routers/utils"
+ context_service "code.gitea.io/gitea/services/context"
+ files_service "code.gitea.io/gitea/services/repository/files"
+)
+
+func errorAs[T error](v error) (e T, ok bool) {
+ if errors.As(v, &e) {
+ return e, true
+ }
+ return e, false
+}
+
+func editorHandleFileOperationErrorRender(ctx *context_service.Context, message, summary, details string) {
+ flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
+ "Message": message,
+ "Summary": summary,
+ "Details": utils.SanitizeFlashErrorString(details),
+ })
+ if err == nil {
+ ctx.JSONError(flashError)
+ } else {
+ log.Error("RenderToHTML: %v", err)
+ ctx.JSONError(message + "\n" + summary + "\n" + utils.SanitizeFlashErrorString(details))
+ }
+}
+
+func editorHandleFileOperationError(ctx *context_service.Context, targetBranchName string, err error) {
+ if errAs := util.ErrorAsLocale(err); errAs != nil {
+ ctx.JSONError(ctx.Tr(errAs.TrKey, errAs.TrArgs...))
+ } else if errAs, ok := errorAs[git.ErrNotExist](err); ok {
+ ctx.JSONError(ctx.Tr("repo.editor.file_modifying_no_longer_exists", errAs.RelPath))
+ } else if errAs, ok := errorAs[git_model.ErrLFSFileLocked](err); ok {
+ ctx.JSONError(ctx.Tr("repo.editor.upload_file_is_locked", errAs.Path, errAs.UserName))
+ } else if errAs, ok := errorAs[files_service.ErrFilenameInvalid](err); ok {
+ ctx.JSONError(ctx.Tr("repo.editor.filename_is_invalid", errAs.Path))
+ } else if errAs, ok := errorAs[files_service.ErrFilePathInvalid](err); ok {
+ switch errAs.Type {
+ case git.EntryModeSymlink:
+ ctx.JSONError(ctx.Tr("repo.editor.file_is_a_symlink", errAs.Path))
+ case git.EntryModeTree:
+ ctx.JSONError(ctx.Tr("repo.editor.filename_is_a_directory", errAs.Path))
+ case git.EntryModeBlob:
+ ctx.JSONError(ctx.Tr("repo.editor.directory_is_a_file", errAs.Path))
+ default:
+ ctx.JSONError(ctx.Tr("repo.editor.filename_is_invalid", errAs.Path))
+ }
+ } else if errAs, ok := errorAs[files_service.ErrRepoFileAlreadyExists](err); ok {
+ ctx.JSONError(ctx.Tr("repo.editor.file_already_exists", errAs.Path))
+ } else if errAs, ok := errorAs[git.ErrBranchNotExist](err); ok {
+ ctx.JSONError(ctx.Tr("repo.editor.branch_does_not_exist", errAs.Name))
+ } else if errAs, ok := errorAs[git_model.ErrBranchAlreadyExists](err); ok {
+ ctx.JSONError(ctx.Tr("repo.editor.branch_already_exists", errAs.BranchName))
+ } else if files_service.IsErrCommitIDDoesNotMatch(err) {
+ ctx.JSONError(ctx.Tr("repo.editor.commit_id_not_matching"))
+ } else if files_service.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) {
+ ctx.JSONError(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(targetBranchName)))
+ } else if errAs, ok := errorAs[*git.ErrPushRejected](err); ok {
+ if errAs.Message == "" {
+ ctx.JSONError(ctx.Tr("repo.editor.push_rejected_no_message"))
+ } else {
+ editorHandleFileOperationErrorRender(ctx, ctx.Locale.TrString("repo.editor.push_rejected"), ctx.Locale.TrString("repo.editor.push_rejected_summary"), errAs.Message)
+ }
+ } else if errors.Is(err, util.ErrNotExist) {
+ ctx.JSONError(ctx.Tr("error.not_found"))
+ } else {
+ setting.PanicInDevOrTesting("unclear err %T: %v", err, err)
+ editorHandleFileOperationErrorRender(ctx, ctx.Locale.TrString("repo.editor.failed_to_commit"), ctx.Locale.TrString("repo.editor.failed_to_commit_summary"), err.Error())
+ }
+}
diff --git a/routers/web/repo/editor_fork.go b/routers/web/repo/editor_fork.go
new file mode 100644
index 0000000000..b78a634c00
--- /dev/null
+++ b/routers/web/repo/editor_fork.go
@@ -0,0 +1,31 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/services/context"
+ repo_service "code.gitea.io/gitea/services/repository"
+)
+
+const tplEditorFork templates.TplName = "repo/editor/fork"
+
+func ForkToEdit(ctx *context.Context) {
+ ctx.HTML(http.StatusOK, tplEditorFork)
+}
+
+func ForkToEditPost(ctx *context.Context) {
+ ForkRepoTo(ctx, ctx.Doer, repo_service.ForkRepoOptions{
+ BaseRepo: ctx.Repo.Repository,
+ Name: getUniqueRepositoryName(ctx, ctx.Doer.ID, ctx.Repo.Repository.Name),
+ Description: ctx.Repo.Repository.Description,
+ SingleBranch: ctx.Repo.Repository.DefaultBranch, // maybe we only need the default branch in the fork?
+ })
+ if ctx.Written() {
+ return
+ }
+ ctx.JSONRedirect("") // reload the page, the new fork should be editable now
+}
diff --git a/routers/web/repo/editor_preview.go b/routers/web/repo/editor_preview.go
new file mode 100644
index 0000000000..14be5b72b6
--- /dev/null
+++ b/routers/web/repo/editor_preview.go
@@ -0,0 +1,41 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/services/context"
+ files_service "code.gitea.io/gitea/services/repository/files"
+)
+
+func DiffPreviewPost(ctx *context.Context) {
+ content := ctx.FormString("content")
+ treePath := files_service.CleanGitTreePath(ctx.Repo.TreePath)
+ if treePath == "" {
+ ctx.HTTPError(http.StatusBadRequest, "file name to diff is invalid")
+ return
+ }
+
+ entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
+ if err != nil {
+ ctx.ServerError("GetTreeEntryByPath", err)
+ return
+ } else if entry.IsDir() {
+ ctx.HTTPError(http.StatusUnprocessableEntity)
+ return
+ }
+
+ diff, err := files_service.GetDiffPreview(ctx, ctx.Repo.Repository, ctx.Repo.BranchName, treePath, content)
+ if err != nil {
+ ctx.ServerError("GetDiffPreview", err)
+ return
+ }
+
+ if len(diff.Files) != 0 {
+ ctx.Data["File"] = diff.Files[0]
+ }
+
+ ctx.HTML(http.StatusOK, tplEditDiffPreview)
+}
diff --git a/routers/web/repo/editor_test.go b/routers/web/repo/editor_test.go
index 89bf8f309c..6e2c1d6219 100644
--- a/routers/web/repo/editor_test.go
+++ b/routers/web/repo/editor_test.go
@@ -6,76 +6,27 @@ package repo
import (
"testing"
+ repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
- "code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
-func TestCleanUploadName(t *testing.T) {
+func TestEditorUtils(t *testing.T) {
unittest.PrepareTestEnv(t)
-
- kases := map[string]string{
- ".git/refs/master": "",
- "/root/abc": "root/abc",
- "./../../abc": "abc",
- "a/../.git": "",
- "a/../../../abc": "abc",
- "../../../acd": "acd",
- "../../.git/abc": "",
- "..\\..\\.git/abc": "..\\..\\.git/abc",
- "..\\../.git/abc": "",
- "..\\../.git": "",
- "abc/../def": "def",
- ".drone.yml": ".drone.yml",
- ".abc/def/.drone.yml": ".abc/def/.drone.yml",
- "..drone.yml.": "..drone.yml.",
- "..a.dotty...name...": "..a.dotty...name...",
- "..a.dotty../.folder../.name...": "..a.dotty../.folder../.name...",
- }
- for k, v := range kases {
- assert.Equal(t, cleanUploadFileName(k), v)
- }
-}
-
-func TestGetUniquePatchBranchName(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam("id", "1")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
- contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- expectedBranchName := "user2-patch-1"
- branchName := GetUniquePatchBranchName(ctx)
- assert.Equal(t, expectedBranchName, branchName)
-}
-
-func TestGetClosestParentWithFiles(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam("id", "1")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
- contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- repo := ctx.Repo.Repository
- branch := repo.DefaultBranch
- gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
- defer gitRepo.Close()
- commit, _ := gitRepo.GetBranchCommit(branch)
- var expectedTreePath string // Should return the root dir, empty string, since there are no subdirs in this repo
- for _, deletedFile := range []string{
- "dir1/dir2/dir3/file.txt",
- "file.txt",
- } {
- treePath := GetClosestParentWithFiles(deletedFile, commit)
- assert.Equal(t, expectedTreePath, treePath)
- }
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ t.Run("getUniquePatchBranchName", func(t *testing.T) {
+ branchName := getUniquePatchBranchName(t.Context(), "user2", repo)
+ assert.Equal(t, "user2-patch-1", branchName)
+ })
+ t.Run("getClosestParentWithFiles", func(t *testing.T) {
+ gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
+ defer gitRepo.Close()
+ treePath := getClosestParentWithFiles(gitRepo, "sub-home-md-img-check", "docs/foo/bar")
+ assert.Equal(t, "docs", treePath)
+ treePath = getClosestParentWithFiles(gitRepo, "sub-home-md-img-check", "any/other")
+ assert.Empty(t, treePath)
+ })
}
diff --git a/routers/web/repo/editor_uploader.go b/routers/web/repo/editor_uploader.go
new file mode 100644
index 0000000000..1ce9a1aca4
--- /dev/null
+++ b/routers/web/repo/editor_uploader.go
@@ -0,0 +1,61 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "net/http"
+
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/context/upload"
+ files_service "code.gitea.io/gitea/services/repository/files"
+)
+
+// UploadFileToServer upload file to server file dir not git
+func UploadFileToServer(ctx *context.Context) {
+ file, header, err := ctx.Req.FormFile("file")
+ if err != nil {
+ ctx.ServerError("FormFile", err)
+ return
+ }
+ defer file.Close()
+
+ buf := make([]byte, 1024)
+ n, _ := util.ReadAtMost(file, buf)
+ if n > 0 {
+ buf = buf[:n]
+ }
+
+ err = upload.Verify(buf, header.Filename, setting.Repository.Upload.AllowedTypes)
+ if err != nil {
+ ctx.HTTPError(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ name := files_service.CleanGitTreePath(header.Filename)
+ if len(name) == 0 {
+ ctx.HTTPError(http.StatusBadRequest, "Upload file name is invalid")
+ return
+ }
+
+ uploaded, err := repo_model.NewUpload(ctx, name, buf, file)
+ if err != nil {
+ ctx.ServerError("NewUpload", err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, map[string]string{"uuid": uploaded.UUID})
+}
+
+// RemoveUploadFileFromServer remove file from server file dir
+func RemoveUploadFileFromServer(ctx *context.Context) {
+ fileUUID := ctx.FormString("file")
+ if err := repo_model.DeleteUploadByUUID(ctx, fileUUID); err != nil {
+ ctx.ServerError("DeleteUploadByUUID", err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/web/repo/editor_util.go b/routers/web/repo/editor_util.go
new file mode 100644
index 0000000000..f910f0bd40
--- /dev/null
+++ b/routers/web/repo/editor_util.go
@@ -0,0 +1,110 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+ "context"
+ "fmt"
+ "path"
+ "strings"
+
+ git_model "code.gitea.io/gitea/models/git"
+ repo_model "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ repo_module "code.gitea.io/gitea/modules/repository"
+ context_service "code.gitea.io/gitea/services/context"
+)
+
+// getUniquePatchBranchName Gets a unique branch name for a new patch branch
+// It will be in the form of <username>-patch-<num> where <num> is the first branch of this format
+// that doesn't already exist. If we exceed 1000 tries or an error is thrown, we just return "" so the user has to
+// type in the branch name themselves (will be an empty field)
+func getUniquePatchBranchName(ctx context.Context, prefixName string, repo *repo_model.Repository) string {
+ prefix := prefixName + "-patch-"
+ for i := 1; i <= 1000; i++ {
+ branchName := fmt.Sprintf("%s%d", prefix, i)
+ if exist, err := git_model.IsBranchExist(ctx, repo.ID, branchName); err != nil {
+ log.Error("getUniquePatchBranchName: %v", err)
+ return ""
+ } else if !exist {
+ return branchName
+ }
+ }
+ return ""
+}
+
+// getClosestParentWithFiles Recursively gets the closest path of parent in a tree that has files when a file in a tree is
+// deleted. It returns "" for the tree root if no parents other than the root have files.
+func getClosestParentWithFiles(gitRepo *git.Repository, branchName, originTreePath string) string {
+ var f func(treePath string, commit *git.Commit) string
+ f = func(treePath string, commit *git.Commit) string {
+ if treePath == "" || treePath == "." {
+ return ""
+ }
+ // see if the tree has entries
+ if tree, err := commit.SubTree(treePath); err != nil {
+ return f(path.Dir(treePath), commit) // failed to get the tree, going up a dir
+ } else if entries, err := tree.ListEntries(); err != nil || len(entries) == 0 {
+ return f(path.Dir(treePath), commit) // no files in this dir, going up a dir
+ }
+ return treePath
+ }
+ commit, err := gitRepo.GetBranchCommit(branchName) // must get the commit again to get the latest change
+ if err != nil {
+ log.Error("GetBranchCommit: %v", err)
+ return ""
+ }
+ return f(originTreePath, commit)
+}
+
+// getContextRepoEditorConfig returns the editorconfig JSON string for given treePath or "null"
+func getContextRepoEditorConfig(ctx *context_service.Context, treePath string) string {
+ ec, _, err := ctx.Repo.GetEditorconfig()
+ if err == nil {
+ def, err := ec.GetDefinitionForFilename(treePath)
+ if err == nil {
+ jsonStr, _ := json.Marshal(def)
+ return string(jsonStr)
+ }
+ }
+ return "null"
+}
+
+// getParentTreeFields returns list of parent tree names and corresponding tree paths based on given treePath.
+// eg: []{"a", "b", "c"}, []{"a", "a/b", "a/b/c"}
+// or: []{""}, []{""} for the root treePath
+func getParentTreeFields(treePath string) (treeNames, treePaths []string) {
+ treeNames = strings.Split(treePath, "/")
+ treePaths = make([]string, len(treeNames))
+ for i := range treeNames {
+ treePaths[i] = strings.Join(treeNames[:i+1], "/")
+ }
+ return treeNames, treePaths
+}
+
+// getUniqueRepositoryName Gets a unique repository name for a user
+// It will append a -<num> postfix if the name is already taken
+func getUniqueRepositoryName(ctx context.Context, ownerID int64, name string) string {
+ uniqueName := name
+ for i := 1; i < 1000; i++ {
+ _, err := repo_model.GetRepositoryByName(ctx, ownerID, uniqueName)
+ if err != nil || repo_model.IsErrRepoNotExist(err) {
+ return uniqueName
+ }
+ uniqueName = fmt.Sprintf("%s-%d", name, i)
+ i++
+ }
+ return ""
+}
+
+func editorPushBranchToForkedRepository(ctx context.Context, doer *user_model.User, baseRepo *repo_model.Repository, baseBranchName string, targetRepo *repo_model.Repository, targetBranchName string) error {
+ return git.Push(ctx, baseRepo.RepoPath(), git.PushOptions{
+ Remote: targetRepo.RepoPath(),
+ Branch: baseBranchName + ":" + targetBranchName,
+ Env: repo_module.PushingEnvironment(doer, targetRepo),
+ })
+}
diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go
index 79f033659b..c2694e540f 100644
--- a/routers/web/repo/fork.go
+++ b/routers/web/repo/fork.go
@@ -151,7 +151,7 @@ func ForkPost(ctx *context.Context) {
ctx.Data["ContextUser"] = ctxUser
if ctx.HasError() {
- ctx.HTML(http.StatusOK, tplFork)
+ ctx.JSONError(ctx.GetErrMsg())
return
}
@@ -159,12 +159,12 @@ func ForkPost(ctx *context.Context) {
traverseParentRepo := forkRepo
for {
if !repository.CanUserForkBetweenOwners(ctxUser.ID, traverseParentRepo.OwnerID) {
- ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
+ ctx.JSONError(ctx.Tr("repo.settings.new_owner_has_same_repo"))
return
}
repo := repo_model.GetForkedRepo(ctx, ctxUser.ID, traverseParentRepo.ID)
if repo != nil {
- ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
+ ctx.JSONRedirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
return
}
if !traverseParentRepo.IsFork {
@@ -189,44 +189,50 @@ func ForkPost(ctx *context.Context) {
}
}
- repo, err := repo_service.ForkRepository(ctx, ctx.Doer, ctxUser, repo_service.ForkRepoOptions{
+ repo := ForkRepoTo(ctx, ctxUser, repo_service.ForkRepoOptions{
BaseRepo: forkRepo,
Name: form.RepoName,
Description: form.Description,
SingleBranch: form.ForkSingleBranch,
})
+ if ctx.Written() {
+ return
+ }
+ ctx.JSONRedirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
+}
+
+func ForkRepoTo(ctx *context.Context, owner *user_model.User, forkOpts repo_service.ForkRepoOptions) *repo_model.Repository {
+ repo, err := repo_service.ForkRepository(ctx, ctx.Doer, owner, forkOpts)
if err != nil {
ctx.Data["Err_RepoName"] = true
switch {
case repo_model.IsErrReachLimitOfRepo(err):
- maxCreationLimit := ctxUser.MaxCreationLimit()
+ maxCreationLimit := owner.MaxCreationLimit()
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
- ctx.RenderWithErr(msg, tplFork, &form)
+ ctx.JSONError(msg)
case repo_model.IsErrRepoAlreadyExist(err):
- ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
+ ctx.JSONError(ctx.Tr("repo.settings.new_owner_has_same_repo"))
case repo_model.IsErrRepoFilesAlreadyExist(err):
switch {
case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
- ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplFork, form)
+ ctx.JSONError(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"))
case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
- ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplFork, form)
+ ctx.JSONError(ctx.Tr("form.repository_files_already_exist.adopt"))
case setting.Repository.AllowDeleteOfUnadoptedRepositories:
- ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplFork, form)
+ ctx.JSONError(ctx.Tr("form.repository_files_already_exist.delete"))
default:
- ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplFork, form)
+ ctx.JSONError(ctx.Tr("form.repository_files_already_exist"))
}
case db.IsErrNameReserved(err):
- ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplFork, &form)
+ ctx.JSONError(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name))
case db.IsErrNamePatternNotAllowed(err):
- ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
+ ctx.JSONError(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern))
case errors.Is(err, user_model.ErrBlockedUser):
- ctx.RenderWithErr(ctx.Tr("repo.fork.blocked_user"), tplFork, form)
+ ctx.JSONError(ctx.Tr("repo.fork.blocked_user"))
default:
ctx.ServerError("ForkPost", err)
}
- return
+ return nil
}
-
- log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
- ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
+ return repo
}
diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go
index 61606f8c5f..deb3ae4f3a 100644
--- a/routers/web/repo/githttp.go
+++ b/routers/web/repo/githttp.go
@@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"regexp"
+ "slices"
"strconv"
"strings"
"sync"
@@ -363,12 +364,7 @@ func containsParentDirectorySeparator(v string) bool {
if !strings.Contains(v, "..") {
return false
}
- for _, ent := range strings.FieldsFunc(v, isSlashRune) {
- if ent == ".." {
- return true
- }
- }
- return false
+ return slices.Contains(strings.FieldsFunc(v, isSlashRune), "..")
}
func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 86ee56b467..54b7e5df2a 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -212,7 +212,7 @@ func getActionIssues(ctx *context.Context) issues_model.IssueList {
return nil
}
issueIDs := make([]int64, 0, 10)
- for _, stringIssueID := range strings.Split(commaSeparatedIssueIDs, ",") {
+ for stringIssueID := range strings.SplitSeq(commaSeparatedIssueIDs, ",") {
issueID, err := strconv.ParseInt(stringIssueID, 10, 64)
if err != nil {
ctx.ServerError("ParseInt", err)
@@ -364,7 +364,9 @@ func UpdateIssueContent(ctx *context.Context) {
}
}
- rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{
+ FootnoteContextID: "0",
+ })
content, err := markdown.RenderString(rctx, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -418,6 +420,16 @@ func UpdateIssueMilestone(ctx *context.Context) {
continue
}
issue.MilestoneID = milestoneID
+ if milestoneID > 0 {
+ var err error
+ issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
+ if err != nil {
+ ctx.ServerError("GetMilestoneByRepoID", err)
+ return
+ }
+ } else {
+ issue.Milestone = nil
+ }
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
ctx.ServerError("ChangeMilestoneAssign", err)
return
diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go
index 8adce26ccc..c2a7f6b682 100644
--- a/routers/web/repo/issue_comment.go
+++ b/routers/web/repo/issue_comment.go
@@ -8,6 +8,7 @@ import (
"fmt"
"html/template"
"net/http"
+ "strconv"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/renderhelper"
@@ -239,21 +240,28 @@ func UpdateCommentContent(ctx *context.Context) {
return
}
- oldContent := comment.Content
newContent := ctx.FormString("content")
contentVersion := ctx.FormInt("content_version")
+ if contentVersion != comment.ContentVersion {
+ ctx.JSONError(ctx.Tr("repo.comments.edit.already_changed"))
+ return
+ }
- // allow to save empty content
- comment.Content = newContent
- if err = issue_service.UpdateComment(ctx, comment, contentVersion, ctx.Doer, oldContent); err != nil {
- if errors.Is(err, user_model.ErrBlockedUser) {
- ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
- } else if errors.Is(err, issues_model.ErrCommentAlreadyChanged) {
- ctx.JSONError(ctx.Tr("repo.comments.edit.already_changed"))
- } else {
- ctx.ServerError("UpdateComment", err)
+ if newContent != comment.Content {
+ // allow to save empty content
+ oldContent := comment.Content
+ comment.Content = newContent
+
+ if err = issue_service.UpdateComment(ctx, comment, contentVersion, ctx.Doer, oldContent); err != nil {
+ if errors.Is(err, user_model.ErrBlockedUser) {
+ ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
+ } else if errors.Is(err, issues_model.ErrCommentAlreadyChanged) {
+ ctx.JSONError(ctx.Tr("repo.comments.edit.already_changed"))
+ } else {
+ ctx.ServerError("UpdateComment", err)
+ }
+ return
}
- return
}
if err := comment.LoadAttachments(ctx); err != nil {
@@ -271,7 +279,9 @@ func UpdateCommentContent(ctx *context.Context) {
var renderedContent template.HTML
if comment.Content != "" {
- rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{
+ FootnoteContextID: strconv.FormatInt(comment.ID, 10),
+ })
renderedContent, err = markdown.RenderString(rctx, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index f9c41adbcf..72a316e98d 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -4,6 +4,7 @@
package repo
import (
+ "errors"
"net/http"
"code.gitea.io/gitea/models/db"
@@ -13,7 +14,9 @@ import (
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ shared_label "code.gitea.io/gitea/routers/web/shared/label"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
issue_service "code.gitea.io/gitea/services/issue"
@@ -100,13 +103,8 @@ func RetrieveLabelsForList(ctx *context.Context) {
// NewLabel create new label for repository
func NewLabel(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.CreateLabelForm)
- ctx.Data["Title"] = ctx.Tr("repo.labels")
- ctx.Data["PageIsLabels"] = true
-
- if ctx.HasError() {
- ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
- ctx.Redirect(ctx.Repo.RepoLink + "/labels")
+ form := shared_label.GetLabelEditForm(ctx)
+ if ctx.Written() {
return
}
@@ -122,34 +120,36 @@ func NewLabel(ctx *context.Context) {
ctx.ServerError("NewLabel", err)
return
}
- ctx.Redirect(ctx.Repo.RepoLink + "/labels")
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/labels")
}
// UpdateLabel update a label's name and color
func UpdateLabel(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.CreateLabelForm)
+ form := shared_label.GetLabelEditForm(ctx)
+ if ctx.Written() {
+ return
+ }
+
l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, form.ID)
- if err != nil {
- switch {
- case issues_model.IsErrRepoLabelNotExist(err):
- ctx.HTTPError(http.StatusNotFound)
- default:
- ctx.ServerError("UpdateLabel", err)
- }
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.JSONErrorNotFound()
+ return
+ } else if err != nil {
+ ctx.ServerError("GetLabelInRepoByID", err)
return
}
+
l.Name = form.Title
l.Exclusive = form.Exclusive
l.ExclusiveOrder = form.ExclusiveOrder
l.Description = form.Description
l.Color = form.Color
-
l.SetArchived(form.IsArchived)
if err := issues_model.UpdateLabel(ctx, l); err != nil {
ctx.ServerError("UpdateLabel", err)
return
}
- ctx.Redirect(ctx.Repo.RepoLink + "/labels")
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/labels")
}
// DeleteLabel delete a label
diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go
index c3fba07034..f4eca26f8e 100644
--- a/routers/web/repo/issue_label_test.go
+++ b/routers/web/repo/issue_label_test.go
@@ -6,10 +6,12 @@ package repo
import (
"net/http"
"strconv"
+ "strings"
"testing"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
@@ -19,19 +21,20 @@ import (
"github.com/stretchr/testify/assert"
)
-func int64SliceToCommaSeparated(a []int64) string {
- s := ""
- for i, n := range a {
- if i > 0 {
- s += ","
- }
- s += strconv.Itoa(int(n))
- }
- return s
+func TestIssueLabel(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+ t.Run("RetrieveLabels", testRetrieveLabels)
+ t.Run("NewLabel", testNewLabel)
+ t.Run("NewLabelInvalidColor", testNewLabelInvalidColor)
+ t.Run("UpdateLabel", testUpdateLabel)
+ t.Run("UpdateLabelInvalidColor", testUpdateLabelInvalidColor)
+ t.Run("UpdateIssueLabelClear", testUpdateIssueLabelClear)
+ t.Run("UpdateIssueLabelToggle", testUpdateIssueLabelToggle)
+ t.Run("InitializeLabels", testInitializeLabels)
+ t.Run("DeleteLabel", testDeleteLabel)
}
-func TestInitializeLabels(t *testing.T) {
- unittest.PrepareTestEnv(t)
+func testInitializeLabels(t *testing.T) {
assert.NoError(t, repository.LoadRepoConfig())
ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/initialize")
contexttest.LoadUser(t, ctx, 2)
@@ -47,8 +50,7 @@ func TestInitializeLabels(t *testing.T) {
assert.Equal(t, "/user2/repo2/labels", test.RedirectURL(ctx.Resp))
}
-func TestRetrieveLabels(t *testing.T) {
- unittest.PrepareTestEnv(t)
+func testRetrieveLabels(t *testing.T) {
for _, testCase := range []struct {
RepoID int64
Sort string
@@ -74,9 +76,8 @@ func TestRetrieveLabels(t *testing.T) {
}
}
-func TestNewLabel(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/edit")
+func testNewLabel(t *testing.T) {
+ ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
web.SetForm(ctx, &forms.CreateLabelForm{
@@ -84,17 +85,32 @@ func TestNewLabel(t *testing.T) {
Color: "#abcdef",
})
NewLabel(ctx)
- assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus())
+ assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{
Name: "newlabel",
Color: "#abcdef",
})
- assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(ctx.Resp))
+ assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(respWriter))
}
-func TestUpdateLabel(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/edit")
+func testNewLabelInvalidColor(t *testing.T) {
+ ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit")
+ contexttest.LoadUser(t, ctx, 2)
+ contexttest.LoadRepo(t, ctx, 1)
+ web.SetForm(ctx, &forms.CreateLabelForm{
+ Title: "newlabel-x",
+ Color: "bad-label-code",
+ })
+ NewLabel(ctx)
+ assert.Equal(t, http.StatusBadRequest, ctx.Resp.WrittenStatus())
+ assert.Equal(t, "repo.issues.label_color_invalid", test.ParseJSONError(respWriter.Body.Bytes()).ErrorMessage)
+ unittest.AssertNotExistsBean(t, &issues_model.Label{
+ Name: "newlabel-x",
+ })
+}
+
+func testUpdateLabel(t *testing.T) {
+ ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
web.SetForm(ctx, &forms.CreateLabelForm{
@@ -104,17 +120,37 @@ func TestUpdateLabel(t *testing.T) {
IsArchived: true,
})
UpdateLabel(ctx)
- assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus())
+ assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{
ID: 2,
Name: "newnameforlabel",
Color: "#abcdef",
})
- assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(ctx.Resp))
+ assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(respWriter))
}
-func TestDeleteLabel(t *testing.T) {
- unittest.PrepareTestEnv(t)
+func testUpdateLabelInvalidColor(t *testing.T) {
+ ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit")
+ contexttest.LoadUser(t, ctx, 2)
+ contexttest.LoadRepo(t, ctx, 1)
+ web.SetForm(ctx, &forms.CreateLabelForm{
+ ID: 1,
+ Title: "label1",
+ Color: "bad-label-code",
+ })
+
+ UpdateLabel(ctx)
+
+ assert.Equal(t, http.StatusBadRequest, ctx.Resp.WrittenStatus())
+ assert.Equal(t, "repo.issues.label_color_invalid", test.ParseJSONError(respWriter.Body.Bytes()).ErrorMessage)
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Label{
+ ID: 1,
+ Name: "label1",
+ Color: "#abcdef",
+ })
+}
+
+func testDeleteLabel(t *testing.T) {
ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/delete")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
@@ -126,8 +162,7 @@ func TestDeleteLabel(t *testing.T) {
assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg)
}
-func TestUpdateIssueLabel_Clear(t *testing.T) {
- unittest.PrepareTestEnv(t)
+func testUpdateIssueLabelClear(t *testing.T) {
ctx, _ := contexttest.MockContext(t, "user2/repo1/issues/labels")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
@@ -140,7 +175,7 @@ func TestUpdateIssueLabel_Clear(t *testing.T) {
unittest.CheckConsistencyFor(t, &issues_model.Label{})
}
-func TestUpdateIssueLabel_Toggle(t *testing.T) {
+func testUpdateIssueLabelToggle(t *testing.T) {
for _, testCase := range []struct {
Action string
IssueIDs []int64
@@ -156,7 +191,8 @@ func TestUpdateIssueLabel_Toggle(t *testing.T) {
ctx, _ := contexttest.MockContext(t, "user2/repo1/issues/labels")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
- ctx.Req.Form.Set("issue_ids", int64SliceToCommaSeparated(testCase.IssueIDs))
+
+ ctx.Req.Form.Set("issue_ids", strings.Join(base.Int64sToStrings(testCase.IssueIDs), ","))
ctx.Req.Form.Set("action", testCase.Action)
ctx.Req.Form.Set("id", strconv.Itoa(int(testCase.LabelID)))
UpdateIssueLabel(ctx)
diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go
index 35107bc585..b55f4bcc90 100644
--- a/routers/web/repo/issue_list.go
+++ b/routers/web/repo/issue_list.go
@@ -61,7 +61,7 @@ func SearchIssues(ctx *context.Context) {
)
{
// find repos user can access (for issue search)
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
Private: false,
AllPublic: true,
TopicOnly: false,
diff --git a/routers/web/repo/issue_new.go b/routers/web/repo/issue_new.go
index d8863961ff..887019b146 100644
--- a/routers/web/repo/issue_new.go
+++ b/routers/web/repo/issue_new.go
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"html/template"
+ "maps"
"net/http"
"slices"
"sort"
@@ -136,9 +137,7 @@ func NewIssue(ctx *context.Context) {
ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates, pageMetaData)
- for k, v := range errs {
- ret.TemplateErrors[k] = v
- }
+ maps.Copy(ret.TemplateErrors, errs)
if ctx.Written() {
return
}
diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go
index 5a8d203771..2de3a7cfec 100644
--- a/routers/web/repo/issue_stopwatch.go
+++ b/routers/web/repo/issue_stopwatch.go
@@ -10,33 +10,47 @@ import (
"code.gitea.io/gitea/services/context"
)
-// IssueStopwatch creates or stops a stopwatch for the given issue.
-func IssueStopwatch(c *context.Context) {
+// IssueStartStopwatch creates a stopwatch for the given issue.
+func IssueStartStopwatch(c *context.Context) {
issue := GetActionIssue(c)
if c.Written() {
return
}
- var showSuccessMessage bool
-
- if !issues_model.StopwatchExists(c, c.Doer.ID, issue.ID) {
- showSuccessMessage = true
- }
-
if !c.Repo.CanUseTimetracker(c, issue, c.Doer) {
c.NotFound(nil)
return
}
- if err := issues_model.CreateOrStopIssueStopwatch(c, c.Doer, issue); err != nil {
- c.ServerError("CreateOrStopIssueStopwatch", err)
+ if ok, err := issues_model.CreateIssueStopwatch(c, c.Doer, issue); err != nil {
+ c.ServerError("CreateIssueStopwatch", err)
return
+ } else if !ok {
+ c.Flash.Warning(c.Tr("repo.issues.stopwatch_already_created"))
+ } else {
+ c.Flash.Success(c.Tr("repo.issues.tracker_auto_close"))
}
+ c.JSONRedirect("")
+}
- if showSuccessMessage {
- c.Flash.Success(c.Tr("repo.issues.tracker_auto_close"))
+// IssueStopStopwatch stops a stopwatch for the given issue.
+func IssueStopStopwatch(c *context.Context) {
+ issue := GetActionIssue(c)
+ if c.Written() {
+ return
+ }
+
+ if !c.Repo.CanUseTimetracker(c, issue, c.Doer) {
+ c.NotFound(nil)
+ return
}
+ if ok, err := issues_model.FinishIssueStopwatch(c, c.Doer, issue); err != nil {
+ c.ServerError("FinishIssueStopwatch", err)
+ return
+ } else if !ok {
+ c.Flash.Warning(c.Tr("repo.issues.stopwatch_already_stopped"))
+ }
c.JSONRedirect("")
}
@@ -51,7 +65,7 @@ func CancelStopwatch(c *context.Context) {
return
}
- if err := issues_model.CancelStopwatch(c, c.Doer, issue); err != nil {
+ if _, err := issues_model.CancelStopwatch(c, c.Doer, issue); err != nil {
c.ServerError("CancelStopwatch", err)
return
}
diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go
index 13b9d83da4..d4458ed19e 100644
--- a/routers/web/repo/issue_view.go
+++ b/routers/web/repo/issue_view.go
@@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"sort"
+ "strconv"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
@@ -442,6 +443,10 @@ func ViewPullMergeBox(ctx *context.Context) {
preparePullViewPullInfo(ctx, issue)
preparePullViewReviewAndMerge(ctx, issue)
ctx.Data["PullMergeBoxReloading"] = issue.PullRequest.IsChecking()
+
+ // TODO: it should use a dedicated struct to render the pull merge box, to make sure all data is prepared correctly
+ ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
+ ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
ctx.HTML(http.StatusOK, tplPullMergeBox)
}
@@ -624,7 +629,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue
comment.Issue = issue
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
- rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{
+ FootnoteContextID: strconv.FormatInt(comment.ID, 10),
+ })
comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -700,7 +707,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue
}
}
} else if comment.Type.HasContentSupport() {
- rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{
+ FootnoteContextID: strconv.FormatInt(comment.ID, 10),
+ })
comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -757,6 +766,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue
}
if !ctx.Repo.CanRead(unit.TypeActions) {
for _, commit := range comment.Commits {
+ if commit.Status == nil {
+ continue
+ }
commit.Status.HideActionsURL(ctx)
git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses)
}
@@ -981,7 +993,9 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss
func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) {
var err error
- rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{
+ FootnoteContextID: "0", // Set footnote context ID to 0 for the issue content
+ })
issue.RenderedContent, err = markdown.RenderString(rctx, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index 8a26a0dcc3..dd53b1d3f1 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -38,10 +38,7 @@ func Milestones(ctx *context.Context) {
isShowClosed := ctx.FormString("state") == "closed"
sortType := ctx.FormString("sort")
keyword := ctx.FormTrim("q")
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
miles, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go
index 65a340a799..d09a57c03f 100644
--- a/routers/web/repo/packages.go
+++ b/routers/web/repo/packages.go
@@ -21,10 +21,7 @@ const (
// Packages displays a list of all packages in the repository
func Packages(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
query := ctx.FormTrim("q")
packageType := ctx.FormTrim("type")
diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go
deleted file mode 100644
index ca346b7e6c..0000000000
--- a/routers/web/repo/patch.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package repo
-
-import (
- "net/http"
- "strings"
-
- git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/templates"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/services/context"
- "code.gitea.io/gitea/services/forms"
- "code.gitea.io/gitea/services/repository/files"
-)
-
-const (
- tplPatchFile templates.TplName = "repo/editor/patch"
-)
-
-// NewDiffPatch render create patch page
-func NewDiffPatch(ctx *context.Context) {
- canCommit := renderCommitRights(ctx)
-
- ctx.Data["PageIsPatch"] = true
-
- ctx.Data["commit_summary"] = ""
- ctx.Data["commit_message"] = ""
- if canCommit {
- ctx.Data["commit_choice"] = frmCommitChoiceDirect
- } else {
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- }
- ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
- ctx.Data["last_commit"] = ctx.Repo.CommitID
- ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
-
- ctx.HTML(http.StatusOK, tplPatchFile)
-}
-
-// NewDiffPatchPost response for sending patch page
-func NewDiffPatchPost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.EditRepoFileForm)
-
- canCommit := renderCommitRights(ctx)
- branchName := ctx.Repo.BranchName
- if form.CommitChoice == frmCommitChoiceNewBranch {
- branchName = form.NewBranchName
- }
- ctx.Data["PageIsPatch"] = true
- ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
- ctx.Data["FileContent"] = form.Content
- ctx.Data["commit_summary"] = form.CommitSummary
- ctx.Data["commit_message"] = form.CommitMessage
- ctx.Data["commit_choice"] = form.CommitChoice
- ctx.Data["new_branch_name"] = form.NewBranchName
- ctx.Data["last_commit"] = ctx.Repo.CommitID
- ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
-
- if ctx.HasError() {
- ctx.HTML(http.StatusOK, tplPatchFile)
- return
- }
-
- // Cannot commit to an existing branch if user doesn't have rights
- if branchName == ctx.Repo.BranchName && !canCommit {
- ctx.Data["Err_NewBranchName"] = true
- ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
- ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
- return
- }
-
- // CommitSummary is optional in the web form, if empty, give it a default message based on add or update
- // `message` will be both the summary and message combined
- message := strings.TrimSpace(form.CommitSummary)
- if len(message) == 0 {
- message = ctx.Locale.TrString("repo.editor.patch")
- }
-
- form.CommitMessage = strings.TrimSpace(form.CommitMessage)
- if len(form.CommitMessage) > 0 {
- message += "\n\n" + form.CommitMessage
- }
-
- gitCommitter, valid := WebGitOperationGetCommitChosenEmailIdentity(ctx, form.CommitEmail)
- if !valid {
- ctx.Data["Err_CommitEmail"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_email"), tplPatchFile, &form)
- return
- }
-
- fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
- LastCommitID: form.LastCommit,
- OldBranch: ctx.Repo.BranchName,
- NewBranch: branchName,
- Message: message,
- Content: strings.ReplaceAll(form.Content, "\r", ""),
- Author: gitCommitter,
- Committer: gitCommitter,
- })
- if err != nil {
- if git_model.IsErrBranchAlreadyExists(err) {
- // User has specified a branch that already exists
- branchErr := err.(git_model.ErrBranchAlreadyExists)
- ctx.Data["Err_NewBranchName"] = true
- ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
- return
- } else if files.IsErrCommitIDDoesNotMatch(err) {
- ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form)
- return
- }
- ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_apply_patch", err), tplPatchFile, &form)
- return
- }
-
- if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
- ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
- } else {
- ctx.Redirect(ctx.Repo.RepoLink + "/commit/" + fileResponse.Commit.SHA)
- }
-}
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 0bf1f64d09..a57976b4ca 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -61,10 +61,7 @@ func Projects(ctx *context.Context) {
isShowClosed := strings.ToLower(ctx.FormTrim("state")) == "closed"
keyword := ctx.FormTrim("q")
repo := ctx.Repo.Repository
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
ctx.Data["OpenCount"] = repo.NumOpenProjects
ctx.Data["ClosedCount"] = repo.NumClosedProjects
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 43ddc265cf..f662152e2e 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -27,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/fileicon"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/graceful"
issue_template "code.gitea.io/gitea/modules/issue/template"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -291,7 +292,7 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
if len(compareInfo.Commits) != 0 {
sha := compareInfo.Commits[0].ID.String()
- commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll)
+ commitStatuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll)
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
@@ -358,7 +359,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
return nil
}
- commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
+ commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
@@ -454,7 +455,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return nil
}
- commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
+ commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
@@ -986,7 +987,9 @@ func UpdatePullRequest(ctx *context.Context) {
// default merge commit message
message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
- if err = pull_service.Update(ctx, issue.PullRequest, ctx.Doer, message, rebase); err != nil {
+ // The update process should not be cancelled by the user
+ // so we set the context to be a background context
+ if err = pull_service.Update(graceful.GetManager().ShutdownContext(), issue.PullRequest, ctx.Doer, message, rebase); err != nil {
if pull_service.IsErrMergeConflicts(err) {
conflictError := err.(pull_service.ErrMergeConflicts)
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
@@ -1255,13 +1258,8 @@ func CancelAutoMergePullRequest(ctx *context.Context) {
}
func stopTimerIfAvailable(ctx *context.Context, user *user_model.User, issue *issues_model.Issue) error {
- if issues_model.StopwatchExists(ctx, user.ID, issue.ID) {
- if err := issues_model.CreateOrStopIssueStopwatch(ctx, user, issue); err != nil {
- return err
- }
- }
-
- return nil
+ _, err := issues_model.FinishIssueStopwatch(ctx, user, issue)
+ return err
}
func PullsNewRedirect(ctx *context.Context) {
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 553bdbf6e5..36ea20c23e 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"net/http"
+ "strconv"
"strings"
"code.gitea.io/gitea/models/db"
@@ -102,7 +103,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
releaseInfos := make([]*ReleaseInfo, 0, len(releases))
for _, r := range releases {
if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok {
- r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
+ r.Publisher, err = user_model.GetPossibleUserByID(ctx, r.PublisherID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
r.Publisher = user_model.NewGhostUser()
@@ -113,7 +114,9 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
cacheUsers[r.PublisherID] = r.Publisher
}
- rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo, renderhelper.RepoCommentOptions{
+ FootnoteContextID: strconv.FormatInt(r.ID, 10),
+ })
r.RenderedNote, err = markdown.RenderString(rctx, r.Note)
if err != nil {
return nil, err
@@ -130,7 +133,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
}
if canReadActions {
- statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptionsAll)
+ statuses, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptionsAll)
if err != nil {
return nil, err
}
@@ -378,7 +381,7 @@ func NewRelease(ctx *context.Context) {
ctx.Data["ShowCreateTagOnlyButton"] = false
ctx.Data["tag_name"] = rel.TagName
- ctx.Data["tag_target"] = rel.Target
+ ctx.Data["tag_target"] = util.IfZero(rel.Target, ctx.Repo.Repository.DefaultBranch)
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["attachments"] = rel.Attachments
@@ -534,7 +537,7 @@ func EditRelease(ctx *context.Context) {
}
ctx.Data["ID"] = rel.ID
ctx.Data["tag_name"] = rel.TagName
- ctx.Data["tag_target"] = rel.Target
+ ctx.Data["tag_target"] = util.IfZero(rel.Target, ctx.Repo.Repository.DefaultBranch)
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease
@@ -580,7 +583,7 @@ func EditReleasePost(ctx *context.Context) {
return
}
ctx.Data["tag_name"] = rel.TagName
- ctx.Data["tag_target"] = rel.Target
+ ctx.Data["tag_target"] = util.IfZero(rel.Target, ctx.Repo.Repository.DefaultBranch)
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index ee112b83f2..828ec08a8a 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -88,7 +88,7 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User {
}
var orgsAvailable []*organization.Organization
- for i := 0; i < len(orgs); i++ {
+ for i := range orgs {
if ctx.Doer.CanCreateRepoIn(orgs[i].AsUser()) {
orgsAvailable = append(orgsAvailable, orgs[i])
}
@@ -461,7 +461,7 @@ func SearchRepo(ctx *context.Context) {
if page <= 0 {
page = 1
}
- opts := &repo_model.SearchRepoOptions{
+ opts := repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go
index a065620b2b..af6708e841 100644
--- a/routers/web/repo/setting/lfs.go
+++ b/routers/web/repo/setting/lfs.go
@@ -45,10 +45,7 @@ func LFSFiles(ctx *context.Context) {
ctx.NotFound(nil)
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
total, err := git_model.CountLFSMetaObjects(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("LFSFiles", err)
@@ -77,10 +74,7 @@ func LFSLocks(ctx *context.Context) {
}
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
total, err := git_model.CountLFSLockByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("LFSLocks", err)
@@ -273,8 +267,10 @@ func LFSFileGet(ctx *context.Context) {
buf = buf[:n]
st := typesniffer.DetectContentType(buf)
+ // FIXME: there is no IsPlainText set, but template uses it
ctx.Data["IsTextFile"] = st.IsText()
ctx.Data["FileSize"] = meta.Size
+ // FIXME: the last field is the URL-base64-encoded filename, it should not be "direct"
ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s/%s.git/info/lfs/objects/%s/%s", setting.AppURL, url.PathEscape(ctx.Repo.Repository.OwnerName), url.PathEscape(ctx.Repo.Repository.Name), url.PathEscape(meta.Oid), "direct")
switch {
case st.IsRepresentableAsText():
@@ -315,8 +311,6 @@ func LFSFileGet(ctx *context.Context) {
}
ctx.Data["LineNums"] = gotemplate.HTML(output.String())
- case st.IsPDF():
- ctx.Data["IsPDFFile"] = true
case st.IsVideo():
ctx.Data["IsVideoFile"] = true
case st.IsAudio():
diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go
index f241242f02..0eea5e3f34 100644
--- a/routers/web/repo/setting/protected_branch.go
+++ b/routers/web/repo/setting/protected_branch.go
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
@@ -89,7 +90,7 @@ func SettingsProtectedBranch(c *context.Context) {
c.Data["recent_status_checks"] = contexts
if c.Repo.Owner.IsOrganization() {
- teams, err := organization.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c, c.Repo.Repository.ID, perm.AccessModeRead)
+ teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(c, c.Repo.Owner.ID, c.Repo.Repository.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
if err != nil {
c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
return
diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go
index 33692778d5..50f5a28c4c 100644
--- a/routers/web/repo/setting/protected_tag.go
+++ b/routers/web/repo/setting/protected_tag.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
+ "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
@@ -156,7 +157,7 @@ func setTagsContext(ctx *context.Context) error {
ctx.Data["Users"] = users
if ctx.Repo.Owner.IsOrganization() {
- teams, err := organization.OrgFromUser(ctx.Repo.Owner).TeamsWithAccessToRepo(ctx, ctx.Repo.Repository.ID, perm.AccessModeRead)
+ teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, ctx.Repo.Owner.ID, ctx.Repo.Repository.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
if err != nil {
ctx.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
return err
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index 5552a8726c..6e16ead183 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -62,7 +62,7 @@ func SettingsCtxData(ctx *context.Context) {
ctx.Data["CanConvertFork"] = ctx.Repo.Repository.IsFork && ctx.Doer.CanCreateRepoIn(ctx.Repo.Repository.Owner)
signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath())
- ctx.Data["SigningKeyAvailable"] = len(signing) > 0
+ ctx.Data["SigningKeyAvailable"] = signing != nil
ctx.Data["SigningSettings"] = setting.Repository.Signing
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
@@ -105,7 +105,7 @@ func SettingsPost(ctx *context.Context) {
ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval
signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath())
- ctx.Data["SigningKeyAvailable"] = len(signing) > 0
+ ctx.Data["SigningKeyAvailable"] = signing != nil
ctx.Data["SigningSettings"] = setting.Repository.Signing
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
@@ -663,44 +663,37 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
func handleSettingsPostSigning(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RepoSettingForm)
repo := ctx.Repo.Repository
- changed := false
trustModel := repo_model.ToTrustModel(form.TrustModel)
if trustModel != repo.TrustModel {
repo.TrustModel = trustModel
- changed = true
- }
-
- if changed {
- if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
- ctx.ServerError("UpdateRepository", err)
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "trust_model"); err != nil {
+ ctx.ServerError("UpdateRepositoryColsNoAutoTime", err)
return
}
+ log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
}
- log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
}
func handleSettingsPostAdmin(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.RepoSettingForm)
- repo := ctx.Repo.Repository
if !ctx.Doer.IsAdmin {
ctx.HTTPError(http.StatusForbidden)
return
}
+ repo := ctx.Repo.Repository
+ form := web.GetForm(ctx).(*forms.RepoSettingForm)
if repo.IsFsckEnabled != form.EnableHealthCheck {
repo.IsFsckEnabled = form.EnableHealthCheck
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_fsck_enabled"); err != nil {
+ ctx.ServerError("UpdateRepositoryColsNoAutoTime", err)
+ return
+ }
+ log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
}
- if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
- ctx.ServerError("UpdateRepository", err)
- return
- }
-
- log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
-
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
}
diff --git a/routers/web/repo/setting/settings_test.go b/routers/web/repo/setting/settings_test.go
index ba6b0d1efc..15ebea888c 100644
--- a/routers/web/repo/setting/settings_test.go
+++ b/routers/web/repo/setting/settings_test.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/contexttest"
@@ -24,23 +25,8 @@ import (
"github.com/stretchr/testify/assert"
)
-func createSSHAuthorizedKeysTmpPath(t *testing.T) func() {
- tmpDir := t.TempDir()
-
- oldPath := setting.SSH.RootPath
- setting.SSH.RootPath = tmpDir
-
- return func() {
- setting.SSH.RootPath = oldPath
- }
-}
-
func TestAddReadOnlyDeployKey(t *testing.T) {
- if deferable := createSSHAuthorizedKeysTmpPath(t); deferable != nil {
- defer deferable()
- } else {
- return
- }
+ defer test.MockVariableValue(&setting.SSH.RootPath, t.TempDir())()
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1/settings/keys")
@@ -64,11 +50,7 @@ func TestAddReadOnlyDeployKey(t *testing.T) {
}
func TestAddReadWriteOnlyDeployKey(t *testing.T) {
- if deferable := createSSHAuthorizedKeysTmpPath(t); deferable != nil {
- defer deferable()
- } else {
- return
- }
+ defer test.MockVariableValue(&setting.SSH.RootPath, t.TempDir())()
unittest.PrepareTestEnv(t)
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index d3151a86a2..006abafe57 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -185,6 +185,7 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
webhook_module.HookEventRepository: form.Repository,
webhook_module.HookEventPackage: form.Package,
webhook_module.HookEventStatus: form.Status,
+ webhook_module.HookEventWorkflowRun: form.WorkflowRun,
webhook_module.HookEventWorkflowJob: form.WorkflowJob,
},
BranchFilter: form.BranchFilter,
diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go
index 0248a0627b..7d7f5a1473 100644
--- a/routers/web/repo/treelist.go
+++ b/routers/web/repo/treelist.go
@@ -6,6 +6,7 @@ package repo
import (
"html/template"
"net/http"
+ "path"
"strings"
pull_model "code.gitea.io/gitea/models/pull"
@@ -111,7 +112,7 @@ func transformDiffTreeForWeb(renderedIconPool *fileicon.RenderedIconPool, diffTr
item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
item.NameHash = git.HashFilePathForWebUI(item.FullName)
- item.FileIcon = fileicon.RenderEntryIconHTML(renderedIconPool, &fileicon.EntryInfo{FullName: file.HeadPath, EntryMode: file.HeadMode})
+ item.FileIcon = fileicon.RenderEntryIconHTML(renderedIconPool, &fileicon.EntryInfo{BaseName: path.Base(file.HeadPath), EntryMode: file.HeadMode})
switch file.HeadMode {
case git.EntryModeTree:
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 2f01434684..773919c054 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -12,6 +12,7 @@ import (
"io"
"net/http"
"net/url"
+ "path"
"strings"
"time"
@@ -59,60 +60,63 @@ const (
)
type fileInfo struct {
- isTextFile bool
- isLFSFile bool
- fileSize int64
- lfsMeta *lfs.Pointer
- st typesniffer.SniffedType
+ fileSize int64
+ lfsMeta *lfs.Pointer
+ st typesniffer.SniffedType
}
-func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, io.ReadCloser, *fileInfo, error) {
- dataRc, err := blob.DataAsync()
+func (fi *fileInfo) isLFSFile() bool {
+ return fi.lfsMeta != nil && fi.lfsMeta.Oid != ""
+}
+
+func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) (buf []byte, dataRc io.ReadCloser, fi *fileInfo, err error) {
+ dataRc, err = blob.DataAsync()
if err != nil {
return nil, nil, nil, err
}
- buf := make([]byte, 1024)
+ const prefetchSize = lfs.MetaFileMaxSize
+
+ buf = make([]byte, prefetchSize)
n, _ := util.ReadAtMost(dataRc, buf)
buf = buf[:n]
- st := typesniffer.DetectContentType(buf)
- isTextFile := st.IsText()
+ fi = &fileInfo{fileSize: blob.Size(), st: typesniffer.DetectContentType(buf)}
// FIXME: what happens when README file is an image?
- if !isTextFile || !setting.LFS.StartServer {
- return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
+ if !fi.st.IsText() || !setting.LFS.StartServer {
+ return buf, dataRc, fi, nil
}
pointer, _ := lfs.ReadPointerFromBuffer(buf)
- if !pointer.IsValid() { // fallback to plain file
- return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
+ if !pointer.IsValid() { // fallback to a plain file
+ return buf, dataRc, fi, nil
}
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
- if err != nil { // fallback to plain file
+ if err != nil { // fallback to a plain file
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
- return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
+ return buf, dataRc, fi, nil
}
- dataRc.Close()
-
+ // close the old dataRc and open the real LFS target
+ _ = dataRc.Close()
dataRc, err = lfs.ReadMetaObject(pointer)
if err != nil {
return nil, nil, nil, err
}
- buf = make([]byte, 1024)
+ buf = make([]byte, prefetchSize)
n, err = util.ReadAtMost(dataRc, buf)
if err != nil {
- dataRc.Close()
- return nil, nil, nil, err
+ _ = dataRc.Close()
+ return nil, nil, fi, err
}
buf = buf[:n]
-
- st = typesniffer.DetectContentType(buf)
-
- return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
+ fi.st = typesniffer.DetectContentType(buf)
+ fi.fileSize = blob.Size()
+ fi.lfsMeta = &meta.Pointer
+ return buf, dataRc, fi, nil
}
func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
@@ -131,7 +135,7 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
ctx.Data["LatestCommitVerification"] = verification
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit)
- statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptionsAll)
+ statuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptionsAll)
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
@@ -257,7 +261,9 @@ func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) {
renderedIconPool := fileicon.NewRenderedIconPool()
fileIcons := map[string]template.HTML{}
for _, f := range files {
- fileIcons[f.Entry.Name()] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFromGitTreeEntry(f.Entry))
+ fullPath := path.Join(ctx.Repo.TreePath, f.Entry.Name())
+ entryInfo := fileicon.EntryInfoFromGitTreeEntry(ctx.Repo.Commit, fullPath, f.Entry)
+ fileIcons[f.Entry.Name()] = fileicon.RenderEntryIconHTML(renderedIconPool, entryInfo)
}
fileIcons[".."] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
ctx.Data["FileIcons"] = fileIcons
@@ -394,9 +400,10 @@ func Forks(ctx *context.Context) {
}
pager := context.NewPagination(int(total), pageSize, page, 5)
+ ctx.Data["ShowRepoOwnerAvatar"] = true
+ ctx.Data["ShowRepoOwnerOnList"] = true
ctx.Data["Page"] = pager
-
- ctx.Data["Forks"] = forks
+ ctx.Data["Repos"] = forks
ctx.HTML(http.StatusOK, tplForks)
}
diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go
index 3df6051975..2d5bddd939 100644
--- a/routers/web/repo/view_file.go
+++ b/routers/web/repo/view_file.go
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
@@ -40,7 +41,128 @@ func prepareLatestCommitInfo(ctx *context.Context) bool {
return loadLatestCommitData(ctx, commit)
}
-func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
+func prepareFileViewLfsAttrs(ctx *context.Context) (*attribute.Attributes, bool) {
+ attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{
+ Filenames: []string{ctx.Repo.TreePath},
+ Attributes: []string{attribute.LinguistGenerated, attribute.LinguistVendored, attribute.LinguistLanguage, attribute.GitlabLanguage},
+ })
+ if err != nil {
+ ctx.ServerError("attribute.CheckAttributes", err)
+ return nil, false
+ }
+ attrs := attrsMap[ctx.Repo.TreePath]
+ if attrs == nil {
+ // this case shouldn't happen, just in case.
+ setting.PanicInDevOrTesting("no attributes found for %s", ctx.Repo.TreePath)
+ attrs = attribute.NewAttributes()
+ }
+ ctx.Data["IsVendored"], ctx.Data["IsGenerated"] = attrs.GetVendored().Value(), attrs.GetGenerated().Value()
+ return attrs, true
+}
+
+func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte, utf8Reader io.Reader) bool {
+ markupType := markup.DetectMarkupTypeByFileName(filename)
+ if markupType == "" {
+ markupType = markup.DetectRendererType(filename, sniffedType, prefetchBuf)
+ }
+ if markupType == "" {
+ return false
+ }
+
+ ctx.Data["HasSourceRenderedToggle"] = true
+
+ if ctx.FormString("display") == "source" {
+ return false
+ }
+
+ ctx.Data["MarkupType"] = markupType
+ metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx)
+ metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
+ rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
+ CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
+ CurrentTreePath: path.Dir(ctx.Repo.TreePath),
+ }).
+ WithMarkupType(markupType).
+ WithRelativePath(ctx.Repo.TreePath).
+ WithMetas(metas)
+
+ var err error
+ ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, utf8Reader)
+ if err != nil {
+ ctx.ServerError("Render", err)
+ return true
+ }
+ // to prevent iframe from loading third-party url
+ ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
+ return true
+}
+
+func handleFileViewRenderSource(ctx *context.Context, filename string, attrs *attribute.Attributes, fInfo *fileInfo, utf8Reader io.Reader) bool {
+ if ctx.FormString("display") == "rendered" || !fInfo.st.IsRepresentableAsText() {
+ return false
+ }
+
+ if !fInfo.st.IsText() {
+ if ctx.FormString("display") == "" {
+ // not text but representable as text, e.g. SVG
+ // since there is no "display" is specified, let other renders to handle
+ return false
+ }
+ ctx.Data["HasSourceRenderedToggle"] = true
+ }
+
+ buf, _ := io.ReadAll(utf8Reader)
+ // The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
+ // empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
+ // Gitea uses the definition (like most modern editors):
+ // empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
+ // When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
+ // To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
+ // This NumLines is only used for the display on the UI: "xxx lines"
+ if len(buf) == 0 {
+ ctx.Data["NumLines"] = 0
+ } else {
+ ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
+ }
+
+ language := attrs.GetLanguage().Value()
+ fileContent, lexerName, err := highlight.File(filename, language, buf)
+ ctx.Data["LexerName"] = lexerName
+ if err != nil {
+ log.Error("highlight.File failed, fallback to plain text: %v", err)
+ fileContent = highlight.PlainText(buf)
+ }
+ status := &charset.EscapeStatus{}
+ statuses := make([]*charset.EscapeStatus, len(fileContent))
+ for i, line := range fileContent {
+ statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
+ status = status.Or(statuses[i])
+ }
+ ctx.Data["EscapeStatus"] = status
+ ctx.Data["FileContent"] = fileContent
+ ctx.Data["LineEscapeStatus"] = statuses
+ return true
+}
+
+func handleFileViewRenderImage(ctx *context.Context, fInfo *fileInfo, prefetchBuf []byte) bool {
+ if !fInfo.st.IsImage() {
+ return false
+ }
+ if fInfo.st.IsSvgImage() && !setting.UI.SVG.Enabled {
+ return false
+ }
+ if fInfo.st.IsSvgImage() {
+ ctx.Data["HasSourceRenderedToggle"] = true
+ } else {
+ img, _, err := image.DecodeConfig(bytes.NewReader(prefetchBuf))
+ if err == nil { // ignore the error for the formats that are not supported by image.DecodeConfig
+ ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
+ }
+ }
+ return true
+}
+
+func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["IsViewFile"] = true
ctx.Data["HideRepoInfo"] = true
@@ -86,11 +208,8 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
}
}
- isDisplayingSource := ctx.FormString("display") == "source"
- isDisplayingRendered := !isDisplayingSource
-
// Don't call any other repository functions depends on git.Repository until the dataRc closed to
- // avoid create unnecessary temporary cat file.
+ // avoid creating an unnecessary temporary cat file.
buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
if err != nil {
ctx.ServerError("getFileReader", err)
@@ -98,226 +217,94 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
}
defer dataRc.Close()
- if fInfo.isLFSFile {
+ if fInfo.isLFSFile() {
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
}
- isRepresentableAsText := fInfo.st.IsRepresentableAsText()
- if !isRepresentableAsText {
- // If we can't show plain text, always try to render.
- isDisplayingSource = false
- isDisplayingRendered = true
+ if !prepareFileViewEditorButtons(ctx) {
+ return
}
- ctx.Data["IsLFSFile"] = fInfo.isLFSFile
+
+ ctx.Data["IsLFSFile"] = fInfo.isLFSFile()
ctx.Data["FileSize"] = fInfo.fileSize
- ctx.Data["IsTextFile"] = fInfo.isTextFile
- ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
- ctx.Data["IsDisplayingSource"] = isDisplayingSource
- ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
+ ctx.Data["IsRepresentableAsText"] = fInfo.st.IsRepresentableAsText()
ctx.Data["IsExecutable"] = entry.IsExecutable()
+ ctx.Data["CanCopyContent"] = fInfo.st.IsRepresentableAsText() || fInfo.st.IsImage()
- isTextSource := fInfo.isTextFile || isDisplayingSource
- ctx.Data["IsTextSource"] = isTextSource
- if isTextSource {
- ctx.Data["CanCopyContent"] = true
- }
-
- // Check LFS Lock
- lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
- ctx.Data["LFSLock"] = lfsLock
- if err != nil {
- ctx.ServerError("GetTreePathLock", err)
+ attrs, ok := prepareFileViewLfsAttrs(ctx)
+ if !ok {
return
}
- if lfsLock != nil {
- u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
- if err != nil {
- ctx.ServerError("GetTreePathLock", err)
- return
- }
- ctx.Data["LFSLockOwner"] = u.Name
- ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
- ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
- }
- // Assume file is not editable first.
- if fInfo.isLFSFile {
- ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
- } else if !isRepresentableAsText {
- ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
- }
-
- // read all needed attributes which will be used later
- // there should be no performance different between reading 2 or 4 here
- attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{
- Filenames: []string{ctx.Repo.TreePath},
- Attributes: []string{attribute.LinguistGenerated, attribute.LinguistVendored, attribute.LinguistLanguage, attribute.GitlabLanguage},
- })
- if err != nil {
- ctx.ServerError("attribute.CheckAttributes", err)
- return
- }
- attrs := attrsMap[ctx.Repo.TreePath]
- if attrs == nil {
- // this case shouldn't happen, just in case.
- setting.PanicInDevOrTesting("no attributes found for %s", ctx.Repo.TreePath)
- attrs = attribute.NewAttributes()
- }
+ // TODO: in the future maybe we need more accurate flags, for example:
+ // * IsRepresentableAsText: some files are text, some are not
+ // * IsRenderableXxx: some files are rendered by backend "markup" engine, some are rendered by frontend (pdf, 3d)
+ // * DefaultViewMode: when there is no "display" query parameter, which view mode should be used by default, source or rendered
+ utf8Reader := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
switch {
- case isRepresentableAsText:
- if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
- ctx.Data["IsFileTooLarge"] = true
- break
- }
-
- if fInfo.st.IsSvgImage() {
- ctx.Data["IsImageFile"] = true
- ctx.Data["CanCopyContent"] = true
- ctx.Data["HasSourceRenderedToggle"] = true
- }
-
- rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
-
- shouldRenderSource := ctx.FormString("display") == "source"
- readmeExist := util.IsReadmeFileName(blob.Name())
- ctx.Data["ReadmeExist"] = readmeExist
-
- markupType := markup.DetectMarkupTypeByFileName(blob.Name())
- if markupType == "" {
- markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
- }
- if markupType != "" {
- ctx.Data["HasSourceRenderedToggle"] = true
- }
- if markupType != "" && !shouldRenderSource {
- ctx.Data["IsMarkup"] = true
- ctx.Data["MarkupType"] = markupType
- metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx)
- metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
- rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
- CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
- CurrentTreePath: path.Dir(ctx.Repo.TreePath),
- }).
- WithMarkupType(markupType).
- WithRelativePath(ctx.Repo.TreePath).
- WithMetas(metas)
-
- ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
- if err != nil {
- ctx.ServerError("Render", err)
- return
- }
- // to prevent iframe load third-party url
- ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
- } else {
- buf, _ := io.ReadAll(rd)
-
- // The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
- // empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
- // Gitea uses the definition (like most modern editors):
- // empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
- // When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
- // To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
- // This NumLines is only used for the display on the UI: "xxx lines"
- if len(buf) == 0 {
- ctx.Data["NumLines"] = 0
- } else {
- ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
- }
-
- language := attrs.GetLanguage().Value()
- fileContent, lexerName, err := highlight.File(blob.Name(), language, buf)
- ctx.Data["LexerName"] = lexerName
- if err != nil {
- log.Error("highlight.File failed, fallback to plain text: %v", err)
- fileContent = highlight.PlainText(buf)
- }
- status := &charset.EscapeStatus{}
- statuses := make([]*charset.EscapeStatus, len(fileContent))
- for i, line := range fileContent {
- statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
- status = status.Or(statuses[i])
- }
- ctx.Data["EscapeStatus"] = status
- ctx.Data["FileContent"] = fileContent
- ctx.Data["LineEscapeStatus"] = statuses
- }
- if !fInfo.isLFSFile {
- if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
- if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
- ctx.Data["CanEditFile"] = false
- ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
- } else {
- ctx.Data["CanEditFile"] = true
- ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
- }
- } else if !ctx.Repo.RefFullName.IsBranch() {
- ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
- } else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
- ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
- }
- }
-
- case fInfo.st.IsPDF():
- ctx.Data["IsPDFFile"] = true
+ case fInfo.fileSize >= setting.UI.MaxDisplayFileSize:
+ ctx.Data["IsFileTooLarge"] = true
+ case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, utf8Reader):
+ // it also sets ctx.Data["FileContent"] and more
+ ctx.Data["IsMarkup"] = true
+ case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, utf8Reader):
+ // it also sets ctx.Data["FileContent"] and more
+ ctx.Data["IsDisplayingSource"] = true
+ case handleFileViewRenderImage(ctx, fInfo, buf):
+ ctx.Data["IsImageFile"] = true
case fInfo.st.IsVideo():
ctx.Data["IsVideoFile"] = true
case fInfo.st.IsAudio():
ctx.Data["IsAudioFile"] = true
- case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()):
- ctx.Data["IsImageFile"] = true
- ctx.Data["CanCopyContent"] = true
default:
- if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
- ctx.Data["IsFileTooLarge"] = true
- break
- }
+ // unable to render anything, show the "view raw" or let frontend handle it
+ }
+}
- // TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
- // It is used by "external renders", markupRender will execute external programs to get rendered content.
- if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType != "" {
- rd := io.MultiReader(bytes.NewReader(buf), dataRc)
- ctx.Data["IsMarkup"] = true
- ctx.Data["MarkupType"] = markupType
-
- rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
- CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
- CurrentTreePath: path.Dir(ctx.Repo.TreePath),
- }).
- WithMarkupType(markupType).
- WithRelativePath(ctx.Repo.TreePath)
-
- ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
- if err != nil {
- ctx.ServerError("Render", err)
- return
- }
- }
+func prepareFileViewEditorButtons(ctx *context.Context) bool {
+ // archived or mirror repository, the buttons should not be shown
+ if !ctx.Repo.Repository.CanEnableEditor() {
+ return true
}
- ctx.Data["IsVendored"], ctx.Data["IsGenerated"] = attrs.GetVendored().Value(), attrs.GetGenerated().Value()
+ // The buttons should not be shown if it's not a branch
+ if !ctx.Repo.RefFullName.IsBranch() {
+ ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
+ ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
+ return true
+ }
- if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() {
- img, _, err := image.DecodeConfig(bytes.NewReader(buf))
- if err == nil {
- // There are Image formats go can't decode
- // Instead of throwing an error in that case, we show the size only when we can decode
- ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
- }
+ if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
+ ctx.Data["CanEditFile"] = true
+ ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
+ ctx.Data["CanDeleteFile"] = true
+ ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
+ return true
}
- if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
- if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
- ctx.Data["CanDeleteFile"] = false
- ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
- } else {
- ctx.Data["CanDeleteFile"] = true
- ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
+ lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
+ ctx.Data["LFSLock"] = lfsLock
+ if err != nil {
+ ctx.ServerError("GetTreePathLock", err)
+ return false
+ }
+ if lfsLock != nil {
+ u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
+ if err != nil {
+ ctx.ServerError("GetTreePathLock", err)
+ return false
}
- } else if !ctx.Repo.RefFullName.IsBranch() {
- ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
- } else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
- ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
+ ctx.Data["LFSLockOwner"] = u.Name
+ ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
+ ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
}
+
+ // it's a lfs file and the user is not the owner of the lock
+ isLFSLocked := lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID
+ ctx.Data["CanEditFile"] = !isLFSLocked
+ ctx.Data["EditFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.edit_this_file"))
+ ctx.Data["CanDeleteFile"] = !isLFSLocked
+ ctx.Data["DeleteFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.delete_this_file"))
+ return true
}
diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go
index 3b053821ee..c7396d44e3 100644
--- a/routers/web/repo/view_home.go
+++ b/routers/web/repo/view_home.go
@@ -21,6 +21,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
@@ -142,7 +143,7 @@ func prepareToRenderDirectory(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefFullName.ShortName())
}
- subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
+ subfolder, readmeFile, err := findReadmeFileInEntries(ctx, ctx.Repo.TreePath, entries, true)
if err != nil {
ctx.ServerError("findReadmeFileInEntries", err)
return
@@ -261,6 +262,10 @@ func updateContextRepoEmptyAndStatus(ctx *context.Context, empty bool, status re
func handleRepoEmptyOrBroken(ctx *context.Context) {
showEmpty := true
+ if ctx.Repo.GitRepo == nil {
+ // in case the repo really exists and works, but the status was incorrectly marked as "broken", we need to open and check it again
+ ctx.Repo.GitRepo, _ = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
+ }
if ctx.Repo.GitRepo != nil {
reallyEmpty, err := ctx.Repo.GitRepo.IsEmpty()
if err != nil {
@@ -334,7 +339,7 @@ func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
if entry.IsDir() {
prepareToRenderDirectory(ctx)
} else {
- prepareToRenderFile(ctx, entry)
+ prepareFileView(ctx, entry)
}
}
}
@@ -372,8 +377,8 @@ func prepareHomeTreeSideBarSwitch(ctx *context.Context) {
func redirectSrcToRaw(ctx *context.Context) bool {
// GitHub redirects a tree path with "?raw=1" to the raw path
- // It is useful to embed some raw contents into markdown files,
- // then viewing the markdown in "src" path could embed the raw content correctly.
+ // It is useful to embed some raw contents into Markdown files,
+ // then viewing the Markdown in "src" path could embed the raw content correctly.
if ctx.Repo.TreePath != "" && ctx.FormBool("raw") {
ctx.Redirect(ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath))
return true
@@ -381,6 +386,20 @@ func redirectSrcToRaw(ctx *context.Context) bool {
return false
}
+func redirectFollowSymlink(ctx *context.Context, treePathEntry *git.TreeEntry) bool {
+ if ctx.Repo.TreePath == "" || !ctx.FormBool("follow_symlink") {
+ return false
+ }
+ if treePathEntry.IsLink() {
+ if res, err := git.EntryFollowLinks(ctx.Repo.Commit, ctx.Repo.TreePath, treePathEntry); err == nil {
+ redirect := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(res.TargetFullPath) + "?" + ctx.Req.URL.RawQuery
+ ctx.Redirect(redirect)
+ return true
+ } // else: don't handle the links we cannot resolve, so ignore the error
+ }
+ return false
+}
+
// Home render repository home page
func Home(ctx *context.Context) {
if handleRepoHomeFeed(ctx) {
@@ -389,6 +408,7 @@ func Home(ctx *context.Context) {
if redirectSrcToRaw(ctx) {
return
}
+
// Check whether the repo is viewable: not in migration, and the code unit should be enabled
// Ideally the "feed" logic should be after this, but old code did so, so keep it as-is.
checkHomeCodeViewable(ctx)
@@ -396,10 +416,8 @@ func Home(ctx *context.Context) {
return
}
- prepareHomeTreeSideBarSwitch(ctx)
-
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
- if len(ctx.Repo.Repository.Description) > 0 {
+ if ctx.Repo.Repository.Description != "" {
title += ": " + ctx.Repo.Repository.Description
}
ctx.Data["Title"] = title
@@ -412,6 +430,8 @@ func Home(ctx *context.Context) {
return
}
+ prepareHomeTreeSideBarSwitch(ctx)
+
// get the current git entry which doer user is currently looking at.
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
if err != nil {
@@ -419,6 +439,10 @@ func Home(ctx *context.Context) {
return
}
+ if redirectFollowSymlink(ctx, entry) {
+ return
+ }
+
// prepare the tree path
var treeNames, paths []string
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go
index 459cf0a616..ba03febff3 100644
--- a/routers/web/repo/view_readme.go
+++ b/routers/web/repo/view_readme.go
@@ -32,15 +32,7 @@ import (
// entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
//
// FIXME: There has to be a more efficient way of doing this
-func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
- // Create a list of extensions in priority order
- // 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
- // 2. Txt files - e.g. README.txt
- // 3. No extension - e.g. README
- exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority
- extCount := len(exts)
- readmeFiles := make([]*git.TreeEntry, extCount+1)
-
+func findReadmeFileInEntries(ctx *context.Context, parentDir string, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/)
for _, entry := range entries {
if tryWellKnownDirs && entry.IsDir() {
@@ -62,16 +54,23 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
docsEntries[2] = entry
}
}
- continue
}
+ }
+
+ // Create a list of extensions in priority order
+ // 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
+ // 2. Txt files - e.g. README.txt
+ // 3. No extension - e.g. README
+ exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority
+ extCount := len(exts)
+ readmeFiles := make([]*git.TreeEntry, extCount+1)
+ for _, entry := range entries {
if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok {
- log.Debug("Potential readme file: %s", entry.Name())
+ fullPath := path.Join(parentDir, entry.Name())
if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) {
if entry.IsLink() {
- target, err := entry.FollowLinks()
- if err != nil && !git.IsErrSymlinkUnresolved(err) {
- return "", nil, err
- } else if target != nil && (target.IsExecutable() || target.IsRegular()) {
+ res, err := git.EntryFollowLinks(ctx.Repo.Commit, fullPath, entry)
+ if err == nil && (res.TargetEntry.IsExecutable() || res.TargetEntry.IsRegular()) {
readmeFiles[i] = entry
}
} else {
@@ -80,6 +79,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
}
}
}
+
var readmeFile *git.TreeEntry
for _, f := range readmeFiles {
if f != nil {
@@ -103,7 +103,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
return "", nil, err
}
- subfolder, readmeFile, err := findReadmeFileInEntries(ctx, childEntries, false)
+ subfolder, readmeFile, err := findReadmeFileInEntries(ctx, parentDir, childEntries, false)
if err != nil && !git.IsErrNotExist(err) {
return "", nil, err
}
@@ -139,46 +139,52 @@ func localizedExtensions(ext, languageCode string) (localizedExts []string) {
}
func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
- target := readmeFile
- if readmeFile != nil && readmeFile.IsLink() {
- target, _ = readmeFile.FollowLinks()
- }
- if target == nil {
- // if findReadmeFile() failed and/or gave us a broken symlink (which it shouldn't)
- // simply skip rendering the README
+ if readmeFile == nil {
return
}
+ readmeFullPath := path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())
+ readmeTargetEntry := readmeFile
+ if readmeFile.IsLink() {
+ if res, err := git.EntryFollowLinks(ctx.Repo.Commit, readmeFullPath, readmeFile); err == nil {
+ readmeTargetEntry = res.TargetEntry
+ } else {
+ readmeTargetEntry = nil // if we cannot resolve the symlink, we cannot render the readme, ignore the error
+ }
+ }
+ if readmeTargetEntry == nil {
+ return // if no valid README entry found, skip rendering the README
+ }
+
ctx.Data["RawFileLink"] = ""
- ctx.Data["ReadmeInList"] = true
+ ctx.Data["ReadmeInList"] = path.Join(subfolder, readmeFile.Name()) // the relative path to the readme file to the current tree path
ctx.Data["ReadmeExist"] = true
ctx.Data["FileIsSymlink"] = readmeFile.IsLink()
- buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, target.Blob())
+ buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, readmeTargetEntry.Blob())
if err != nil {
ctx.ServerError("getFileReader", err)
return
}
defer dataRc.Close()
- ctx.Data["FileIsText"] = fInfo.isTextFile
- ctx.Data["FileTreePath"] = path.Join(subfolder, readmeFile.Name())
+ ctx.Data["FileIsText"] = fInfo.st.IsText()
+ ctx.Data["FileTreePath"] = readmeFullPath
ctx.Data["FileSize"] = fInfo.fileSize
- ctx.Data["IsLFSFile"] = fInfo.isLFSFile
+ ctx.Data["IsLFSFile"] = fInfo.isLFSFile()
- if fInfo.isLFSFile {
+ if fInfo.isLFSFile() {
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.Name()))
ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.Link(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64))
}
- if !fInfo.isTextFile {
+ if !fInfo.st.IsText() {
return
}
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
// Pretend that this is a normal text file to display 'This file is too large to be shown'
ctx.Data["IsFileTooLarge"] = true
- ctx.Data["IsTextFile"] = true
return
}
@@ -190,10 +196,10 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
- CurrentTreePath: path.Join(ctx.Repo.TreePath, subfolder),
+ CurrentTreePath: path.Dir(readmeFullPath),
}).
WithMarkupType(markupType).
- WithRelativePath(path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())) // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
+ WithRelativePath(readmeFullPath)
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
if err != nil {
@@ -212,7 +218,7 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
}
- if !fInfo.isLFSFile && ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
+ if !fInfo.isLFSFile() && ctx.Repo.Repository.CanEnableEditor() {
ctx.Data["CanEditReadmeFile"] = true
}
}
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index d70760bc36..69858c9692 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -7,6 +7,7 @@ package repo
import (
"bytes"
gocontext "context"
+ "html/template"
"io"
"net/http"
"net/url"
@@ -61,9 +62,9 @@ func MustEnableWiki(ctx *context.Context) {
return
}
- unit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalWiki)
+ repoUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalWiki)
if err == nil {
- ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
+ ctx.Redirect(repoUnit.ExternalWikiConfig().ExternalWikiURL)
return
}
}
@@ -95,7 +96,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
}
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
- wikiGitRepo, errGitRepo := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
+ wikiGitRepo, errGitRepo := gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository.WikiStorageRepo())
if errGitRepo != nil {
ctx.ServerError("OpenRepository", errGitRepo)
return nil, nil, errGitRepo
@@ -109,7 +110,7 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err
return wikiGitRepo, nil, errBranch
}
// update the default branch in the database
- errDb := repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, DefaultWikiBranch: gitRepoDefaultBranch}, "default_wiki_branch")
+ errDb := repo_model.UpdateRepositoryColsNoAutoTime(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, DefaultWikiBranch: gitRepoDefaultBranch}, "default_wiki_branch")
if errDb != nil {
return wikiGitRepo, nil, errDb
}
@@ -178,23 +179,17 @@ func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_
}
func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
- wikiRepo, commit, err := findWikiRepoCommit(ctx)
+ wikiGitRepo, commit, err := findWikiRepoCommit(ctx)
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
if !git.IsErrNotExist(err) {
ctx.ServerError("GetBranchCommit", err)
}
return nil, nil
}
- // Get page list.
+ // get the wiki pages list.
entries, err := commit.ListEntries()
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
ctx.ServerError("ListEntries", err)
return nil, nil
}
@@ -208,9 +203,6 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
if repo_model.IsErrWikiInvalidFileName(err) {
continue
}
- if wikiRepo != nil {
- wikiRepo.Close()
- }
ctx.ServerError("WikiFilenameToName", err)
return nil, nil
} else if wikiName == "_Sidebar" || wikiName == "_Footer" {
@@ -249,58 +241,26 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
ctx.Redirect(util.URLJoin(ctx.Repo.RepoLink, "wiki/raw", string(pageName)))
}
if entry == nil || ctx.Written() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
return nil, nil
}
- // get filecontent
+ // get page content
data := wikiContentsByEntry(ctx, entry)
if ctx.Written() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
return nil, nil
}
- var sidebarContent []byte
- if !isSideBar {
- sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar")
- if ctx.Written() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
- return nil, nil
- }
- } else {
- sidebarContent = data
- }
-
- var footerContent []byte
- if !isFooter {
- footerContent, _, _, _ = wikiContentsByName(ctx, commit, "_Footer")
- if ctx.Written() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
- return nil, nil
- }
- } else {
- footerContent = data
- }
-
rctx := renderhelper.NewRenderContextRepoWiki(ctx, ctx.Repo.Repository)
- buf := &strings.Builder{}
- renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
+ renderFn := func(data []byte) (escaped *charset.EscapeStatus, output template.HTML, err error) {
+ buf := &strings.Builder{}
markupRd, markupWr := io.Pipe()
defer markupWr.Close()
done := make(chan struct{})
go func() {
// We allow NBSP here this is rendered
escaped, _ = charset.EscapeControlReader(markupRd, buf, ctx.Locale, charset.RuneNBSP)
- output = buf.String()
+ output = template.HTML(buf.String())
buf.Reset()
close(done)
}()
@@ -311,75 +271,61 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
return escaped, output, err
}
- ctx.Data["EscapeStatus"], ctx.Data["content"], err = renderFn(data)
+ ctx.Data["EscapeStatus"], ctx.Data["WikiContentHTML"], err = renderFn(data)
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
ctx.ServerError("Render", err)
return nil, nil
}
if rctx.SidebarTocNode != nil {
- sb := &strings.Builder{}
- err = markdown.SpecializedMarkdown(rctx).Renderer().Render(sb, nil, rctx.SidebarTocNode)
- if err != nil {
+ sb := strings.Builder{}
+ if err = markdown.SpecializedMarkdown(rctx).Renderer().Render(&sb, nil, rctx.SidebarTocNode); err != nil {
log.Error("Failed to render wiki sidebar TOC: %v", err)
- } else {
- ctx.Data["sidebarTocContent"] = sb.String()
}
+ ctx.Data["WikiSidebarTocHTML"] = templates.SanitizeHTML(sb.String())
}
if !isSideBar {
- buf.Reset()
- ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"], err = renderFn(sidebarContent)
+ sidebarContent, _, _, _ := wikiContentsByName(ctx, commit, "_Sidebar")
+ if ctx.Written() {
+ return nil, nil
+ }
+ ctx.Data["WikiSidebarEscapeStatus"], ctx.Data["WikiSidebarHTML"], err = renderFn(sidebarContent)
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
ctx.ServerError("Render", err)
return nil, nil
}
- ctx.Data["sidebarPresent"] = sidebarContent != nil
- } else {
- ctx.Data["sidebarPresent"] = false
}
if !isFooter {
- buf.Reset()
- ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"], err = renderFn(footerContent)
+ footerContent, _, _, _ := wikiContentsByName(ctx, commit, "_Footer")
+ if ctx.Written() {
+ return nil, nil
+ }
+ ctx.Data["WikiFooterEscapeStatus"], ctx.Data["WikiFooterHTML"], err = renderFn(footerContent)
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
ctx.ServerError("Render", err)
return nil, nil
}
- ctx.Data["footerPresent"] = footerContent != nil
- } else {
- ctx.Data["footerPresent"] = false
}
// get commit count - wiki revisions
- commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
+ commitsCount, _ := wikiGitRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
ctx.Data["CommitCount"] = commitsCount
- return wikiRepo, entry
+ return wikiGitRepo, entry
}
func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
- wikiRepo, commit, err := findWikiRepoCommit(ctx)
+ wikiGitRepo, commit, err := findWikiRepoCommit(ctx)
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
if !git.IsErrNotExist(err) {
ctx.ServerError("GetBranchCommit", err)
}
return nil, nil
}
- // get requested pagename
+ // get requested page name
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 {
pageName = "Home"
@@ -394,53 +340,35 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
- // lookup filename in wiki - get filecontent, gitTree entry , real filename
- data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
+ // lookup filename in wiki - get page content, gitTree entry , real filename
+ _, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
if noEntry {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
}
if entry == nil || ctx.Written() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
return nil, nil
}
- ctx.Data["content"] = string(data)
- ctx.Data["sidebarPresent"] = false
- ctx.Data["sidebarContent"] = ""
- ctx.Data["footerPresent"] = false
- ctx.Data["footerContent"] = ""
-
// get commit count - wiki revisions
- commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
+ commitsCount, _ := wikiGitRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
ctx.Data["CommitCount"] = commitsCount
// get page
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
// get Commit Count
- commitsHistory, err := wikiRepo.CommitsByFileAndRange(
+ commitsHistory, err := wikiGitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.Repository.DefaultWikiBranch,
File: pageFilename,
Page: page,
})
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
ctx.ServerError("CommitsByFileAndRange", err)
return nil, nil
}
ctx.Data["Commits"], err = git_service.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository)
if err != nil {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
ctx.ServerError("ConvertFromGitCommit", err)
return nil, nil
}
@@ -449,16 +377,11 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
- return wikiRepo, entry
+ return wikiGitRepo, entry
}
func renderEditPage(ctx *context.Context) {
- wikiRepo, commit, err := findWikiRepoCommit(ctx)
- defer func() {
- if wikiRepo != nil {
- _ = wikiRepo.Close()
- }
- }()
+ _, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if !git.IsErrNotExist(err) {
ctx.ServerError("GetBranchCommit", err)
@@ -466,7 +389,7 @@ func renderEditPage(ctx *context.Context) {
return
}
- // get requested pagename
+ // get requested page name
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
if len(pageName) == 0 {
pageName = "Home"
@@ -490,17 +413,13 @@ func renderEditPage(ctx *context.Context) {
return
}
- // get filecontent
+ // get wiki page content
data := wikiContentsByEntry(ctx, entry)
if ctx.Written() {
return
}
- ctx.Data["content"] = string(data)
- ctx.Data["sidebarPresent"] = false
- ctx.Data["sidebarContent"] = ""
- ctx.Data["footerPresent"] = false
- ctx.Data["footerContent"] = ""
+ ctx.Data["WikiEditContent"] = string(data)
}
// WikiPost renders post of wiki page
@@ -562,12 +481,7 @@ func Wiki(ctx *context.Context) {
return
}
- wikiRepo, entry := renderViewPage(ctx)
- defer func() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
- }()
+ wikiGitRepo, entry := renderViewPage(ctx)
if ctx.Written() {
return
}
@@ -583,7 +497,7 @@ func Wiki(ctx *context.Context) {
ctx.Data["FormatWarning"] = ext + " rendering is not supported at the moment. Rendered as Markdown."
}
// Get last change information.
- lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
+ lastCommit, err := wikiGitRepo.GetCommitByPath(wikiPath)
if err != nil {
ctx.ServerError("GetCommitByPath", err)
return
@@ -603,13 +517,7 @@ func WikiRevision(ctx *context.Context) {
return
}
- wikiRepo, entry := renderRevisionPage(ctx)
- defer func() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
- }()
-
+ wikiGitRepo, entry := renderRevisionPage(ctx)
if ctx.Written() {
return
}
@@ -621,7 +529,7 @@ func WikiRevision(ctx *context.Context) {
// Get last change information.
wikiPath := entry.Name()
- lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
+ lastCommit, err := wikiGitRepo.GetCommitByPath(wikiPath)
if err != nil {
ctx.ServerError("GetCommitByPath", err)
return
@@ -641,12 +549,7 @@ func WikiPages(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
- wikiRepo, commit, err := findWikiRepoCommit(ctx)
- defer func() {
- if wikiRepo != nil {
- _ = wikiRepo.Close()
- }
- }()
+ _, commit, err := findWikiRepoCommit(ctx)
if err != nil {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
return
@@ -700,13 +603,7 @@ func WikiPages(ctx *context.Context) {
// WikiRaw outputs raw blob requested by user (image for example)
func WikiRaw(ctx *context.Context) {
- wikiRepo, commit, err := findWikiRepoCommit(ctx)
- defer func() {
- if wikiRepo != nil {
- wikiRepo.Close()
- }
- }()
-
+ _, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(nil)
diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go
index b5dfa9f856..59bf6ed79b 100644
--- a/routers/web/repo/wiki_test.go
+++ b/routers/web/repo/wiki_test.go
@@ -164,7 +164,7 @@ func TestEditWiki(t *testing.T) {
EditWiki(ctx)
assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus())
assert.EqualValues(t, "Home", ctx.Data["Title"])
- assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["content"])
+ assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["WikiEditContent"])
ctx, _ = contexttest.MockContext(t, "user2/repo1/wiki/jpeg.jpg?action=_edit")
ctx.SetPathParam("*", "jpeg.jpg")
@@ -245,7 +245,12 @@ func TestDefaultWikiBranch(t *testing.T) {
assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(db.DefaultContext, repoWithNoWiki, "main"))
// repo with wiki
- assert.NoError(t, repo_model.UpdateRepositoryCols(db.DefaultContext, &repo_model.Repository{ID: 1, DefaultWikiBranch: "wrong-branch"}))
+ assert.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(
+ db.DefaultContext,
+ &repo_model.Repository{ID: 1, DefaultWikiBranch: "wrong-branch"},
+ "default_wiki_branch",
+ ),
+ )
ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki")
ctx.SetPathParam("*", "Home")
diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go
index a642cfd66d..648f8046a4 100644
--- a/routers/web/shared/actions/runners.go
+++ b/routers/web/shared/actions/runners.go
@@ -108,10 +108,7 @@ func Runners(ctx *context.Context) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
opts := actions_model.FindRunnerOptions{
ListOptions: db.ListOptions{
@@ -179,10 +176,7 @@ func RunnersEdit(ctx *context.Context) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
runnerID := ctx.PathParamInt64("runnerid")
ownerID := rCtx.OwnerID
diff --git a/routers/web/shared/label/label.go b/routers/web/shared/label/label.go
new file mode 100644
index 0000000000..6968a318c4
--- /dev/null
+++ b/routers/web/shared/label/label.go
@@ -0,0 +1,26 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package label
+
+import (
+ "code.gitea.io/gitea/modules/label"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/forms"
+)
+
+func GetLabelEditForm(ctx *context.Context) *forms.CreateLabelForm {
+ form := web.GetForm(ctx).(*forms.CreateLabelForm)
+ if ctx.HasError() {
+ ctx.JSONError(ctx.Data["ErrorMsg"].(string))
+ return nil
+ }
+ var err error
+ form.Color, err = label.NormalizeColor(form.Color)
+ if err != nil {
+ ctx.JSONError(ctx.Tr("repo.issues.label_color_invalid"))
+ return nil
+ }
+ return form
+}
diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go
index 3d1795b42c..a18dedf89c 100644
--- a/routers/web/shared/packages/packages.go
+++ b/routers/web/shared/packages/packages.go
@@ -8,7 +8,6 @@ import (
"net/http"
"time"
- "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
@@ -159,12 +158,18 @@ func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) {
PackageID: p.ID,
IsInternal: optional.Some(false),
Sort: packages_model.SortCreatedDesc,
- Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
})
if err != nil {
ctx.ServerError("SearchVersions", err)
return
}
+ if pcr.KeepCount > 0 {
+ if pcr.KeepCount < len(pvs) {
+ pvs = pvs[pcr.KeepCount:]
+ } else {
+ pvs = nil
+ }
+ }
for _, pv := range pvs {
if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil {
ctx.ServerError("ShouldBeSkipped", err)
@@ -177,7 +182,6 @@ func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) {
if pcr.MatchFullName {
toMatch = p.LowerName + "/" + pv.LowerVersion
}
-
if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) {
continue
}
diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go
index c8b80ebb26..29f4e9520d 100644
--- a/routers/web/shared/secrets/secrets.go
+++ b/routers/web/shared/secrets/secrets.go
@@ -32,11 +32,11 @@ func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL
s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data), form.Description)
if err != nil {
log.Error("CreateOrUpdateSecret failed: %v", err)
- ctx.JSONError(ctx.Tr("secrets.creation.failed"))
+ ctx.JSONError(ctx.Tr("secrets.save_failed"))
return
}
- ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name))
+ ctx.Flash.Success(ctx.Tr("secrets.save_success", s.Name))
ctx.JSONRedirect(redirectURL)
}
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
index 48a5d58ea4..2bd0abc4c0 100644
--- a/routers/web/shared/user/header.go
+++ b/routers/web/shared/user/header.go
@@ -47,13 +47,12 @@ func prepareContextForProfileBigAvatar(ctx *context.Context) {
ctx.Data["RenderedDescription"] = content
}
- showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
- UserID: ctx.ContextUser.ID,
- IncludePrivate: showPrivate,
+ UserID: ctx.ContextUser.ID,
+ IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
ListOptions: db.ListOptions{
Page: 1,
- // query one more results (without a separate counting) to see whether we need to add the "show more orgs" link
+ // query one more result (without a separate counting) to see whether we need to add the "show more orgs" link
PageSize: setting.UI.User.OrgPagingNum + 1,
},
})
@@ -165,7 +164,7 @@ func RenderUserOrgHeader(ctx *context.Context) (result *PrepareOwnerHeaderResult
}
func loadHeaderCount(ctx *context.Context) error {
- repoCount, err := repo_model.CountRepository(ctx, &repo_model.SearchRepoOptions{
+ repoCount, err := repo_model.CountRepository(ctx, repo_model.SearchRepoOptions{
Actor: ctx.Doer,
OwnerID: ctx.ContextUser.ID,
Private: ctx.IsSigned,
diff --git a/routers/web/user/code.go b/routers/web/user/code.go
index f2153c6d54..11579c40a6 100644
--- a/routers/web/user/code.go
+++ b/routers/web/user/code.go
@@ -5,6 +5,7 @@ package user
import (
"net/http"
+ "slices"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
@@ -86,14 +87,7 @@ func CodeSearch(ctx *context.Context) {
loadRepoIDs := make([]int64, 0, len(searchResults))
for _, result := range searchResults {
- var find bool
- for _, id := range loadRepoIDs {
- if id == result.RepoID {
- find = true
- break
- }
- }
- if !find {
+ if !slices.Contains(loadRepoIDs, result.RepoID) {
loadRepoIDs = append(loadRepoIDs, result.RepoID)
}
}
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 77f9cb8cca..b53a3daedb 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -176,7 +176,7 @@ func Milestones(ctx *context.Context) {
}
var (
- userRepoCond = repo_model.SearchRepositoryCondition(&repoOpts) // all repo condition user could visit
+ userRepoCond = repo_model.SearchRepositoryCondition(repoOpts) // all repo condition user could visit
repoCond = userRepoCond
repoIDs []int64
@@ -197,7 +197,7 @@ func Milestones(ctx *context.Context) {
reposQuery = reposQuery[1 : len(reposQuery)-1]
// for each ID (delimiter ",") add to int to repoIDs
- for _, rID := range strings.Split(reposQuery, ",") {
+ for rID := range strings.SplitSeq(reposQuery, ",") {
// Ensure nonempty string entries
if rID != "" && rID != "0" {
rIDint64, err := strconv.ParseInt(rID, 10, 64)
@@ -242,7 +242,7 @@ func Milestones(ctx *context.Context) {
return
}
- showRepos, _, err := repo_model.SearchRepositoryByCondition(ctx, &repoOpts, userRepoCond, false)
+ showRepos, _, err := repo_model.SearchRepositoryByCondition(ctx, repoOpts, userRepoCond, false)
if err != nil {
ctx.ServerError("SearchRepositoryByCondition", err)
return
@@ -461,7 +461,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// As team:
// - Team org's owns the repository.
// - Team has read permission to repository.
- repoOpts := &repo_model.SearchRepoOptions{
+ repoOpts := repo_model.SearchRepoOptions{
Actor: ctx.Doer,
OwnerID: ctxUser.ID,
Private: true,
@@ -520,10 +520,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
opts.IsClosed = optional.Some(isShowClosed)
// Make sure page number is at least 1. Will be posted to ctx.Data.
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
opts.Paginator = &db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go
index 2a8a8812e3..68ad79b11e 100644
--- a/routers/web/user/home_test.go
+++ b/routers/web/user/home_test.go
@@ -28,7 +28,7 @@ func TestArchivedIssues(t *testing.T) {
ctx.Req.Form.Set("state", "open")
// Assume: User 30 has access to two Repos with Issues, one of the Repos being archived.
- repos, _, _ := repo_model.GetUserRepositories(db.DefaultContext, &repo_model.SearchRepoOptions{Actor: ctx.Doer})
+ repos, _, _ := repo_model.GetUserRepositories(db.DefaultContext, repo_model.SearchRepoOptions{Actor: ctx.Doer})
assert.Len(t, repos, 3)
IsArchived := make(map[int64]bool)
NumIssues := make(map[int64]int)
diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go
index 89f3c6956f..610a9b8076 100644
--- a/routers/web/user/notification.go
+++ b/routers/web/user/notification.go
@@ -203,10 +203,7 @@ func NotificationPurgePost(ctx *context.Context) {
// NotificationSubscriptions returns the list of subscribed issues
func NotificationSubscriptions(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page < 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
sortType := ctx.FormString("sort")
ctx.Data["SortType"] = sortType
@@ -287,16 +284,8 @@ func NotificationSubscriptions(ctx *context.Context) {
ctx.Data["CommitLastStatus"] = lastStatus
ctx.Data["CommitStatuses"] = commitStatuses
ctx.Data["Issues"] = issues
-
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, "")
- commitStatus, err := pull_service.GetIssuesLastCommitStatus(ctx, issues)
- if err != nil {
- ctx.ServerError("GetIssuesLastCommitStatus", err)
- return
- }
- ctx.Data["CommitStatus"] = commitStatus
-
approvalCounts, err := issues.GetApprovalCounts(ctx)
if err != nil {
ctx.ServerError("ApprovalCounts", err)
@@ -339,10 +328,7 @@ func NotificationSubscriptions(ctx *context.Context) {
// NotificationWatching returns the list of watching repos
func NotificationWatching(ctx *context.Context) {
- page := ctx.FormInt("page")
- if page < 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
keyword := ctx.FormTrim("q")
ctx.Data["Keyword"] = keyword
@@ -390,7 +376,7 @@ func NotificationWatching(ctx *context.Context) {
private := ctx.FormOptionalBool("private")
ctx.Data["IsPrivate"] = private
- repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
Page: page,
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index e96f5a04ea..216acdf927 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -4,6 +4,8 @@
package user
import (
+ gocontext "context"
+ "errors"
"net/http"
"net/url"
@@ -20,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/optional"
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
arch_module "code.gitea.io/gitea/modules/packages/arch"
+ container_module "code.gitea.io/gitea/modules/packages/container"
debian_module "code.gitea.io/gitea/modules/packages/debian"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/setting"
@@ -31,6 +34,7 @@ import (
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
packages_service "code.gitea.io/gitea/services/packages"
+ container_service "code.gitea.io/gitea/services/packages/container"
)
const (
@@ -46,10 +50,7 @@ func ListPackages(ctx *context.Context) {
ctx.ServerError("RenderUserOrgHeader", err)
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
query := ctx.FormTrim("q")
packageType := ctx.FormTrim("type")
@@ -165,6 +166,24 @@ func RedirectToLastVersion(ctx *context.Context) {
ctx.Redirect(pd.VersionWebLink())
}
+func viewPackageContainerImage(ctx gocontext.Context, pd *packages_model.PackageDescriptor, digest string) (*container_module.Metadata, error) {
+ manifestBlob, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
+ OwnerID: pd.Owner.ID,
+ Image: pd.Package.LowerName,
+ Digest: digest,
+ })
+ if err != nil {
+ return nil, err
+ }
+ manifestReader, err := packages_service.OpenBlobStream(manifestBlob.Blob)
+ if err != nil {
+ return nil, err
+ }
+ defer manifestReader.Close()
+ _, _, metadata, err := container_service.ParseManifestMetadata(ctx, manifestReader, pd.Owner.ID, pd.Package.LowerName)
+ return metadata, err
+}
+
// ViewPackageVersion displays a single package version
func ViewPackageVersion(ctx *context.Context) {
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
@@ -172,6 +191,7 @@ func ViewPackageVersion(ctx *context.Context) {
return
}
+ versionSub := ctx.PathParam("version_sub")
pd := ctx.Package.Descriptor
ctx.Data["Title"] = pd.Package.Name
ctx.Data["IsPackagesPage"] = true
@@ -260,21 +280,30 @@ func ViewPackageVersion(ctx *context.Context) {
ctx.Data["Groups"] = util.Sorted(groups.Values())
ctx.Data["Architectures"] = util.Sorted(architectures.Values())
- }
-
- var (
- total int64
- pvs []*packages_model.PackageVersion
- )
- switch pd.Package.Type {
case packages_model.TypeContainer:
- pvs, total, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{
+ imageMetadata := pd.Metadata
+ if versionSub != "" {
+ imageMetadata, err = viewPackageContainerImage(ctx, pd, versionSub)
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.NotFound(nil)
+ return
+ } else if err != nil {
+ ctx.ServerError("viewPackageContainerImage", err)
+ return
+ }
+ }
+ ctx.Data["ContainerImageMetadata"] = imageMetadata
+ }
+ var pvs []*packages_model.PackageVersion
+ var pvsTotal int64
+ if pd.Package.Type == packages_model.TypeContainer {
+ pvs, pvsTotal, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{
Paginator: db.NewAbsoluteListOptions(0, 5),
PackageID: pd.Package.ID,
IsTagged: true,
})
- default:
- pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
+ } else {
+ pvs, pvsTotal, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
Paginator: db.NewAbsoluteListOptions(0, 5),
PackageID: pd.Package.ID,
IsInternal: optional.Some(false),
@@ -284,9 +313,8 @@ func ViewPackageVersion(ctx *context.Context) {
ctx.ServerError("", err)
return
}
-
ctx.Data["LatestVersions"] = pvs
- ctx.Data["TotalVersionCount"] = total
+ ctx.Data["TotalVersionCount"] = pvsTotal
ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin()
@@ -320,10 +348,7 @@ func ListPackageVersions(ctx *context.Context) {
return
}
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
+ page := max(ctx.FormInt("page"), 1)
pagination := &db.ListOptions{
PageSize: setting.UI.PackagesPagingNum,
Page: page,
@@ -407,7 +432,7 @@ func PackageSettings(ctx *context.Context) {
ctx.Data["IsPackagesPage"] = true
ctx.Data["PackageDescriptor"] = pd
- repos, _, _ := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
+ repos, _, _ := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: pd.Owner,
Private: true,
})
@@ -488,9 +513,9 @@ func DownloadPackageFile(ctx *context.Context) {
return
}
- s, u, _, err := packages_service.GetPackageFileStream(ctx, pf)
+ s, u, _, err := packages_service.OpenFileForDownload(ctx, pf)
if err != nil {
- ctx.ServerError("GetPackageFileStream", err)
+ ctx.ServerError("OpenFileForDownload", err)
return
}
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index ee19665109..d7052914b6 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -63,21 +63,9 @@ func userProfile(ctx *context.Context) {
ctx.Data["Title"] = ctx.ContextUser.DisplayName()
ctx.Data["PageIsUserProfile"] = true
- // prepare heatmap data
- if setting.Service.EnableUserHeatmap {
- data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserHeatmapDataByUser", err)
- return
- }
- ctx.Data["HeatmapData"] = data
- ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
- }
-
profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer)
- showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
- prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob)
+ prepareUserProfileTabData(ctx, profileDbRepo, profileReadmeBlob)
// prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing
// because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them
@@ -90,7 +78,7 @@ func userProfile(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplProfile)
}
-func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
+func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
// if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page
// if there is not a profile readme, the overview tab should be treated as the repositories tab
tab := ctx.FormString("tab")
@@ -173,8 +161,20 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
ctx.Data["Cards"] = following
total = int(numFollowing)
case "activity":
+ // prepare heatmap data
+ if setting.Service.EnableUserHeatmap {
+ data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserHeatmapDataByUser", err)
+ return
+ }
+ ctx.Data["HeatmapData"] = data
+ ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
+ }
+
date := ctx.FormString("date")
pagingNum = setting.UI.FeedPagingNum
+ showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
items, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{
RequestedUser: ctx.ContextUser,
Actor: ctx.Doer,
@@ -197,7 +197,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
total = int(count)
case "stars":
ctx.Data["PageIsProfileStarList"] = true
- repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ ctx.Data["ShowRepoOwnerOnList"] = true
+ repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: pagingNum,
Page: page,
@@ -224,7 +225,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
total = int(count)
case "watching":
- repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: pagingNum,
Page: page,
@@ -265,8 +266,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
}
case "organizations":
orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{
- UserID: ctx.ContextUser.ID,
- IncludePrivate: showPrivate,
+ UserID: ctx.ContextUser.ID,
+ IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
ListOptions: db.ListOptions{
Page: page,
PageSize: pagingNum,
@@ -279,7 +280,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
ctx.Data["Cards"] = orgs
total = int(count)
default: // default to "repositories"
- repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: pagingNum,
Page: page,
diff --git a/routers/web/user/search.go b/routers/web/user/search.go
index be5eee90a9..9acb9694d7 100644
--- a/routers/web/user/search.go
+++ b/routers/web/user/search.go
@@ -16,7 +16,7 @@ import (
// SearchCandidates searches candidate users for dropdown list
func SearchCandidates(ctx *context.Context) {
- users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ users, _, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
Type: user_model.UserTypeIndividual,
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index 7577036a55..98995cd69c 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/typesniffer"
@@ -206,8 +207,8 @@ func Organization(ctx *context.Context) {
PageSize: setting.UI.Admin.UserPagingNum,
Page: ctx.FormInt("page"),
},
- UserID: ctx.Doer.ID,
- IncludePrivate: ctx.IsSigned,
+ UserID: ctx.Doer.ID,
+ IncludeVisibility: structs.VisibleTypePrivate,
}
if opts.Page <= 0 {
@@ -284,7 +285,7 @@ func Repos(ctx *context.Context) {
return
}
- userRepos, _, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
+ userRepos, _, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: ctxUser,
Private: true,
ListOptions: db.ListOptions{
@@ -309,7 +310,7 @@ func Repos(ctx *context.Context) {
ctx.Data["Dirs"] = repoNames
ctx.Data["ReposMap"] = repos
} else {
- repos, count64, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts})
+ repos, count64, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts})
if err != nil {
ctx.ServerError("GetUserRepositories", err)
return
diff --git a/routers/web/web.go b/routers/web/web.go
index bd850baec0..66c3a2da09 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -281,7 +281,7 @@ func Routes() *web.Router {
}
mid = append(mid, goGet)
- mid = append(mid, common.PageTmplFunctions)
+ mid = append(mid, common.PageGlobalData)
webRoutes := web.NewRouter()
webRoutes.Use(mid...)
@@ -964,7 +964,8 @@ func registerWebRoutes(m *web.Router) {
addSettingsVariablesRoutes()
}, actions.MustEnableActions)
- m.Methods("GET,POST", "/delete", org.SettingsDelete)
+ m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRenamePost)
+ m.Post("/delete", org.SettingsDeleteOrgPost)
m.Group("/packages", func() {
m.Get("", org.Packages)
@@ -1012,6 +1013,7 @@ func registerWebRoutes(m *web.Router) {
m.Get("/versions", user.ListPackageVersions)
m.Group("/{version}", func() {
m.Get("", user.ViewPackageVersion)
+ m.Get("/{version_sub}", user.ViewPackageVersion)
m.Get("/files/{fileid}", user.DownloadPackageFile)
m.Group("/settings", func() {
m.Get("", user.PackageSettings)
@@ -1029,7 +1031,7 @@ func registerWebRoutes(m *web.Router) {
m.Get("", org.Projects)
m.Get("/{id}", org.ViewProject)
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
- m.Group("", func() { //nolint:dupl
+ m.Group("", func() { //nolint:dupl // duplicates lines 1421-1441
m.Get("/new", org.RenderNewProject)
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
m.Group("/{id}", func() {
@@ -1251,7 +1253,8 @@ func registerWebRoutes(m *web.Router) {
m.Post("/add", web.Bind(forms.AddTimeManuallyForm{}), repo.AddTimeManually)
m.Post("/{timeid}/delete", repo.DeleteTime)
m.Group("/stopwatch", func() {
- m.Post("/toggle", repo.IssueStopwatch)
+ m.Post("/start", repo.IssueStartStopwatch)
+ m.Post("/stop", repo.IssueStopStopwatch)
m.Post("/cancel", repo.CancelStopwatch)
})
})
@@ -1311,26 +1314,38 @@ func registerWebRoutes(m *web.Router) {
}, reqSignIn, context.RepoAssignment, context.RepoMustNotBeArchived())
// end "/{username}/{reponame}": create or edit issues, pulls, labels, milestones
- m.Group("/{username}/{reponame}", func() { // repo code
+ m.Group("/{username}/{reponame}", func() { // repo code (at least "code reader")
m.Group("", func() {
m.Group("", func() {
- m.Post("/_preview/*", web.Bind(forms.EditPreviewDiffForm{}), repo.DiffPreviewPost)
- m.Combo("/_edit/*").Get(repo.EditFile).
- Post(web.Bind(forms.EditRepoFileForm{}), repo.EditFilePost)
- m.Combo("/_new/*").Get(repo.NewFile).
- Post(web.Bind(forms.EditRepoFileForm{}), repo.NewFilePost)
- m.Combo("/_delete/*").Get(repo.DeleteFile).
- Post(web.Bind(forms.DeleteRepoFileForm{}), repo.DeleteFilePost)
- m.Combo("/_upload/*", repo.MustBeAbleToUpload).Get(repo.UploadFile).
- Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost)
- m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
- Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
- m.Combo("/_cherrypick/{sha:([a-f0-9]{7,64})}/*").Get(repo.CherryPick).
- Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost)
- }, context.RepoRefByType(git.RefTypeBranch), context.CanWriteToBranch(), repo.WebGitOperationCommonData)
+ // "GET" requests only need "code reader" permission, "POST" requests need "code writer" permission.
+ // Because reader can "fork and edit"
+ canWriteToBranch := context.CanWriteToBranch()
+ m.Post("/_preview/*", repo.DiffPreviewPost) // read-only, fine with "code reader"
+ m.Post("/_fork/*", repo.ForkToEditPost) // read-only, fork to own repo, fine with "code reader"
+
+ // the path params are used in PrepareCommitFormOptions to construct the correct form action URL
+ m.Combo("/{editor_action:_edit}/*").
+ Get(repo.EditFile).
+ Post(web.Bind(forms.EditRepoFileForm{}), canWriteToBranch, repo.EditFilePost)
+ m.Combo("/{editor_action:_new}/*").
+ Get(repo.EditFile).
+ Post(web.Bind(forms.EditRepoFileForm{}), canWriteToBranch, repo.EditFilePost)
+ m.Combo("/{editor_action:_delete}/*").
+ Get(repo.DeleteFile).
+ Post(web.Bind(forms.DeleteRepoFileForm{}), canWriteToBranch, repo.DeleteFilePost)
+ m.Combo("/{editor_action:_upload}/*", repo.MustBeAbleToUpload).
+ Get(repo.UploadFile).
+ Post(web.Bind(forms.UploadRepoFileForm{}), canWriteToBranch, repo.UploadFilePost)
+ m.Combo("/{editor_action:_diffpatch}/*").
+ Get(repo.NewDiffPatch).
+ Post(web.Bind(forms.EditRepoFileForm{}), canWriteToBranch, repo.NewDiffPatchPost)
+ m.Combo("/{editor_action:_cherrypick}/{sha:([a-f0-9]{7,64})}/*").
+ Get(repo.CherryPick).
+ Post(web.Bind(forms.CherryPickForm{}), canWriteToBranch, repo.CherryPickPost)
+ }, context.RepoRefByType(git.RefTypeBranch), repo.WebGitOperationCommonData)
m.Group("", func() {
m.Post("/upload-file", repo.UploadFileToServer)
- m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
+ m.Post("/upload-remove", repo.RemoveUploadFileFromServer)
}, repo.MustBeAbleToUpload, reqRepoCodeWriter)
}, repo.MustBeEditable, context.RepoMustNotBeArchived())
@@ -1403,7 +1418,7 @@ func registerWebRoutes(m *web.Router) {
m.Group("/{username}/{reponame}/projects", func() {
m.Get("", repo.Projects)
m.Get("/{id}", repo.ViewProject)
- m.Group("", func() { //nolint:dupl
+ m.Group("", func() { //nolint:dupl // duplicates lines 1034-1054
m.Get("/new", repo.RenderNewProject)
m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost)
m.Group("/{id}", func() {
@@ -1445,8 +1460,10 @@ func registerWebRoutes(m *web.Router) {
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
m.Get("/logs", actions.Logs)
})
+ m.Get("/workflow", actions.ViewWorkflowFile)
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
+ m.Post("/delete", reqRepoActionsWriter, actions.Delete)
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
m.Delete("/artifacts/{artifact_name}", reqRepoActionsWriter, actions.ArtifactsDeleteView)
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
diff --git a/services/actions/cleanup.go b/services/actions/cleanup.go
index 23d6e3a49d..d0cc63e538 100644
--- a/services/actions/cleanup.go
+++ b/services/actions/cleanup.go
@@ -5,12 +5,14 @@ package actions
import (
"context"
+ "errors"
"fmt"
"time"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
actions_module "code.gitea.io/gitea/modules/actions"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
@@ -27,7 +29,7 @@ func Cleanup(ctx context.Context) error {
}
// clean up old logs
- if err := CleanupLogs(ctx); err != nil {
+ if err := CleanupExpiredLogs(ctx); err != nil {
return fmt.Errorf("cleanup logs: %w", err)
}
@@ -98,8 +100,15 @@ func cleanNeedDeleteArtifacts(taskCtx context.Context) error {
const deleteLogBatchSize = 100
-// CleanupLogs removes logs which are older than the configured retention time
-func CleanupLogs(ctx context.Context) error {
+func removeTaskLog(ctx context.Context, task *actions_model.ActionTask) {
+ if err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename); err != nil {
+ log.Error("Failed to remove log %s (in storage %v) of task %v: %v", task.LogFilename, task.LogInStorage, task.ID, err)
+ // do not return error here, go on
+ }
+}
+
+// CleanupExpiredLogs removes logs which are older than the configured retention time
+func CleanupExpiredLogs(ctx context.Context) error {
olderThan := timeutil.TimeStampNow().AddDuration(-time.Duration(setting.Actions.LogRetentionDays) * 24 * time.Hour)
count := 0
@@ -109,10 +118,7 @@ func CleanupLogs(ctx context.Context) error {
return fmt.Errorf("find old tasks: %w", err)
}
for _, task := range tasks {
- if err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename); err != nil {
- log.Error("Failed to remove log %s (in storage %v) of task %v: %v", task.LogFilename, task.LogInStorage, task.ID, err)
- // do not return error here, go on
- }
+ removeTaskLog(ctx, task)
task.LogIndexes = nil // clear log indexes since it's a heavy field
task.LogExpired = true
if err := actions_model.UpdateTask(ctx, task, "log_indexes", "log_expired"); err != nil {
@@ -148,3 +154,107 @@ func CleanupEphemeralRunners(ctx context.Context) error {
log.Info("Removed %d runners", affected)
return nil
}
+
+// CleanupEphemeralRunnersByPickedTaskOfRepo removes all ephemeral runners that have active/finished tasks on the given repository
+func CleanupEphemeralRunnersByPickedTaskOfRepo(ctx context.Context, repoID int64) error {
+ subQuery := builder.Select("`action_runner`.id").
+ From(builder.Select("*").From("`action_runner`"), "`action_runner`"). // mysql needs this redundant subquery
+ Join("INNER", "`action_task`", "`action_task`.`runner_id` = `action_runner`.`id`").
+ Where(builder.And(builder.Eq{"`action_runner`.`ephemeral`": true}, builder.Eq{"`action_task`.`repo_id`": repoID}))
+ b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
+ res, err := db.GetEngine(ctx).Exec(b)
+ if err != nil {
+ return fmt.Errorf("find runners: %w", err)
+ }
+ affected, _ := res.RowsAffected()
+ log.Info("Removed %d runners", affected)
+ return nil
+}
+
+// DeleteRun deletes workflow run, including all logs and artifacts.
+func DeleteRun(ctx context.Context, run *actions_model.ActionRun) error {
+ if !run.Status.IsDone() {
+ return errors.New("run is not done")
+ }
+
+ repoID := run.RepoID
+
+ jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
+ if err != nil {
+ return err
+ }
+ jobIDs := container.FilterSlice(jobs, func(j *actions_model.ActionRunJob) (int64, bool) {
+ return j.ID, true
+ })
+ tasks := make(actions_model.TaskList, 0)
+ if len(jobIDs) > 0 {
+ if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).In("job_id", jobIDs).Find(&tasks); err != nil {
+ return err
+ }
+ }
+
+ artifacts, err := db.Find[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
+ RepoID: repoID,
+ RunID: run.ID,
+ })
+ if err != nil {
+ return err
+ }
+
+ var recordsToDelete []any
+
+ recordsToDelete = append(recordsToDelete, &actions_model.ActionRun{
+ RepoID: repoID,
+ ID: run.ID,
+ })
+ recordsToDelete = append(recordsToDelete, &actions_model.ActionRunJob{
+ RepoID: repoID,
+ RunID: run.ID,
+ })
+ for _, tas := range tasks {
+ recordsToDelete = append(recordsToDelete, &actions_model.ActionTask{
+ RepoID: repoID,
+ ID: tas.ID,
+ })
+ recordsToDelete = append(recordsToDelete, &actions_model.ActionTaskStep{
+ RepoID: repoID,
+ TaskID: tas.ID,
+ })
+ recordsToDelete = append(recordsToDelete, &actions_model.ActionTaskOutput{
+ TaskID: tas.ID,
+ })
+ }
+ recordsToDelete = append(recordsToDelete, &actions_model.ActionArtifact{
+ RepoID: repoID,
+ RunID: run.ID,
+ })
+
+ if err := db.WithTx(ctx, func(ctx context.Context) error {
+ // TODO: Deleting task records could break current ephemeral runner implementation. This is a temporary workaround suggested by ChristopherHX.
+ // Since you delete potentially the only task an ephemeral act_runner has ever run, please delete the affected runners first.
+ // one of
+ // call cleanup ephemeral runners first
+ // delete affected ephemeral act_runners
+ // I would make ephemeral runners fully delete directly before formally finishing the task
+ //
+ // See also: https://github.com/go-gitea/gitea/pull/34337#issuecomment-2862222788
+ if err := CleanupEphemeralRunners(ctx); err != nil {
+ return err
+ }
+ return db.DeleteBeans(ctx, recordsToDelete...)
+ }); err != nil {
+ return err
+ }
+
+ // Delete files on storage
+ for _, tas := range tasks {
+ removeTaskLog(ctx, tas)
+ }
+ for _, art := range artifacts {
+ if err := storage.ActionsArtifacts.Delete(art.StoragePath); err != nil {
+ log.Error("remove artifact file %q: %v", art.StoragePath, err)
+ }
+ }
+
+ return nil
+}
diff --git a/services/actions/clear_tasks.go b/services/actions/clear_tasks.go
index 2aeb0e8c96..274c04aa57 100644
--- a/services/actions/clear_tasks.go
+++ b/services/actions/clear_tasks.go
@@ -42,6 +42,10 @@ func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.Ac
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
+ if len(jobs) > 0 {
+ job := jobs[0]
+ notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
+ }
}
}
@@ -123,7 +127,7 @@ func CancelAbandonedJobs(ctx context.Context) error {
}
CreateCommitStatus(ctx, job)
if updated {
- _ = job.LoadAttributes(ctx)
+ NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
}
diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go
index eb15d16061..ef241e5091 100644
--- a/services/actions/commit_status.go
+++ b/services/actions/commit_status.go
@@ -14,9 +14,9 @@ import (
git_model "code.gitea.io/gitea/models/git"
user_model "code.gitea.io/gitea/models/user"
actions_module "code.gitea.io/gitea/modules/actions"
+ "code.gitea.io/gitea/modules/commitstatus"
git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
- api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
@@ -92,7 +92,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
}
ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
state := toCommitStatus(job.Status)
- if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll); err == nil {
+ if statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll); err == nil {
for _, v := range statuses {
if v.Context == ctxname {
if v.State == state {
@@ -147,16 +147,18 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
return commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &status)
}
-func toCommitStatus(status actions_model.Status) api.CommitStatusState {
+func toCommitStatus(status actions_model.Status) commitstatus.CommitStatusState {
switch status {
- case actions_model.StatusSuccess, actions_model.StatusSkipped:
- return api.CommitStatusSuccess
+ case actions_model.StatusSuccess:
+ return commitstatus.CommitStatusSuccess
case actions_model.StatusFailure, actions_model.StatusCancelled:
- return api.CommitStatusFailure
+ return commitstatus.CommitStatusFailure
case actions_model.StatusWaiting, actions_model.StatusBlocked, actions_model.StatusRunning:
- return api.CommitStatusPending
+ return commitstatus.CommitStatusPending
+ case actions_model.StatusSkipped:
+ return commitstatus.CommitStatusSkipped
default:
- return api.CommitStatusError
+ return commitstatus.CommitStatusError
}
}
diff --git a/services/actions/context.go b/services/actions/context.go
index 2667e18337..b6de429ccf 100644
--- a/services/actions/context.go
+++ b/services/actions/context.go
@@ -15,11 +15,16 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/nektos/act/pkg/model"
)
+type GiteaContext map[string]any
+
// GenerateGiteaContext generate the gitea context without token and gitea_runtime_token
// job can be nil when generating a context for parsing workflow-level expressions
-func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) map[string]any {
+func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) GiteaContext {
event := map[string]any{}
_ = json.Unmarshal([]byte(run.EventPayload), &event)
@@ -42,7 +47,7 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio
refName := git.RefName(ref)
- gitContext := map[string]any{
+ gitContext := GiteaContext{
// standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
"action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.
"action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action.
@@ -160,3 +165,37 @@ func mergeTwoOutputs(o1, o2 map[string]string) map[string]string {
}
return ret
}
+
+func (g *GiteaContext) ToGitHubContext() *model.GithubContext {
+ return &model.GithubContext{
+ Event: util.GetMapValueOrDefault(*g, "event", map[string]any(nil)),
+ EventPath: util.GetMapValueOrDefault(*g, "event_path", ""),
+ Workflow: util.GetMapValueOrDefault(*g, "workflow", ""),
+ RunID: util.GetMapValueOrDefault(*g, "run_id", ""),
+ RunNumber: util.GetMapValueOrDefault(*g, "run_number", ""),
+ Actor: util.GetMapValueOrDefault(*g, "actor", ""),
+ Repository: util.GetMapValueOrDefault(*g, "repository", ""),
+ EventName: util.GetMapValueOrDefault(*g, "event_name", ""),
+ Sha: util.GetMapValueOrDefault(*g, "sha", ""),
+ Ref: util.GetMapValueOrDefault(*g, "ref", ""),
+ RefName: util.GetMapValueOrDefault(*g, "ref_name", ""),
+ RefType: util.GetMapValueOrDefault(*g, "ref_type", ""),
+ HeadRef: util.GetMapValueOrDefault(*g, "head_ref", ""),
+ BaseRef: util.GetMapValueOrDefault(*g, "base_ref", ""),
+ Token: "", // deliberately omitted for security
+ Workspace: util.GetMapValueOrDefault(*g, "workspace", ""),
+ Action: util.GetMapValueOrDefault(*g, "action", ""),
+ ActionPath: util.GetMapValueOrDefault(*g, "action_path", ""),
+ ActionRef: util.GetMapValueOrDefault(*g, "action_ref", ""),
+ ActionRepository: util.GetMapValueOrDefault(*g, "action_repository", ""),
+ Job: util.GetMapValueOrDefault(*g, "job", ""),
+ JobName: "", // not present in GiteaContext
+ RepositoryOwner: util.GetMapValueOrDefault(*g, "repository_owner", ""),
+ RetentionDays: util.GetMapValueOrDefault(*g, "retention_days", ""),
+ RunnerPerflog: "", // not present in GiteaContext
+ RunnerTrackingID: "", // not present in GiteaContext
+ ServerURL: util.GetMapValueOrDefault(*g, "server_url", ""),
+ APIURL: util.GetMapValueOrDefault(*g, "api_url", ""),
+ GraphQLURL: util.GetMapValueOrDefault(*g, "graphql_url", ""),
+ }
+}
diff --git a/services/actions/interface.go b/services/actions/interface.go
index b407f5c6c8..a054c38e4f 100644
--- a/services/actions/interface.go
+++ b/services/actions/interface.go
@@ -33,4 +33,8 @@ type API interface {
GetRunner(*context.APIContext)
// DeleteRunner delete runner
DeleteRunner(*context.APIContext)
+ // ListWorkflowJobs list jobs
+ ListWorkflowJobs(*context.APIContext)
+ // ListWorkflowRuns list runs
+ ListWorkflowRuns(*context.APIContext)
}
diff --git a/services/actions/job_emitter.go b/services/actions/job_emitter.go
index c11bb5875f..47c9f59094 100644
--- a/services/actions/job_emitter.go
+++ b/services/actions/job_emitter.go
@@ -11,6 +11,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue"
notify_service "code.gitea.io/gitea/services/notify"
@@ -78,9 +79,30 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
+ if len(jobs) > 0 {
+ runUpdated := true
+ for _, job := range jobs {
+ if !job.Status.IsDone() {
+ runUpdated = false
+ break
+ }
+ }
+ if runUpdated {
+ NotifyWorkflowRunStatusUpdateWithReload(ctx, jobs[0])
+ }
+ }
return nil
}
+func NotifyWorkflowRunStatusUpdateWithReload(ctx context.Context, job *actions_model.ActionRunJob) {
+ job.Run = nil
+ if err := job.LoadAttributes(ctx); err != nil {
+ log.Error("LoadAttributes: %v", err)
+ return
+ }
+ notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
+}
+
type jobStatusResolver struct {
statuses map[int64]actions_model.Status
needs map[int64][]int64
diff --git a/services/actions/notifier.go b/services/actions/notifier.go
index 831cde3523..d10cc0ab34 100644
--- a/services/actions/notifier.go
+++ b/services/actions/notifier.go
@@ -6,13 +6,16 @@ package actions
import (
"context"
+ actions_model "code.gitea.io/gitea/models/actions"
issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
perm_model "code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
@@ -762,3 +765,41 @@ func (n *actionsNotifier) MigrateRepository(ctx context.Context, doer, u *user_m
Sender: convert.ToUser(ctx, doer, nil),
}).Notify(ctx)
}
+
+func (n *actionsNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
+ ctx = withMethod(ctx, "WorkflowRunStatusUpdate")
+
+ var org *api.Organization
+ if repo.Owner.IsOrganization() {
+ org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
+ }
+
+ status := convert.ToWorkflowRunAction(run.Status)
+
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo)
+ if err != nil {
+ log.Error("OpenRepository: %v", err)
+ return
+ }
+ defer gitRepo.Close()
+
+ convertedWorkflow, err := convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
+ if err != nil {
+ log.Error("GetActionWorkflow: %v", err)
+ return
+ }
+ convertedRun, err := convert.ToActionWorkflowRun(ctx, repo, run)
+ if err != nil {
+ log.Error("ToActionWorkflowRun: %v", err)
+ return
+ }
+
+ newNotifyInput(repo, sender, webhook_module.HookEventWorkflowRun).WithPayload(&api.WorkflowRunPayload{
+ Action: status,
+ Workflow: convertedWorkflow,
+ WorkflowRun: convertedRun,
+ Organization: org,
+ Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeOwner}),
+ Sender: convert.ToUser(ctx, sender, nil),
+ }).Notify(ctx)
+}
diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go
index d179134798..8010f51a86 100644
--- a/services/actions/notifier_helper.go
+++ b/services/actions/notifier_helper.go
@@ -33,7 +33,9 @@ import (
"github.com/nektos/act/pkg/model"
)
-var methodCtxKey struct{}
+type methodCtxKeyType struct{}
+
+var methodCtxKey methodCtxKeyType
// withMethod sets the notification method that this context currently executes.
// Used for debugging/ troubleshooting purposes.
@@ -44,8 +46,7 @@ func withMethod(ctx context.Context, method string) context.Context {
return ctx
}
}
- // FIXME: review the use of this nolint directive
- return context.WithValue(ctx, methodCtxKey, method) //nolint:staticcheck
+ return context.WithValue(ctx, methodCtxKey, method)
}
// getMethod gets the notification method that this context currently executes.
@@ -178,7 +179,7 @@ func notify(ctx context.Context, input *notifyInput) error {
return fmt.Errorf("gitRepo.GetCommit: %w", err)
}
- if skipWorkflows(input, commit) {
+ if skipWorkflows(ctx, input, commit) {
return nil
}
@@ -243,7 +244,7 @@ func notify(ctx context.Context, input *notifyInput) error {
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
}
-func skipWorkflows(input *notifyInput, commit *git.Commit) bool {
+func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool {
// skip workflow runs with a configured skip-ci string in commit message or pr title if the event is push or pull_request(_sync)
// https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs
skipWorkflowEvents := []webhook_module.HookEventType{
@@ -263,6 +264,27 @@ func skipWorkflows(input *notifyInput, commit *git.Commit) bool {
}
}
}
+ if input.Event == webhook_module.HookEventWorkflowRun {
+ wrun, ok := input.Payload.(*api.WorkflowRunPayload)
+ for i := 0; i < 5 && ok && wrun.WorkflowRun != nil; i++ {
+ if wrun.WorkflowRun.Event != "workflow_run" {
+ return false
+ }
+ r, err := actions_model.GetRunByRepoAndID(ctx, input.Repo.ID, wrun.WorkflowRun.ID)
+ if err != nil {
+ log.Error("GetRunByRepoAndID: %v", err)
+ return true
+ }
+ wrun, err = r.GetWorkflowRunEventPayload()
+ if err != nil {
+ log.Error("GetWorkflowRunEventPayload: %v", err)
+ return true
+ }
+ }
+ // skip workflow runs events exceeding the maxiumum of 5 recursive events
+ log.Debug("repo %s: skipped workflow_run because of recursive event of 5", input.Repo.RepoPath())
+ return true
+ }
return false
}
@@ -302,9 +324,11 @@ func handleWorkflows(
run := &actions_model.ActionRun{
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
RepoID: input.Repo.ID,
+ Repo: input.Repo,
OwnerID: input.Repo.OwnerID,
WorkflowID: dwf.EntryName,
TriggerUserID: input.Doer.ID,
+ TriggerUser: input.Doer,
Ref: ref,
CommitSHA: commit.ID.String(),
IsForkPullRequest: isForkPullRequest,
@@ -333,12 +357,18 @@ func handleWorkflows(
continue
}
- jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars))
+ giteaCtx := GenerateGiteaContext(run, nil)
+
+ jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars), jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
if err != nil {
log.Error("jobparser.Parse: %v", err)
continue
}
+ if len(jobs) > 0 && jobs[0].RunName != "" {
+ run.Title = jobs[0].RunName
+ }
+
// cancel running jobs if the event is push or pull_request_sync
if run.Event == webhook_module.HookEventPush ||
run.Event == webhook_module.HookEventPullRequestSync {
@@ -364,6 +394,15 @@ func handleWorkflows(
continue
}
CreateCommitStatus(ctx, alljobs...)
+ if len(alljobs) > 0 {
+ job := alljobs[0]
+ err := job.LoadRun(ctx)
+ if err != nil {
+ log.Error("LoadRun: %v", err)
+ continue
+ }
+ notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
+ }
for _, job := range alljobs {
notify_service.WorkflowJobStatusUpdate(ctx, input.Repo, input.Doer, job, nil)
}
@@ -508,9 +547,11 @@ func handleSchedules(
run := &actions_model.ActionSchedule{
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
RepoID: input.Repo.ID,
+ Repo: input.Repo,
OwnerID: input.Repo.OwnerID,
WorkflowID: dwf.EntryName,
TriggerUserID: user_model.ActionsUserID,
+ TriggerUser: user_model.NewActionsUser(),
Ref: ref,
CommitSHA: commit.ID.String(),
Event: input.Event,
@@ -518,6 +559,25 @@ func handleSchedules(
Specs: schedules,
Content: dwf.Content,
}
+
+ vars, err := actions_model.GetVariablesOfRun(ctx, run.ToActionRun())
+ if err != nil {
+ log.Error("GetVariablesOfRun: %v", err)
+ continue
+ }
+
+ giteaCtx := GenerateGiteaContext(run.ToActionRun(), nil)
+
+ jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars), jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
+ if err != nil {
+ log.Error("jobparser.Parse: %v", err)
+ continue
+ }
+
+ if len(jobs) > 0 && jobs[0].RunName != "" {
+ run.Title = jobs[0].RunName
+ }
+
crons = append(crons, run)
}
diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go
index a30b166063..c029c5a1a2 100644
--- a/services/actions/schedule_tasks.go
+++ b/services/actions/schedule_tasks.go
@@ -157,6 +157,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
if err != nil {
log.Error("LoadAttributes: %v", err)
}
+ notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
for _, job := range allJobs {
notify_service.WorkflowJobStatusUpdate(ctx, run.Repo, run.TriggerUser, job, nil)
}
diff --git a/services/actions/workflow.go b/services/actions/workflow.go
index dc8a1dd349..233e22b5dd 100644
--- a/services/actions/workflow.go
+++ b/services/actions/workflow.go
@@ -5,9 +5,6 @@ package actions
import (
"fmt"
- "net/http"
- "net/url"
- "path"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
@@ -31,61 +28,8 @@ import (
"github.com/nektos/act/pkg/model"
)
-func getActionWorkflowPath(commit *git.Commit) string {
- paths := []string{".gitea/workflows", ".github/workflows"}
- for _, treePath := range paths {
- if _, err := commit.SubTree(treePath); err == nil {
- return treePath
- }
- }
- return ""
-}
-
-func getActionWorkflowEntry(ctx *context.APIContext, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
- cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
- cfg := cfgUnit.ActionsConfig()
-
- defaultBranch, _ := commit.GetBranchName()
-
- workflowURL := fmt.Sprintf("%s/actions/workflows/%s", ctx.Repo.Repository.APIURL(), url.PathEscape(entry.Name()))
- workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", ctx.Repo.Repository.HTMLURL(ctx), util.PathEscapeSegments(defaultBranch), util.PathEscapeSegments(folder), url.PathEscape(entry.Name()))
- badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", ctx.Repo.Repository.HTMLURL(ctx), url.PathEscape(entry.Name()), url.QueryEscape(ctx.Repo.Repository.DefaultBranch))
-
- // See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
- // State types:
- // - active
- // - deleted
- // - disabled_fork
- // - disabled_inactivity
- // - disabled_manually
- state := "active"
- if cfg.IsWorkflowDisabled(entry.Name()) {
- state = "disabled_manually"
- }
-
- // The CreatedAt and UpdatedAt fields currently reflect the timestamp of the latest commit, which can later be refined
- // by retrieving the first and last commits for the file history. The first commit would indicate the creation date,
- // while the last commit would represent the modification date. The DeletedAt could be determined by identifying
- // the last commit where the file existed. However, this implementation has not been done here yet, as it would likely
- // cause a significant performance degradation.
- createdAt := commit.Author.When
- updatedAt := commit.Author.When
-
- return &api.ActionWorkflow{
- ID: entry.Name(),
- Name: entry.Name(),
- Path: path.Join(folder, entry.Name()),
- State: state,
- CreatedAt: createdAt,
- UpdatedAt: updatedAt,
- URL: workflowURL,
- HTMLURL: workflowRepoURL,
- BadgeURL: badgeURL,
- }
-}
-
func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnable bool) error {
- workflow, err := GetActionWorkflow(ctx, workflowID)
+ workflow, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID)
if err != nil {
return err
}
@@ -102,44 +46,6 @@ func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnabl
return repo_model.UpdateRepoUnit(ctx, cfgUnit)
}
-func ListActionWorkflows(ctx *context.APIContext) ([]*api.ActionWorkflow, error) {
- defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
- if err != nil {
- ctx.APIErrorInternal(err)
- return nil, err
- }
-
- entries, err := actions.ListWorkflows(defaultBranchCommit)
- if err != nil {
- ctx.APIError(http.StatusNotFound, err.Error())
- return nil, err
- }
-
- folder := getActionWorkflowPath(defaultBranchCommit)
-
- workflows := make([]*api.ActionWorkflow, len(entries))
- for i, entry := range entries {
- workflows[i] = getActionWorkflowEntry(ctx, defaultBranchCommit, folder, entry)
- }
-
- return workflows, nil
-}
-
-func GetActionWorkflow(ctx *context.APIContext, workflowID string) (*api.ActionWorkflow, error) {
- entries, err := ListActionWorkflows(ctx)
- if err != nil {
- return nil, err
- }
-
- for _, entry := range entries {
- if entry.Name == workflowID {
- return entry, nil
- }
- }
-
- return nil, util.NewNotExistErrorf("workflow %q not found", workflowID)
-}
-
func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) error {
if workflowID == "" {
return util.ErrorWrapLocale(
@@ -185,29 +91,62 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
}
// get workflow entry from runTargetCommit
- entries, err := actions.ListWorkflows(runTargetCommit)
+ _, entries, err := actions.ListWorkflows(runTargetCommit)
if err != nil {
return err
}
// find workflow from commit
var workflows []*jobparser.SingleWorkflow
- for _, entry := range entries {
- if entry.Name() != workflowID {
- continue
- }
+ var entry *git.TreeEntry
- content, err := actions.GetContentFromEntry(entry)
- if err != nil {
- return err
- }
- workflows, err = jobparser.Parse(content)
- if err != nil {
- return err
+ run := &actions_model.ActionRun{
+ Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0],
+ RepoID: repo.ID,
+ Repo: repo,
+ OwnerID: repo.OwnerID,
+ WorkflowID: workflowID,
+ TriggerUserID: doer.ID,
+ TriggerUser: doer,
+ Ref: string(refName),
+ CommitSHA: runTargetCommit.ID.String(),
+ IsForkPullRequest: false,
+ Event: "workflow_dispatch",
+ TriggerEvent: "workflow_dispatch",
+ Status: actions_model.StatusWaiting,
+ }
+
+ for _, e := range entries {
+ if e.Name() != workflowID {
+ continue
}
+ entry = e
break
}
+ if entry == nil {
+ return util.ErrorWrapLocale(
+ util.NewNotExistErrorf("workflow %q doesn't exist", workflowID),
+ "actions.workflow.not_found", workflowID,
+ )
+ }
+
+ content, err := actions.GetContentFromEntry(entry)
+ if err != nil {
+ return err
+ }
+
+ giteaCtx := GenerateGiteaContext(run, nil)
+
+ workflows, err = jobparser.Parse(content, jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
+ if err != nil {
+ return err
+ }
+
+ if len(workflows) > 0 && workflows[0].RunName != "" {
+ run.Title = workflows[0].RunName
+ }
+
if len(workflows) == 0 {
return util.ErrorWrapLocale(
util.NewNotExistErrorf("workflow %q doesn't exist", workflowID),
@@ -236,25 +175,12 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
Inputs: inputsWithDefaults,
Sender: convert.ToUserWithAccessMode(ctx, doer, perm.AccessModeNone),
}
+
var eventPayload []byte
if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil {
return fmt.Errorf("JSONPayload: %w", err)
}
-
- run := &actions_model.ActionRun{
- Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0],
- RepoID: repo.ID,
- OwnerID: repo.OwnerID,
- WorkflowID: workflowID,
- TriggerUserID: doer.ID,
- Ref: string(refName),
- CommitSHA: runTargetCommit.ID.String(),
- IsForkPullRequest: false,
- Event: "workflow_dispatch",
- TriggerEvent: "workflow_dispatch",
- EventPayload: string(eventPayload),
- Status: actions_model.StatusWaiting,
- }
+ run.EventPayload = string(eventPayload)
// cancel running jobs of the same workflow
if err := CancelPreviousJobs(
@@ -277,9 +203,17 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
log.Error("FindRunJobs: %v", err)
}
CreateCommitStatus(ctx, allJobs...)
+ if len(allJobs) > 0 {
+ job := allJobs[0]
+ err := job.LoadRun(ctx)
+ if err != nil {
+ log.Error("LoadRun: %v", err)
+ } else {
+ notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
+ }
+ }
for _, job := range allJobs {
notify_service.WorkflowJobStatusUpdate(ctx, repo, doer, job, nil)
}
-
return nil
}
diff --git a/services/agit/agit.go b/services/agit/agit.go
index 0fe28c5d66..b27dfc8ecd 100644
--- a/services/agit/agit.go
+++ b/services/agit/agit.go
@@ -9,6 +9,7 @@ import (
"os"
"strings"
+ git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
@@ -199,11 +200,37 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
}
}
+ // Store old commit ID for review staleness checking
+ oldHeadCommitID := pr.HeadCommitID
+
pr.HeadCommitID = opts.NewCommitIDs[i]
if err = pull_service.UpdateRef(ctx, pr); err != nil {
return nil, fmt.Errorf("failed to update pull ref. Error: %w", err)
}
+ // Mark existing reviews as stale when PR content changes (same as regular GitHub flow)
+ if oldHeadCommitID != opts.NewCommitIDs[i] {
+ if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
+ log.Error("MarkReviewsAsStale: %v", err)
+ }
+
+ // Dismiss all approval reviews if protected branch rule item enabled
+ pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
+ if err != nil {
+ log.Error("GetFirstMatchProtectedBranchRule: %v", err)
+ }
+ if pb != nil && pb.DismissStaleApprovals {
+ if err := pull_service.DismissApprovalReviews(ctx, pusher, pr); err != nil {
+ log.Error("DismissApprovalReviews: %v", err)
+ }
+ }
+
+ // Mark reviews for the new commit as not stale
+ if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitIDs[i]); err != nil {
+ log.Error("MarkReviewsAsNotStale: %v", err)
+ }
+ }
+
pull_service.StartPullRequestCheckImmediately(ctx, pr)
err = pr.LoadIssue(ctx)
if err != nil {
diff --git a/services/asymkey/commit.go b/services/asymkey/commit.go
index 105782a93a..148f51fd10 100644
--- a/services/asymkey/commit.go
+++ b/services/asymkey/commit.go
@@ -6,6 +6,7 @@ package asymkey
import (
"context"
"fmt"
+ "slices"
"strings"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -359,24 +360,39 @@ func VerifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, si
return nil
}
+func verifySSHCommitVerificationByInstanceKey(c *git.Commit, committerUser, signerUser *user_model.User, committerGitEmail, publicKeyContent string) *asymkey_model.CommitVerification {
+ fingerprint, err := asymkey_model.CalcFingerprint(publicKeyContent)
+ if err != nil {
+ log.Error("Error calculating the fingerprint public key %q, err: %v", publicKeyContent, err)
+ return nil
+ }
+ sshPubKey := &asymkey_model.PublicKey{
+ Verified: true,
+ Content: publicKeyContent,
+ Fingerprint: fingerprint,
+ HasUsed: true,
+ }
+ return verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, sshPubKey, committerUser, signerUser, committerGitEmail)
+}
+
// ParseCommitWithSSHSignature check if signature is good against keystore.
-func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification {
+func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committerUser *user_model.User) *asymkey_model.CommitVerification {
// Now try to associate the signature with the committer, if present
- if committer.ID != 0 {
+ if committerUser.ID != 0 {
keys, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
- OwnerID: committer.ID,
+ OwnerID: committerUser.ID,
NotKeytype: asymkey_model.KeyTypePrincipal,
})
if err != nil { // Skipping failed to get ssh keys of user
log.Error("ListPublicKeys: %v", err)
return &asymkey_model.CommitVerification{
- CommittingUser: committer,
+ CommittingUser: committerUser,
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
- committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses)
+ committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committerUser.ID, user_model.GetEmailAddresses)
if err != nil {
log.Error("GetEmailAddresses: %v", err)
}
@@ -391,7 +407,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
for _, k := range keys {
if k.Verified && activated {
- commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
+ commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committerUser, committerUser, c.Committer.Email)
if commitVerification != nil {
return commitVerification
}
@@ -399,8 +415,45 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
}
}
+ // Try the pre-set trusted keys (for key-rotation purpose)
+ // At the moment, we still use the SigningName&SigningEmail for the rotated keys.
+ // Maybe in the future we can extend the key format to "ssh-xxx .... old-user@example.com" to support different signer emails.
+ for _, k := range setting.Repository.Signing.TrustedSSHKeys {
+ signerUser := &user_model.User{
+ Name: setting.Repository.Signing.SigningName,
+ Email: setting.Repository.Signing.SigningEmail,
+ }
+ commitVerification := verifySSHCommitVerificationByInstanceKey(c, committerUser, signerUser, c.Committer.Email, k)
+ if commitVerification != nil && commitVerification.Verified {
+ return commitVerification
+ }
+ }
+
+ // Try the configured instance-wide SSH public key
+ if setting.Repository.Signing.SigningFormat == git.SigningKeyFormatSSH && !slices.Contains([]string{"", "default", "none"}, setting.Repository.Signing.SigningKey) {
+ gpgSettings := git.GPGSettings{
+ Sign: true,
+ KeyID: setting.Repository.Signing.SigningKey,
+ Name: setting.Repository.Signing.SigningName,
+ Email: setting.Repository.Signing.SigningEmail,
+ Format: setting.Repository.Signing.SigningFormat,
+ }
+ signerUser := &user_model.User{
+ Name: gpgSettings.Name,
+ Email: gpgSettings.Email,
+ }
+ if err := gpgSettings.LoadPublicKeyContent(); err != nil {
+ log.Error("Error getting instance-wide SSH signing key %q, err: %v", gpgSettings.KeyID, err)
+ } else {
+ commitVerification := verifySSHCommitVerificationByInstanceKey(c, committerUser, signerUser, gpgSettings.Email, gpgSettings.PublicKeyContent)
+ if commitVerification != nil && commitVerification.Verified {
+ return commitVerification
+ }
+ }
+ }
+
return &asymkey_model.CommitVerification{
- CommittingUser: committer,
+ CommittingUser: committerUser,
Verified: false,
Reason: asymkey_model.NoKeyFound,
}
diff --git a/services/asymkey/commit_test.go b/services/asymkey/commit_test.go
new file mode 100644
index 0000000000..0438209a61
--- /dev/null
+++ b/services/asymkey/commit_test.go
@@ -0,0 +1,54 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package asymkey
+
+import (
+ "strings"
+ "testing"
+
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestParseCommitWithSSHSignature(t *testing.T) {
+ // Here we only test the TrustedSSHKeys. The complete signing test is in tests/integration/gpg_ssh_git_test.go
+ t.Run("TrustedSSHKey", func(t *testing.T) {
+ defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")()
+ defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")()
+ defer test.MockVariableValue(&setting.Repository.Signing.TrustedSSHKeys, []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH6Y4idVaW3E+bLw1uqoAfJD7o5Siu+HqS51E9oQLPE9"})()
+
+ commit, err := git.CommitFromReader(nil, git.Sha1ObjectFormat.EmptyObjectID(), strings.NewReader(`tree 9a93ffa76e8b72bdb6431910b3a506fa2b39f42e
+author User Two <user2@example.com> 1749230009 +0200
+committer User Two <user2@example.com> 1749230009 +0200
+gpgsig -----BEGIN SSH SIGNATURE-----
+ U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgfpjiJ1VpbcT5svDW6qgB8kPujl
+ KK74epLnUT2hAs8T0AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+ AAAAQDX2t2iHuuLxEWHLJetYXKsgayv3c43r0pJNfAzdLN55Q65pC5M7rG6++gT2bxcpOu
+ Y6EXbpLqia9sunEF3+LQY=
+ -----END SSH SIGNATURE-----
+
+Initial commit with signed file
+`))
+ require.NoError(t, err)
+ committingUser := &user_model.User{
+ ID: 2,
+ Name: "User Two",
+ Email: "user2@example.com",
+ }
+ ret := ParseCommitWithSSHSignature(t.Context(), commit, committingUser)
+ require.NotNil(t, ret)
+ assert.True(t, ret.Verified)
+ assert.False(t, ret.Warning)
+ assert.Equal(t, committingUser, ret.CommittingUser)
+ if assert.NotNil(t, ret.SigningUser) {
+ assert.Equal(t, "gitea", ret.SigningUser.Name)
+ assert.Equal(t, "gitea@fake.local", ret.SigningUser.Email)
+ }
+ })
+}
diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go
index 2216bca54a..f94462ea46 100644
--- a/services/asymkey/sign.go
+++ b/services/asymkey/sign.go
@@ -6,6 +6,7 @@ package asymkey
import (
"context"
"fmt"
+ "os"
"strings"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -85,9 +86,9 @@ func IsErrWontSign(err error) bool {
}
// SigningKey returns the KeyID and git Signature for the repo
-func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) {
+func SigningKey(ctx context.Context, repoPath string) (*git.SigningKey, *git.Signature) {
if setting.Repository.Signing.SigningKey == "none" {
- return "", nil
+ return nil, nil
}
if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" {
@@ -95,53 +96,77 @@ func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) {
value, _, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunStdString(ctx, &git.RunOpts{Dir: repoPath})
sign, valid := git.ParseBool(strings.TrimSpace(value))
if !sign || !valid {
- return "", nil
+ return nil, nil
}
+ format, _, _ := git.NewCommand("config", "--default", git.SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(ctx, &git.RunOpts{Dir: repoPath})
signingKey, _, _ := git.NewCommand("config", "--get", "user.signingkey").RunStdString(ctx, &git.RunOpts{Dir: repoPath})
signingName, _, _ := git.NewCommand("config", "--get", "user.name").RunStdString(ctx, &git.RunOpts{Dir: repoPath})
signingEmail, _, _ := git.NewCommand("config", "--get", "user.email").RunStdString(ctx, &git.RunOpts{Dir: repoPath})
- return strings.TrimSpace(signingKey), &git.Signature{
- Name: strings.TrimSpace(signingName),
- Email: strings.TrimSpace(signingEmail),
+
+ if strings.TrimSpace(signingKey) == "" {
+ return nil, nil
}
+
+ return &git.SigningKey{
+ KeyID: strings.TrimSpace(signingKey),
+ Format: strings.TrimSpace(format),
+ }, &git.Signature{
+ Name: strings.TrimSpace(signingName),
+ Email: strings.TrimSpace(signingEmail),
+ }
}
- return setting.Repository.Signing.SigningKey, &git.Signature{
- Name: setting.Repository.Signing.SigningName,
- Email: setting.Repository.Signing.SigningEmail,
+ if setting.Repository.Signing.SigningKey == "" {
+ return nil, nil
}
+
+ return &git.SigningKey{
+ KeyID: setting.Repository.Signing.SigningKey,
+ Format: setting.Repository.Signing.SigningFormat,
+ }, &git.Signature{
+ Name: setting.Repository.Signing.SigningName,
+ Email: setting.Repository.Signing.SigningEmail,
+ }
}
// PublicSigningKey gets the public signing key within a provided repository directory
-func PublicSigningKey(ctx context.Context, repoPath string) (string, error) {
+func PublicSigningKey(ctx context.Context, repoPath string) (content, format string, err error) {
signingKey, _ := SigningKey(ctx, repoPath)
- if signingKey == "" {
- return "", nil
+ if signingKey == nil {
+ return "", "", nil
+ }
+ if signingKey.Format == git.SigningKeyFormatSSH {
+ content, err := os.ReadFile(signingKey.KeyID)
+ if err != nil {
+ log.Error("Unable to read SSH public key file in %s: %s, %v", repoPath, signingKey, err)
+ return "", signingKey.Format, err
+ }
+ return string(content), signingKey.Format, nil
}
content, stderr, err := process.GetManager().ExecDir(ctx, -1, repoPath,
- "gpg --export -a", "gpg", "--export", "-a", signingKey)
+ "gpg --export -a", "gpg", "--export", "-a", signingKey.KeyID)
if err != nil {
log.Error("Unable to get default signing key in %s: %s, %s, %v", repoPath, signingKey, stderr, err)
- return "", err
+ return "", signingKey.Format, err
}
- return content, nil
+ return content, signingKey.Format, nil
}
// SignInitialCommit determines if we should sign the initial commit to this repository
-func SignInitialCommit(ctx context.Context, repoPath string, u *user_model.User) (bool, string, *git.Signature, error) {
+func SignInitialCommit(ctx context.Context, repoPath string, u *user_model.User) (bool, *git.SigningKey, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit)
signingKey, sig := SigningKey(ctx, repoPath)
- if signingKey == "" {
- return false, "", nil, &ErrWontSign{noKey}
+ if signingKey == nil {
+ return false, nil, nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", nil, &ErrWontSign{never}
+ return false, nil, nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
@@ -150,18 +175,18 @@ Loop:
IncludeSubKeys: true,
})
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
if len(keys) == 0 {
- return false, "", nil, &ErrWontSign{pubkey}
+ return false, nil, nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
- return false, "", nil, err
+ return false, nil, nil, err
}
if twofaModel == nil {
- return false, "", nil, &ErrWontSign{twofa}
+ return false, nil, nil, &ErrWontSign{twofa}
}
}
}
@@ -169,19 +194,19 @@ Loop:
}
// SignWikiCommit determines if we should sign the commits to this repository wiki
-func SignWikiCommit(ctx context.Context, repo *repo_model.Repository, u *user_model.User) (bool, string, *git.Signature, error) {
+func SignWikiCommit(ctx context.Context, repo *repo_model.Repository, u *user_model.User) (bool, *git.SigningKey, *git.Signature, error) {
repoWikiPath := repo.WikiPath()
rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
signingKey, sig := SigningKey(ctx, repoWikiPath)
- if signingKey == "" {
- return false, "", nil, &ErrWontSign{noKey}
+ if signingKey == nil {
+ return false, nil, nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", nil, &ErrWontSign{never}
+ return false, nil, nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
@@ -190,35 +215,35 @@ Loop:
IncludeSubKeys: true,
})
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
if len(keys) == 0 {
- return false, "", nil, &ErrWontSign{pubkey}
+ return false, nil, nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
- return false, "", nil, err
+ return false, nil, nil, err
}
if twofaModel == nil {
- return false, "", nil, &ErrWontSign{twofa}
+ return false, nil, nil, &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("HEAD")
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
if commit.Signature == nil {
- return false, "", nil, &ErrWontSign{parentSigned}
+ return false, nil, nil, &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(ctx, commit)
if !verification.Verified {
- return false, "", nil, &ErrWontSign{parentSigned}
+ return false, nil, nil, &ErrWontSign{parentSigned}
}
}
}
@@ -226,18 +251,18 @@ Loop:
}
// SignCRUDAction determines if we should sign a CRUD commit to this repository
-func SignCRUDAction(ctx context.Context, repoPath string, u *user_model.User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) {
+func SignCRUDAction(ctx context.Context, repoPath string, u *user_model.User, tmpBasePath, parentCommit string) (bool, *git.SigningKey, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
signingKey, sig := SigningKey(ctx, repoPath)
- if signingKey == "" {
- return false, "", nil, &ErrWontSign{noKey}
+ if signingKey == nil {
+ return false, nil, nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", nil, &ErrWontSign{never}
+ return false, nil, nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
@@ -246,35 +271,35 @@ Loop:
IncludeSubKeys: true,
})
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
if len(keys) == 0 {
- return false, "", nil, &ErrWontSign{pubkey}
+ return false, nil, nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
- return false, "", nil, err
+ return false, nil, nil, err
}
if twofaModel == nil {
- return false, "", nil, &ErrWontSign{twofa}
+ return false, nil, nil, &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(ctx, tmpBasePath)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(parentCommit)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
if commit.Signature == nil {
- return false, "", nil, &ErrWontSign{parentSigned}
+ return false, nil, nil, &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(ctx, commit)
if !verification.Verified {
- return false, "", nil, &ErrWontSign{parentSigned}
+ return false, nil, nil, &ErrWontSign{parentSigned}
}
}
}
@@ -282,16 +307,16 @@ Loop:
}
// SignMerge determines if we should sign a PR merge commit to the base repository
-func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) {
+func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.User, tmpBasePath, baseCommit, headCommit string) (bool, *git.SigningKey, *git.Signature, error) {
if err := pr.LoadBaseRepo(ctx); err != nil {
log.Error("Unable to get Base Repo for pull request")
- return false, "", nil, err
+ return false, nil, nil, err
}
repo := pr.BaseRepo
signingKey, signer := SigningKey(ctx, repo.RepoPath())
- if signingKey == "" {
- return false, "", nil, &ErrWontSign{noKey}
+ if signingKey == nil {
+ return false, nil, nil, &ErrWontSign{noKey}
}
rules := signingModeFromStrings(setting.Repository.Signing.Merges)
@@ -302,7 +327,7 @@ Loop:
for _, rule := range rules {
switch rule {
case never:
- return false, "", nil, &ErrWontSign{never}
+ return false, nil, nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
@@ -311,91 +336,91 @@ Loop:
IncludeSubKeys: true,
})
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
if len(keys) == 0 {
- return false, "", nil, &ErrWontSign{pubkey}
+ return false, nil, nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
- return false, "", nil, err
+ return false, nil, nil, err
}
if twofaModel == nil {
- return false, "", nil, &ErrWontSign{twofa}
+ return false, nil, nil, &ErrWontSign{twofa}
}
case approved:
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pr.BaseBranch)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
if protectedBranch == nil {
- return false, "", nil, &ErrWontSign{approved}
+ return false, nil, nil, &ErrWontSign{approved}
}
if issues_model.GetGrantedApprovalsCount(ctx, protectedBranch, pr) < 1 {
- return false, "", nil, &ErrWontSign{approved}
+ return false, nil, nil, &ErrWontSign{approved}
}
case baseSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(baseCommit)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
verification := ParseCommitWithSignature(ctx, commit)
if !verification.Verified {
- return false, "", nil, &ErrWontSign{baseSigned}
+ return false, nil, nil, &ErrWontSign{baseSigned}
}
case headSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
verification := ParseCommitWithSignature(ctx, commit)
if !verification.Verified {
- return false, "", nil, &ErrWontSign{headSigned}
+ return false, nil, nil, &ErrWontSign{headSigned}
}
case commitsSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
verification := ParseCommitWithSignature(ctx, commit)
if !verification.Verified {
- return false, "", nil, &ErrWontSign{commitsSigned}
+ return false, nil, nil, &ErrWontSign{commitsSigned}
}
// need to work out merge-base
mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit)
if err != nil {
- return false, "", nil, err
+ return false, nil, nil, err
}
for _, commit := range commitList {
verification := ParseCommitWithSignature(ctx, commit)
if !verification.Verified {
- return false, "", nil, &ErrWontSign{commitsSigned}
+ return false, nil, nil, &ErrWontSign{commitsSigned}
}
}
}
diff --git a/services/auth/basic.go b/services/auth/basic.go
index a208590d7b..b2bd14ef5d 100644
--- a/services/auth/basic.go
+++ b/services/auth/basic.go
@@ -7,12 +7,11 @@ package auth
import (
"errors"
"net/http"
- "strings"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/auth/httpauth"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -54,17 +53,15 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
return nil, nil
}
- baHead := req.Header.Get("Authorization")
- if len(baHead) == 0 {
+ authHeader := req.Header.Get("Authorization")
+ if authHeader == "" {
return nil, nil
}
-
- auths := strings.SplitN(baHead, " ", 2)
- if len(auths) != 2 || (strings.ToLower(auths[0]) != "basic") {
+ parsed, ok := httpauth.ParseAuthorizationHeader(authHeader)
+ if !ok || parsed.BasicAuth == nil {
return nil, nil
}
-
- uname, passwd, _ := base.BasicAuthDecode(auths[1])
+ uname, passwd := parsed.BasicAuth.Username, parsed.BasicAuth.Password
// Check if username or password is a token
isUsernameToken := len(passwd) == 0 || passwd == "x-oauth-basic"
diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go
index 66cc686809..7df6f4638e 100644
--- a/services/auth/oauth2.go
+++ b/services/auth/oauth2.go
@@ -13,6 +13,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/auth/httpauth"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -97,9 +98,9 @@ func parseToken(req *http.Request) (string, bool) {
// check header token
if auHead := req.Header.Get("Authorization"); auHead != "" {
- auths := strings.Fields(auHead)
- if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") {
- return auths[1], true
+ parsed, ok := httpauth.ParseAuthorizationHeader(auHead)
+ if ok && parsed.BearerToken != nil {
+ return parsed.BearerToken.Token, true
}
}
return "", false
diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go
index a2e8c2b86a..6005a4744a 100644
--- a/services/auth/source/ldap/source_authenticate.go
+++ b/services/auth/source/ldap/source_authenticate.go
@@ -58,7 +58,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
opts := &user_service.UpdateOptions{}
if source.AdminFilter != "" && user.IsAdmin != sr.IsAdmin {
// Change existing admin flag only if AdminFilter option is set
- opts.IsAdmin = optional.Some(sr.IsAdmin)
+ opts.IsAdmin = user_service.UpdateOptionFieldFromSync(sr.IsAdmin)
}
if !sr.IsAdmin && source.RestrictedFilter != "" && user.IsRestricted != sr.IsRestricted {
// Change existing restricted flag only if RestrictedFilter option is set
diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go
index fa2c45ce4a..e6bce04a83 100644
--- a/services/auth/source/ldap/source_search.go
+++ b/services/auth/source/ldap/source_search.go
@@ -117,10 +117,10 @@ func dial(source *Source) (*ldap.Conn, error) {
}
if source.SecurityProtocol == SecurityProtocolLDAPS {
- return ldap.DialTLS("tcp", net.JoinHostPort(source.Host, strconv.Itoa(source.Port)), tlsConfig) //nolint:staticcheck
+ return ldap.DialTLS("tcp", net.JoinHostPort(source.Host, strconv.Itoa(source.Port)), tlsConfig) //nolint:staticcheck // DialTLS is deprecated
}
- conn, err := ldap.Dial("tcp", net.JoinHostPort(source.Host, strconv.Itoa(source.Port))) //nolint:staticcheck
+ conn, err := ldap.Dial("tcp", net.JoinHostPort(source.Host, strconv.Itoa(source.Port))) //nolint:staticcheck // Dial is deprecated
if err != nil {
return nil, fmt.Errorf("error during Dial: %w", err)
}
diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go
index 678b6b2b62..7b401c5c96 100644
--- a/services/auth/source/ldap/source_sync.go
+++ b/services/auth/source/ldap/source_sync.go
@@ -162,7 +162,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
IsActive: optional.Some(true),
}
if source.AdminFilter != "" {
- opts.IsAdmin = optional.Some(su.IsAdmin)
+ opts.IsAdmin = user_service.UpdateOptionFieldFromSync(su.IsAdmin)
}
// Change existing restricted flag only if RestrictedFilter option is set
if !su.IsAdmin && source.RestrictedFilter != "" {
@@ -178,8 +178,9 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
}
}
- if usr.IsUploadAvatarChanged(su.Avatar) {
- if err == nil && source.AttributeAvatar != "" {
+ if source.AttributeAvatar != "" {
+ if len(su.Avatar) > 0 && usr.IsUploadAvatarChanged(su.Avatar) {
+ log.Trace("SyncExternalUsers[%s]: Uploading new avatar for %s", source.AuthSource.Name, usr.Name)
_ = user_service.UploadAvatar(ctx, usr, su.Avatar)
}
}
diff --git a/services/auth/source/oauth2/urlmapping.go b/services/auth/source/oauth2/urlmapping.go
index d0442d58a8..b9f445caa7 100644
--- a/services/auth/source/oauth2/urlmapping.go
+++ b/services/auth/source/oauth2/urlmapping.go
@@ -14,11 +14,11 @@ type CustomURLMapping struct {
// CustomURLSettings describes the urls values and availability to use when customizing OAuth2 provider URLs
type CustomURLSettings struct {
- AuthURL Attribute `json:",omitempty"`
- TokenURL Attribute `json:",omitempty"`
- ProfileURL Attribute `json:",omitempty"`
- EmailURL Attribute `json:",omitempty"`
- Tenant Attribute `json:",omitempty"`
+ AuthURL Attribute
+ TokenURL Attribute
+ ProfileURL Attribute
+ EmailURL Attribute
+ Tenant Attribute
}
// Attribute describes the availability, and required status for a custom url configuration
diff --git a/services/context/api.go b/services/context/api.go
index d43e15bf24..ab50a360f4 100644
--- a/services/context/api.go
+++ b/services/context/api.go
@@ -9,6 +9,7 @@ import (
"fmt"
"net/http"
"net/url"
+ "slices"
"strconv"
"strings"
@@ -245,7 +246,7 @@ func APIContexter() func(http.Handler) http.Handler {
// APIErrorNotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice
func (ctx *APIContext) APIErrorNotFound(objs ...any) {
- message := ctx.Locale.TrString("error.not_found")
+ var message string
var errs []string
for _, obj := range objs {
// Ignore nil
@@ -259,9 +260,8 @@ func (ctx *APIContext) APIErrorNotFound(objs ...any) {
message = obj.(string)
}
}
-
ctx.JSON(http.StatusNotFound, map[string]any{
- "message": message,
+ "message": util.IfZero(message, "not found"), // do not use locale in API
"url": setting.API.SwaggerURL,
"errors": errs,
})
@@ -365,11 +365,5 @@ func (ctx *APIContext) IsUserRepoAdmin() bool {
// IsUserRepoWriter returns true if current user has "write" privilege in current repo
func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool {
- for _, unitType := range unitTypes {
- if ctx.Repo.CanWrite(unitType) {
- return true
- }
- }
-
- return false
+ return slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite)
}
diff --git a/services/context/base_form.go b/services/context/base_form.go
index 5b8cae9e99..81fd7cd328 100644
--- a/services/context/base_form.go
+++ b/services/context/base_form.go
@@ -12,6 +12,8 @@ import (
)
// FormString returns the first value matching the provided key in the form as a string
+// It works the same as http.Request.FormValue:
+// try urlencoded request body first, then query string, then multipart form body
func (b *Base) FormString(key string, def ...string) string {
s := b.Req.FormValue(key)
if s == "" {
@@ -20,7 +22,7 @@ func (b *Base) FormString(key string, def ...string) string {
return s
}
-// FormStrings returns a string slice for the provided key from the form
+// FormStrings returns a values for the key in the form (including query parameters), similar to FormString
func (b *Base) FormStrings(key string) []string {
if b.Req.Form == nil {
if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
diff --git a/services/context/context.go b/services/context/context.go
index 7f623f85bd..32ec260aab 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types"
@@ -261,3 +262,11 @@ func (ctx *Context) JSONError(msg any) {
panic(fmt.Sprintf("unsupported type: %T", msg))
}
}
+
+func (ctx *Context) JSONErrorNotFound(optMsg ...string) {
+ msg := util.OptionalArg(optMsg)
+ if msg == "" {
+ msg = ctx.Locale.TrString("error.not_found")
+ }
+ ctx.JSON(http.StatusNotFound, map[string]any{"errorMessage": msg, "renderFormat": "text"})
+}
diff --git a/services/context/pagination.go b/services/context/pagination.go
index 25a9298e01..2a9805db05 100644
--- a/services/context/pagination.go
+++ b/services/context/pagination.go
@@ -33,8 +33,8 @@ func (p *Pagination) WithCurRows(n int) *Pagination {
return p
}
-func (p *Pagination) AddParamFromRequest(req *http.Request) {
- for key, values := range req.URL.Query() {
+func (p *Pagination) AddParamFromQuery(q url.Values) {
+ for key, values := range q {
if key == "page" || len(values) == 0 || (len(values) == 1 && values[0] == "") {
continue
}
@@ -45,6 +45,10 @@ func (p *Pagination) AddParamFromRequest(req *http.Request) {
}
}
+func (p *Pagination) AddParamFromRequest(req *http.Request) {
+ p.AddParamFromQuery(req.URL.Query())
+}
+
// GetParams returns the configured URL params
func (p *Pagination) GetParams() template.URL {
return template.URL(strings.Join(p.urlParams, "&"))
diff --git a/services/context/permission.go b/services/context/permission.go
index 7055f798da..c0a5a98724 100644
--- a/services/context/permission.go
+++ b/services/context/permission.go
@@ -5,6 +5,7 @@ package context
import (
"net/http"
+ "slices"
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
@@ -34,10 +35,8 @@ func CanWriteToBranch() func(ctx *Context) {
// RequireUnitWriter returns a middleware for requiring repository write to one of the unit permission
func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) {
- for _, unitType := range unitTypes {
- if ctx.Repo.CanWrite(unitType) {
- return
- }
+ if slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite) {
+ return
}
ctx.NotFound(nil)
}
diff --git a/services/context/private.go b/services/context/private.go
index 3f7637518b..d20e49f588 100644
--- a/services/context/private.go
+++ b/services/context/private.go
@@ -28,7 +28,6 @@ func init() {
})
}
-// Deadline is part of the interface for context.Context and we pass this to the request context
func (ctx *PrivateContext) Deadline() (deadline time.Time, ok bool) {
if ctx.Override != nil {
return ctx.Override.Deadline()
@@ -36,7 +35,6 @@ func (ctx *PrivateContext) Deadline() (deadline time.Time, ok bool) {
return ctx.Base.Deadline()
}
-// Done is part of the interface for context.Context and we pass this to the request context
func (ctx *PrivateContext) Done() <-chan struct{} {
if ctx.Override != nil {
return ctx.Override.Done()
@@ -44,7 +42,6 @@ func (ctx *PrivateContext) Done() <-chan struct{} {
return ctx.Base.Done()
}
-// Err is part of the interface for context.Context and we pass this to the request context
func (ctx *PrivateContext) Err() error {
if ctx.Override != nil {
return ctx.Override.Err()
@@ -52,14 +49,14 @@ func (ctx *PrivateContext) Err() error {
return ctx.Base.Err()
}
-var privateContextKey any = "default_private_context"
+type privateContextKeyType struct{}
+
+var privateContextKey privateContextKeyType
-// GetPrivateContext returns a context for Private routes
func GetPrivateContext(req *http.Request) *PrivateContext {
return req.Context().Value(privateContextKey).(*PrivateContext)
}
-// PrivateContexter returns apicontext as middleware
func PrivateContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
diff --git a/services/context/repo.go b/services/context/repo.go
index 127d313258..572211712b 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -71,11 +71,6 @@ func (r *Repository) CanWriteToBranch(ctx context.Context, user *user_model.User
return issues_model.CanMaintainerWriteToBranch(ctx, r.Permission, branch, user)
}
-// CanEnableEditor returns true if repository is editable and user has proper access level.
-func (r *Repository) CanEnableEditor(ctx context.Context, user *user_model.User) bool {
- return r.RefFullName.IsBranch() && r.CanWriteToBranch(ctx, user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
-}
-
// CanCreateBranch returns true if repository is editable and user has proper access level.
func (r *Repository) CanCreateBranch() bool {
return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch()
@@ -94,58 +89,100 @@ func RepoMustNotBeArchived() func(ctx *Context) {
}
}
-// CanCommitToBranchResults represents the results of CanCommitToBranch
-type CanCommitToBranchResults struct {
- CanCommitToBranch bool
- EditorEnabled bool
- UserCanPush bool
- RequireSigned bool
- WillSign bool
- SigningKey string
- WontSignReason string
+type CommitFormOptions struct {
+ NeedFork bool
+
+ TargetRepo *repo_model.Repository
+ TargetFormAction string
+ WillSubmitToFork bool
+ CanCommitToBranch bool
+ UserCanPush bool
+ RequireSigned bool
+ WillSign bool
+ SigningKey *git.SigningKey
+ WontSignReason string
+ CanCreatePullRequest bool
+ CanCreateBasePullRequest bool
}
-// CanCommitToBranch returns true if repository is editable and user has proper access level
-//
-// and branch is not protected for push
-func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
- protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, r.Repository.ID, r.BranchName)
+func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *repo_model.Repository, doerRepoPerm access_model.Permission, refName git.RefName) (*CommitFormOptions, error) {
+ if !refName.IsBranch() {
+ // it shouldn't happen because middleware already checks
+ return nil, util.NewInvalidArgumentErrorf("ref %q is not a branch", refName)
+ }
+
+ originRepo := targetRepo
+ branchName := refName.ShortName()
+ // TODO: CanMaintainerWriteToBranch is a bad name, but it really does what "CanWriteToBranch" does
+ if !issues_model.CanMaintainerWriteToBranch(ctx, doerRepoPerm, branchName, doer) {
+ targetRepo = repo_model.GetForkedRepo(ctx, doer.ID, targetRepo.ID)
+ if targetRepo == nil {
+ return &CommitFormOptions{NeedFork: true}, nil
+ }
+ // now, we get our own forked repo; it must be writable by us.
+ }
+ submitToForkedRepo := targetRepo.ID != originRepo.ID
+ err := targetRepo.GetBaseRepo(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, targetRepo.ID, branchName)
if err != nil {
- return CanCommitToBranchResults{}, err
+ return nil, err
}
- userCanPush := true
- requireSigned := false
+ canPushWithProtection := true
+ protectionRequireSigned := false
if protectedBranch != nil {
- protectedBranch.Repo = r.Repository
- userCanPush = protectedBranch.CanUserPush(ctx, doer)
- requireSigned = protectedBranch.RequireSignedCommits
+ protectedBranch.Repo = targetRepo
+ canPushWithProtection = protectedBranch.CanUserPush(ctx, doer)
+ protectionRequireSigned = protectedBranch.RequireSignedCommits
}
- sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
-
- canCommit := r.CanEnableEditor(ctx, doer) && userCanPush
- if requireSigned {
- canCommit = canCommit && sign
- }
+ willSign, signKeyID, _, err := asymkey_service.SignCRUDAction(ctx, targetRepo.RepoPath(), doer, targetRepo.RepoPath(), refName.String())
wontSignReason := ""
- if err != nil {
- if asymkey_service.IsErrWontSign(err) {
- wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
- err = nil
- } else {
- wontSignReason = "error"
- }
+ if asymkey_service.IsErrWontSign(err) {
+ wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
+ } else if err != nil {
+ return nil, err
}
- return CanCommitToBranchResults{
- CanCommitToBranch: canCommit,
- EditorEnabled: r.CanEnableEditor(ctx, doer),
- UserCanPush: userCanPush,
- RequireSigned: requireSigned,
- WillSign: sign,
- SigningKey: keyID,
+ canCommitToBranch := !submitToForkedRepo /* same repo */ && targetRepo.CanEnableEditor() && canPushWithProtection
+ if protectionRequireSigned {
+ canCommitToBranch = canCommitToBranch && willSign
+ }
+
+ canCreateBasePullRequest := targetRepo.BaseRepo != nil && targetRepo.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests)
+ canCreatePullRequest := targetRepo.UnitEnabled(ctx, unit_model.TypePullRequests) || canCreateBasePullRequest
+
+ opts := &CommitFormOptions{
+ TargetRepo: targetRepo,
+ WillSubmitToFork: submitToForkedRepo,
+ CanCommitToBranch: canCommitToBranch,
+ UserCanPush: canPushWithProtection,
+ RequireSigned: protectionRequireSigned,
+ WillSign: willSign,
+ SigningKey: signKeyID,
WontSignReason: wontSignReason,
- }, err
+
+ CanCreatePullRequest: canCreatePullRequest,
+ CanCreateBasePullRequest: canCreateBasePullRequest,
+ }
+ editorAction := ctx.PathParam("editor_action")
+ editorPathParamRemaining := util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+ if submitToForkedRepo {
+ // there is only "default branch" in forked repo, we will use "from_base_branch" to get a new branch from base repo
+ editorPathParamRemaining = util.PathEscapeSegments(targetRepo.DefaultBranch) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + "?from_base_branch=" + url.QueryEscape(branchName)
+ }
+ if editorAction == "_cherrypick" {
+ opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + ctx.PathParam("sha") + "/" + editorPathParamRemaining
+ } else {
+ opts.TargetFormAction = targetRepo.Link() + "/" + editorAction + "/" + editorPathParamRemaining
+ }
+ if ctx.Req.URL.RawQuery != "" {
+ opts.TargetFormAction += util.Iif(strings.Contains(opts.TargetFormAction, "?"), "&", "?") + ctx.Req.URL.RawQuery
+ }
+ return opts, nil
}
// CanUseTimetracker returns whether a user can use the timetracker.
@@ -795,8 +832,8 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
return func(ctx *Context) {
var err error
refType := detectRefType
- if ctx.Repo.Repository.IsBeingCreated() {
- return // no git repo, so do nothing, users will see a "migrating" UI provided by "migrate/migrating.tmpl"
+ if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
+ return // no git repo, so do nothing, users will see a "migrating" UI provided by "migrate/migrating.tmpl", or empty repo guide
}
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
@@ -936,6 +973,15 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
ctx.ServerError("GetCommitsCount", err)
return
}
+ if ctx.Repo.RefFullName.IsTag() {
+ rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Repo.RefFullName.TagName())
+ if err == nil && rel.NumCommits <= 0 {
+ rel.NumCommits = ctx.Repo.CommitsCount
+ if err := repo_model.UpdateReleaseNumCommits(ctx, rel); err != nil {
+ log.Error("UpdateReleaseNumCommits", err)
+ }
+ }
+ }
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
}
diff --git a/services/context/upload/upload.go b/services/context/upload/upload.go
index 12aa485aa7..23707950d4 100644
--- a/services/context/upload/upload.go
+++ b/services/context/upload/upload.go
@@ -11,7 +11,9 @@ import (
"regexp"
"strings"
+ repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
)
@@ -39,7 +41,7 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error {
allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format
allowedTypes := []string{}
- for _, entry := range strings.Split(allowedTypesStr, ",") {
+ for entry := range strings.SplitSeq(allowedTypesStr, ",") {
entry = strings.ToLower(strings.TrimSpace(entry))
if entry != "" {
allowedTypes = append(allowedTypes, entry)
@@ -106,12 +108,17 @@ func AddUploadContext(ctx *context.Context, uploadType string) {
ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Attachment.AllowedTypes, "|", ",")
ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles
ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize
- case "repo":
- ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/upload-file"
- ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/upload-remove"
- ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/upload-file"
- ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Upload.AllowedTypes, "|", ",")
- ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
- ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
+ default:
+ setting.PanicInDevOrTesting("Invalid upload type: %s", uploadType)
}
}
+
+func AddUploadContextForRepo(ctx reqctx.RequestContext, repo *repo_model.Repository) {
+ ctxData, repoLink := ctx.GetData(), repo.Link()
+ ctxData["UploadUrl"] = repoLink + "/upload-file"
+ ctxData["UploadRemoveUrl"] = repoLink + "/upload-remove"
+ ctxData["UploadLinkUrl"] = repoLink + "/upload-file"
+ ctxData["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Upload.AllowedTypes, "|", ",")
+ ctxData["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
+ ctxData["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
+}
diff --git a/services/convert/convert.go b/services/convert/convert.go
index b93365bbb9..0de3822140 100644
--- a/services/convert/convert.go
+++ b/services/convert/convert.go
@@ -5,8 +5,11 @@
package convert
import (
+ "bytes"
"context"
"fmt"
+ "net/url"
+ "path"
"strconv"
"strings"
"time"
@@ -14,6 +17,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
@@ -22,6 +26,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
@@ -32,6 +37,7 @@ import (
"code.gitea.io/gitea/services/gitdiff"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
+ "github.com/nektos/act/pkg/model"
)
// ToEmail convert models.EmailAddress to api.Email
@@ -143,7 +149,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
mergeWhitelistUsernames := getWhitelistEntities(readers, bp.MergeWhitelistUserIDs)
approvalsWhitelistUsernames := getWhitelistEntities(readers, bp.ApprovalsWhitelistUserIDs)
- teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
+ teamReaders, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.Owner.ID, repo.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
if err != nil {
log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
}
@@ -241,6 +247,242 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
}, nil
}
+func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun) (*api.ActionWorkflowRun, error) {
+ err := run.LoadAttributes(ctx)
+ if err != nil {
+ return nil, err
+ }
+ status, conclusion := ToActionsStatus(run.Status)
+ return &api.ActionWorkflowRun{
+ ID: run.ID,
+ URL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), run.ID),
+ HTMLURL: run.HTMLURL(),
+ RunNumber: run.Index,
+ StartedAt: run.Started.AsLocalTime(),
+ CompletedAt: run.Stopped.AsLocalTime(),
+ Event: string(run.Event),
+ DisplayTitle: run.Title,
+ HeadBranch: git.RefName(run.Ref).BranchName(),
+ HeadSha: run.CommitSHA,
+ Status: status,
+ Conclusion: conclusion,
+ Path: fmt.Sprintf("%s@%s", run.WorkflowID, run.Ref),
+ Repository: ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
+ TriggerActor: ToUser(ctx, run.TriggerUser, nil),
+ // We do not have a way to get a different User for the actor than the trigger user
+ Actor: ToUser(ctx, run.TriggerUser, nil),
+ }, nil
+}
+
+func ToWorkflowRunAction(status actions_model.Status) string {
+ var action string
+ switch status {
+ case actions_model.StatusWaiting, actions_model.StatusBlocked:
+ action = "requested"
+ case actions_model.StatusRunning:
+ action = "in_progress"
+ }
+ if status.IsDone() {
+ action = "completed"
+ }
+ return action
+}
+
+func ToActionsStatus(status actions_model.Status) (string, string) {
+ var action string
+ var conclusion string
+ switch status {
+ // This is a naming conflict of the webhook between Gitea and GitHub Actions
+ case actions_model.StatusWaiting:
+ action = "queued"
+ case actions_model.StatusBlocked:
+ action = "waiting"
+ case actions_model.StatusRunning:
+ action = "in_progress"
+ }
+ if status.IsDone() {
+ action = "completed"
+ switch status {
+ case actions_model.StatusSuccess:
+ conclusion = "success"
+ case actions_model.StatusCancelled:
+ conclusion = "cancelled"
+ case actions_model.StatusFailure:
+ conclusion = "failure"
+ case actions_model.StatusSkipped:
+ conclusion = "skipped"
+ }
+ }
+ return action, conclusion
+}
+
+// ToActionWorkflowJob convert a actions_model.ActionRunJob to an api.ActionWorkflowJob
+// task is optional and can be nil
+func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task *actions_model.ActionTask, job *actions_model.ActionRunJob) (*api.ActionWorkflowJob, error) {
+ err := job.LoadAttributes(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ jobIndex := 0
+ jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
+ if err != nil {
+ return nil, err
+ }
+ for i, j := range jobs {
+ if j.ID == job.ID {
+ jobIndex = i
+ break
+ }
+ }
+
+ status, conclusion := ToActionsStatus(job.Status)
+ var runnerID int64
+ var runnerName string
+ var steps []*api.ActionWorkflowStep
+
+ if job.TaskID != 0 {
+ if task == nil {
+ task, _, err = db.GetByID[actions_model.ActionTask](ctx, job.TaskID)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ runnerID = task.RunnerID
+ if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
+ runnerName = runner.Name
+ }
+ for i, step := range task.Steps {
+ stepStatus, stepConclusion := ToActionsStatus(job.Status)
+ steps = append(steps, &api.ActionWorkflowStep{
+ Name: step.Name,
+ Number: int64(i),
+ Status: stepStatus,
+ Conclusion: stepConclusion,
+ StartedAt: step.Started.AsTime().UTC(),
+ CompletedAt: step.Stopped.AsTime().UTC(),
+ })
+ }
+ }
+
+ return &api.ActionWorkflowJob{
+ ID: job.ID,
+ // missing api endpoint for this location
+ URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), job.ID),
+ HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), jobIndex),
+ RunID: job.RunID,
+ // Missing api endpoint for this location, artifacts are available under a nested url
+ RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID),
+ Name: job.Name,
+ Labels: job.RunsOn,
+ RunAttempt: job.Attempt,
+ HeadSha: job.Run.CommitSHA,
+ HeadBranch: git.RefName(job.Run.Ref).BranchName(),
+ Status: status,
+ Conclusion: conclusion,
+ RunnerID: runnerID,
+ RunnerName: runnerName,
+ Steps: steps,
+ CreatedAt: job.Created.AsTime().UTC(),
+ StartedAt: job.Started.AsTime().UTC(),
+ CompletedAt: job.Stopped.AsTime().UTC(),
+ }, nil
+}
+
+func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
+ cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions)
+ cfg := cfgUnit.ActionsConfig()
+
+ defaultBranch, _ := commit.GetBranchName()
+
+ workflowURL := fmt.Sprintf("%s/actions/workflows/%s", repo.APIURL(), util.PathEscapeSegments(entry.Name()))
+ workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", repo.HTMLURL(ctx), util.PathEscapeSegments(defaultBranch), util.PathEscapeSegments(folder), util.PathEscapeSegments(entry.Name()))
+ badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", repo.HTMLURL(ctx), util.PathEscapeSegments(entry.Name()), url.QueryEscape(repo.DefaultBranch))
+
+ // See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
+ // State types:
+ // - active
+ // - deleted
+ // - disabled_fork
+ // - disabled_inactivity
+ // - disabled_manually
+ state := "active"
+ if cfg.IsWorkflowDisabled(entry.Name()) {
+ state = "disabled_manually"
+ }
+
+ // The CreatedAt and UpdatedAt fields currently reflect the timestamp of the latest commit, which can later be refined
+ // by retrieving the first and last commits for the file history. The first commit would indicate the creation date,
+ // while the last commit would represent the modification date. The DeletedAt could be determined by identifying
+ // the last commit where the file existed. However, this implementation has not been done here yet, as it would likely
+ // cause a significant performance degradation.
+ createdAt := commit.Author.When
+ updatedAt := commit.Author.When
+
+ content, err := actions.GetContentFromEntry(entry)
+ name := entry.Name()
+ if err == nil {
+ workflow, err := model.ReadWorkflow(bytes.NewReader(content))
+ if err == nil {
+ // Only use the name when specified in the workflow file
+ if workflow.Name != "" {
+ name = workflow.Name
+ }
+ } else {
+ log.Error("getActionWorkflowEntry: Failed to parse workflow: %v", err)
+ }
+ } else {
+ log.Error("getActionWorkflowEntry: Failed to get content from entry: %v", err)
+ }
+
+ return &api.ActionWorkflow{
+ ID: entry.Name(),
+ Name: name,
+ Path: path.Join(folder, entry.Name()),
+ State: state,
+ CreatedAt: createdAt,
+ UpdatedAt: updatedAt,
+ URL: workflowURL,
+ HTMLURL: workflowRepoURL,
+ BadgeURL: badgeURL,
+ }
+}
+
+func ListActionWorkflows(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository) ([]*api.ActionWorkflow, error) {
+ defaultBranchCommit, err := gitrepo.GetBranchCommit(repo.DefaultBranch)
+ if err != nil {
+ return nil, err
+ }
+
+ folder, entries, err := actions.ListWorkflows(defaultBranchCommit)
+ if err != nil {
+ return nil, err
+ }
+
+ workflows := make([]*api.ActionWorkflow, len(entries))
+ for i, entry := range entries {
+ workflows[i] = getActionWorkflowEntry(ctx, repo, defaultBranchCommit, folder, entry)
+ }
+
+ return workflows, nil
+}
+
+func GetActionWorkflow(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository, workflowID string) (*api.ActionWorkflow, error) {
+ entries, err := ListActionWorkflows(ctx, gitrepo, repo)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, entry := range entries {
+ if entry.ID == workflowID {
+ return entry, nil
+ }
+ }
+
+ return nil, util.NewNotExistErrorf("workflow %q not found", workflowID)
+}
+
// ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact
func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) {
url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID)
@@ -485,7 +727,7 @@ func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo
whitelistUsernames := getWhitelistEntities(readers, pt.AllowlistUserIDs)
- teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
+ teamReaders, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.Owner.ID, repo.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
if err != nil {
log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
}
diff --git a/services/convert/pull.go b/services/convert/pull.go
index 7798bebb08..8f9679f649 100644
--- a/services/convert/pull.go
+++ b/services/convert/pull.go
@@ -419,6 +419,9 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs
if baseBranch != nil {
apiPullRequest.Base.Sha = baseBranch.CommitID
}
+ if pr.HeadRepoID == pr.BaseRepoID {
+ apiPullRequest.Head.Repository = apiPullRequest.Base.Repository
+ }
// pull request head branch, both repository and branch could not exist
if pr.HeadRepo != nil {
@@ -431,22 +434,19 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs
if exist {
apiPullRequest.Head.Ref = pr.HeadBranch
}
+ if pr.HeadRepoID != pr.BaseRepoID {
+ p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
+ if err != nil {
+ log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err)
+ p.AccessMode = perm.AccessModeNone
+ }
+ apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p)
+ }
}
if apiPullRequest.Head.Ref == "" {
apiPullRequest.Head.Ref = pr.GetGitRefName()
}
- if pr.HeadRepoID == pr.BaseRepoID {
- apiPullRequest.Head.Repository = apiPullRequest.Base.Repository
- } else {
- p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
- if err != nil {
- log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err)
- p.AccessMode = perm.AccessModeNone
- }
- apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p)
- }
-
if pr.Flow == issues_model.PullRequestFlowAGit {
apiPullRequest.Head.Name = ""
}
diff --git a/services/convert/pull_test.go b/services/convert/pull_test.go
index cd86283c8a..dfbe24d184 100644
--- a/services/convert/pull_test.go
+++ b/services/convert/pull_test.go
@@ -46,4 +46,11 @@ func TestPullRequest_APIFormat(t *testing.T) {
assert.NotNil(t, apiPullRequest)
assert.Nil(t, apiPullRequest.Head.Repository)
assert.EqualValues(t, -1, apiPullRequest.Head.RepoID)
+
+ apiPullRequests, err := ToAPIPullRequests(git.DefaultContext, pr.BaseRepo, []*issues_model.PullRequest{pr}, nil)
+ assert.NoError(t, err)
+ assert.Len(t, apiPullRequests, 1)
+ assert.NotNil(t, apiPullRequests[0])
+ assert.Nil(t, apiPullRequests[0].Head.Repository)
+ assert.EqualValues(t, -1, apiPullRequests[0].Head.RepoID)
}
diff --git a/services/convert/repository.go b/services/convert/repository.go
index 7dfdfd2179..614eb58a88 100644
--- a/services/convert/repository.go
+++ b/services/convert/repository.go
@@ -98,6 +98,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
allowSquash := false
allowFastForwardOnly := false
allowRebaseUpdate := false
+ allowManualMerge := true
+ autodetectManualMerge := false
defaultDeleteBranchAfterMerge := false
defaultMergeStyle := repo_model.MergeStyleMerge
defaultAllowMaintainerEdit := false
@@ -111,6 +113,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
allowSquash = config.AllowSquash
allowFastForwardOnly = config.AllowFastForwardOnly
allowRebaseUpdate = config.AllowRebaseUpdate
+ allowManualMerge = config.AllowManualMerge
+ autodetectManualMerge = config.AutodetectManualMerge
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
defaultMergeStyle = config.GetDefaultMergeStyle()
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
@@ -235,6 +239,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
AllowSquash: allowSquash,
AllowFastForwardOnly: allowFastForwardOnly,
AllowRebaseUpdate: allowRebaseUpdate,
+ AllowManualMerge: allowManualMerge,
+ AutodetectManualMerge: autodetectManualMerge,
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
DefaultMergeStyle: string(defaultMergeStyle),
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
diff --git a/services/convert/status.go b/services/convert/status.go
index 6cef63c1cd..b4864a0307 100644
--- a/services/convert/status.go
+++ b/services/convert/status.go
@@ -5,6 +5,7 @@ package convert
import (
"context"
+ "net/url"
git_model "code.gitea.io/gitea/models/git"
user_model "code.gitea.io/gitea/models/user"
@@ -32,34 +33,29 @@ func ToCommitStatus(ctx context.Context, status *git_model.CommitStatus) *api.Co
return apiStatus
}
+func ToCommitStatuses(ctx context.Context, statuses []*git_model.CommitStatus) []*api.CommitStatus {
+ apiStatuses := make([]*api.CommitStatus, len(statuses))
+ for i, status := range statuses {
+ apiStatuses[i] = ToCommitStatus(ctx, status)
+ }
+ return apiStatuses
+}
+
// ToCombinedStatus converts List of CommitStatus to a CombinedStatus
func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, repo *api.Repository) *api.CombinedStatus {
if len(statuses) == 0 {
return nil
}
- retStatus := &api.CombinedStatus{
- SHA: statuses[0].SHA,
+ combinedStatus := git_model.CalcCommitStatus(statuses)
+
+ return &api.CombinedStatus{
+ State: combinedStatus.State,
+ Statuses: ToCommitStatuses(ctx, statuses),
+ SHA: combinedStatus.SHA,
TotalCount: len(statuses),
Repository: repo,
- URL: "",
+ CommitURL: repo.URL + "/commits/" + url.PathEscape(combinedStatus.SHA),
+ URL: repo.URL + "/commits/" + url.PathEscape(combinedStatus.SHA) + "/status",
}
-
- retStatus.Statuses = make([]*api.CommitStatus, 0, len(statuses))
- for _, status := range statuses {
- retStatus.Statuses = append(retStatus.Statuses, ToCommitStatus(ctx, status))
- if retStatus.State == "" || status.State.NoBetterThan(retStatus.State) {
- retStatus.State = status.State
- }
- }
- // According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference
- // > Additionally, a combined state is returned. The state is one of:
- // > failure if any of the contexts report as error or failure
- // > pending if there are no statuses or a context is pending
- // > success if the latest status for all contexts is success
- if retStatus.State.IsError() {
- retStatus.State = api.CommitStatusFailure
- }
-
- return retStatus
}
diff --git a/services/doctor/actions.go b/services/doctor/actions.go
index 7c44fb8392..28e26c88eb 100644
--- a/services/doctor/actions.go
+++ b/services/doctor/actions.go
@@ -19,7 +19,7 @@ func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bo
var reposToFix []*repo_model.Repository
for page := 1; ; page++ {
- repos, _, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ repos, _, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: repo_model.RepositoryListDefaultPageSize,
Page: page,
diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go
index 62326ed07c..d5a133d8b2 100644
--- a/services/doctor/dbconsistency.go
+++ b/services/doctor/dbconsistency.go
@@ -15,6 +15,7 @@ import (
secret_model "code.gitea.io/gitea/models/secret"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ issue_service "code.gitea.io/gitea/services/issue"
)
type consistencyCheck struct {
@@ -93,7 +94,7 @@ func prepareDBConsistencyChecks() []consistencyCheck {
// find issues without existing repository
Name: "Orphaned Issues without existing repository",
Counter: issues_model.CountOrphanedIssues,
- Fixer: asFixer(issues_model.DeleteOrphanedIssues),
+ Fixer: asFixer(issue_service.DeleteOrphanedIssues),
},
// find releases without existing repository
genericOrphanCheck("Orphaned Releases without existing repository",
diff --git a/services/doctor/paths.go b/services/doctor/paths.go
index 3f62d587ab..4214c36b1a 100644
--- a/services/doctor/paths.go
+++ b/services/doctor/paths.go
@@ -99,15 +99,14 @@ func checkConfigurationFiles(ctx context.Context, logger log.Logger, autofix boo
func isWritableDir(path string) error {
// There's no platform-independent way of checking if a directory is writable
// https://stackoverflow.com/questions/20026320/how-to-tell-if-folder-exists-and-is-writable
-
tmpFile, err := os.CreateTemp(path, "doctors-order")
if err != nil {
return err
}
if err := os.Remove(tmpFile.Name()); err != nil {
- fmt.Printf("Warning: can't remove temporary file: '%s'\n", tmpFile.Name()) //nolint:forbidigo
+ log.Warn("can't remove temporary file: %q", tmpFile.Name())
}
- tmpFile.Close()
+ _ = tmpFile.Close()
return nil
}
diff --git a/services/doctor/repository.go b/services/doctor/repository.go
index 6c33426636..359c4a17e0 100644
--- a/services/doctor/repository.go
+++ b/services/doctor/repository.go
@@ -7,7 +7,6 @@ import (
"context"
"code.gitea.io/gitea/models/db"
- user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
repo_service "code.gitea.io/gitea/services/repository"
@@ -39,7 +38,6 @@ func deleteOrphanedRepos(ctx context.Context) (int64, error) {
batchSize := db.MaxBatchInsertSize("repository")
e := db.GetEngine(ctx)
var deleted int64
- adminUser := &user_model.User{IsAdmin: true}
for {
select {
@@ -60,7 +58,7 @@ func deleteOrphanedRepos(ctx context.Context) (int64, error) {
}
for _, id := range ids {
- if err := repo_service.DeleteRepositoryDirectly(ctx, adminUser, id, true); err != nil {
+ if err := repo_service.DeleteRepositoryDirectly(ctx, id, true); err != nil {
return deleted, err
}
deleted++
diff --git a/services/feed/feed_test.go b/services/feed/feed_test.go
index a3492938c8..705d42a2eb 100644
--- a/services/feed/feed_test.go
+++ b/services/feed/feed_test.go
@@ -147,7 +147,7 @@ func TestRepoActions(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
_ = db.TruncateBeans(db.DefaultContext, &activities_model.Action{})
- for i := 0; i < 3; i++ {
+ for i := range 3 {
_ = db.Insert(db.DefaultContext, &activities_model.Action{
UserID: 2 + int64(i),
ActUserID: 2,
diff --git a/services/forms/org.go b/services/forms/org.go
index db182f7e96..2ac18ef25c 100644
--- a/services/forms/org.go
+++ b/services/forms/org.go
@@ -36,7 +36,6 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding
// UpdateOrgSettingForm form for updating organization settings
type UpdateOrgSettingForm struct {
- Name string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
FullName string `binding:"MaxSize(100)"`
Email string `binding:"MaxSize(255)"`
Description string `binding:"MaxSize(255)"`
@@ -53,6 +52,11 @@ func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors)
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
+type RenameOrgForm struct {
+ OrgName string `binding:"Required"`
+ NewOrgName string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
+}
+
// ___________
// \__ ___/___ _____ _____
// | |_/ __ \\__ \ / \
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index a2827e516a..d116bb9f11 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -233,6 +233,7 @@ type WebhookForm struct {
Release bool
Package bool
Status bool
+ WorkflowRun bool
WorkflowJob bool
Active bool
BranchFilter string `binding:"GlobPattern"`
@@ -679,129 +680,6 @@ func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.E
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
-// ___________ .___.__ __
-// \_ _____/ __| _/|__|/ |_
-// | __)_ / __ | | \ __\
-// | \/ /_/ | | || |
-// /_______ /\____ | |__||__|
-// \/ \/
-
-// EditRepoFileForm form for changing repository file
-type EditRepoFileForm struct {
- TreePath string `binding:"Required;MaxSize(500)"`
- Content string
- CommitSummary string `binding:"MaxSize(100)"`
- CommitMessage string
- CommitChoice string `binding:"Required;MaxSize(50)"`
- NewBranchName string `binding:"GitRefName;MaxSize(100)"`
- LastCommit string
- Signoff bool
- CommitEmail string
-}
-
-// Validate validates the fields
-func (f *EditRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
- ctx := context.GetValidateContext(req)
- return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// EditPreviewDiffForm form for changing preview diff
-type EditPreviewDiffForm struct {
- Content string
-}
-
-// Validate validates the fields
-func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
- ctx := context.GetValidateContext(req)
- return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// _________ .__ __________.__ __
-// \_ ___ \| |__ __________________ ___.__. \______ \__| ____ | | __
-// / \ \/| | \_/ __ \_ __ \_ __ < | | | ___/ |/ ___\| |/ /
-// \ \___| Y \ ___/| | \/| | \/\___ | | | | \ \___| <
-// \______ /___| /\___ >__| |__| / ____| |____| |__|\___ >__|_ \
-// \/ \/ \/ \/ \/ \/
-
-// CherryPickForm form for changing repository file
-type CherryPickForm struct {
- CommitSummary string `binding:"MaxSize(100)"`
- CommitMessage string
- CommitChoice string `binding:"Required;MaxSize(50)"`
- NewBranchName string `binding:"GitRefName;MaxSize(100)"`
- LastCommit string
- Revert bool
- Signoff bool
- CommitEmail string
-}
-
-// Validate validates the fields
-func (f *CherryPickForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
- ctx := context.GetValidateContext(req)
- return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ____ ___ .__ .___
-// | | \______ | | _________ __| _/
-// | | /\____ \| | / _ \__ \ / __ |
-// | | / | |_> > |_( <_> ) __ \_/ /_/ |
-// |______/ | __/|____/\____(____ /\____ |
-// |__| \/ \/
-//
-
-// UploadRepoFileForm form for uploading repository file
-type UploadRepoFileForm struct {
- TreePath string `binding:"MaxSize(500)"`
- CommitSummary string `binding:"MaxSize(100)"`
- CommitMessage string
- CommitChoice string `binding:"Required;MaxSize(50)"`
- NewBranchName string `binding:"GitRefName;MaxSize(100)"`
- Files []string
- Signoff bool
- CommitEmail string
-}
-
-// Validate validates the fields
-func (f *UploadRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
- ctx := context.GetValidateContext(req)
- return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// RemoveUploadFileForm form for removing uploaded file
-type RemoveUploadFileForm struct {
- File string `binding:"Required;MaxSize(50)"`
-}
-
-// Validate validates the fields
-func (f *RemoveUploadFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
- ctx := context.GetValidateContext(req)
- return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ________ .__ __
-// \______ \ ____ | | _____/ |_ ____
-// | | \_/ __ \| | _/ __ \ __\/ __ \
-// | ` \ ___/| |_\ ___/| | \ ___/
-// /_______ /\___ >____/\___ >__| \___ >
-// \/ \/ \/ \/
-
-// DeleteRepoFileForm form for deleting repository file
-type DeleteRepoFileForm struct {
- CommitSummary string `binding:"MaxSize(100)"`
- CommitMessage string
- CommitChoice string `binding:"Required;MaxSize(50)"`
- NewBranchName string `binding:"GitRefName;MaxSize(100)"`
- LastCommit string
- Signoff bool
- CommitEmail string
-}
-
-// Validate validates the fields
-func (f *DeleteRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
- ctx := context.GetValidateContext(req)
- return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
-}
-
// ___________.__ ___________ __
// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________
// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \
diff --git a/services/forms/repo_form_editor.go b/services/forms/repo_form_editor.go
new file mode 100644
index 0000000000..3ad2eae75d
--- /dev/null
+++ b/services/forms/repo_form_editor.go
@@ -0,0 +1,57 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+type CommitCommonForm struct {
+ TreePath string `binding:"MaxSize(500)"`
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ LastCommit string
+ Signoff bool
+ CommitEmail string
+}
+
+func (f *CommitCommonForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+type CommitCommonFormInterface interface {
+ GetCommitCommonForm() *CommitCommonForm
+}
+
+func (f *CommitCommonForm) GetCommitCommonForm() *CommitCommonForm {
+ return f
+}
+
+type EditRepoFileForm struct {
+ CommitCommonForm
+ Content optional.Option[string]
+}
+
+type DeleteRepoFileForm struct {
+ CommitCommonForm
+}
+
+type UploadRepoFileForm struct {
+ CommitCommonForm
+ Files []string
+}
+
+type CherryPickForm struct {
+ CommitCommonForm
+ Revert bool
+}
diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go
index b4120f20ed..09e9ec0f65 100644
--- a/services/forms/user_form_test.go
+++ b/services/forms/user_form_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"github.com/gobwas/glob"
"github.com/stretchr/testify/assert"
@@ -26,12 +27,7 @@ func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
}
func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
- oldService := setting.Service
- defer func() {
- setting.Service = oldService
- }()
-
- setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io")}
+ defer test.MockVariableValue(&setting.Service.EmailDomainAllowList, []glob.Glob{glob.MustCompile("gitea.io")})()
tt := []struct {
email string
@@ -48,12 +44,7 @@ func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
}
func TestRegisterForm_IsDomainAllowed_AllowedEmail(t *testing.T) {
- oldService := setting.Service
- defer func() {
- setting.Service = oldService
- }()
-
- setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.allow")}
+ defer test.MockVariableValue(&setting.Service.EmailDomainAllowList, []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.allow")})()
tt := []struct {
email string
@@ -76,13 +67,7 @@ func TestRegisterForm_IsDomainAllowed_AllowedEmail(t *testing.T) {
}
func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) {
- oldService := setting.Service
- defer func() {
- setting.Service = oldService
- }()
-
- setting.Service.EmailDomainAllowList = nil
- setting.Service.EmailDomainBlockList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.block")}
+ defer test.MockVariableValue(&setting.Service.EmailDomainBlockList, []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.block")})()
tt := []struct {
email string
diff --git a/services/git/commit.go b/services/git/commit.go
index 3faef76782..2e0e8a5096 100644
--- a/services/git/commit.go
+++ b/services/git/commit.go
@@ -34,9 +34,9 @@ func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository,
}
for _, c := range oldCommits {
- committer, ok := emailUsers[c.Committer.Email]
- if !ok && c.Committer != nil {
- committer = &user_model.User{
+ committerUser := emailUsers.GetByEmail(c.Committer.Email) // FIXME: why ValidateCommitsWithEmails uses "Author", but ParseCommitsWithSignature uses "Committer"?
+ if committerUser == nil {
+ committerUser = &user_model.User{
Name: c.Committer.Name,
Email: c.Committer.Email,
}
@@ -44,7 +44,7 @@ func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository,
signCommit := &asymkey_model.SignCommit{
UserCommit: c,
- Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committer),
+ Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committerUser),
}
isOwnerMemberCollaborator := func(user *user_model.User) (bool, error) {
@@ -84,7 +84,7 @@ func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.Sig
commit := &git_model.SignCommitWithStatuses{
SignCommit: c,
}
- statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.ID.String(), db.ListOptions{})
+ statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.ID.String(), db.ListOptionsAll)
if err != nil {
return nil, err
}
diff --git a/services/gitdiff/csv.go b/services/gitdiff/csv.go
index 8db73c56a3..c10ee14490 100644
--- a/services/gitdiff/csv.go
+++ b/services/gitdiff/csv.go
@@ -134,7 +134,7 @@ func createCsvDiffSingle(reader *csv.Reader, celltype TableDiffCellType) ([]*Tab
return nil, err
}
cells := make([]*TableDiffCell, len(row))
- for j := 0; j < len(row); j++ {
+ for j := range row {
if celltype == TableDiffCellDel {
cells[j] = &TableDiffCell{LeftCell: row[j], Type: celltype}
} else {
@@ -365,11 +365,11 @@ func getColumnMapping(baseCSVReader, headCSVReader *csvReader) ([]int, []int) {
}
// Loops through the baseRow and see if there is a match in the head row
- for i := 0; i < len(baseRow); i++ {
+ for i := range baseRow {
base2HeadColMap[i] = unmappedColumn
baseCell, err := getCell(baseRow, i)
if err == nil {
- for j := 0; j < len(headRow); j++ {
+ for j := range headRow {
if head2BaseColMap[j] == -1 {
headCell, err := getCell(headRow, j)
if err == nil && baseCell == headCell {
@@ -390,7 +390,7 @@ func getColumnMapping(baseCSVReader, headCSVReader *csvReader) ([]int, []int) {
// tryMapColumnsByContent tries to map missing columns by the content of the first lines.
func tryMapColumnsByContent(baseCSVReader *csvReader, base2HeadColMap []int, headCSVReader *csvReader, head2BaseColMap []int) {
- for i := 0; i < len(base2HeadColMap); i++ {
+ for i := range base2HeadColMap {
headStart := 0
for base2HeadColMap[i] == unmappedColumn && headStart < len(head2BaseColMap) {
if head2BaseColMap[headStart] == unmappedColumn {
@@ -424,7 +424,7 @@ func getCell(row []string, column int) (string, error) {
// countUnmappedColumns returns the count of unmapped columns.
func countUnmappedColumns(mapping []int) int {
count := 0
- for i := 0; i < len(mapping); i++ {
+ for i := range mapping {
if mapping[i] == unmappedColumn {
count++
}
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index a859945378..9964329876 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -214,51 +214,6 @@ func (diffSection *DiffSection) GetLine(idx int) *DiffLine {
return diffSection.Lines[idx]
}
-// GetLine gets a specific line by type (add or del) and file line number
-// This algorithm is not quite right.
-// Actually now we have "Match" field, it is always right, so use it instead in new GetLine
-func (diffSection *DiffSection) getLineLegacy(lineType DiffLineType, idx int) *DiffLine { //nolint:unused
- var (
- difference = 0
- addCount = 0
- delCount = 0
- matchDiffLine *DiffLine
- )
-
-LOOP:
- for _, diffLine := range diffSection.Lines {
- switch diffLine.Type {
- case DiffLineAdd:
- addCount++
- case DiffLineDel:
- delCount++
- default:
- if matchDiffLine != nil {
- break LOOP
- }
- difference = diffLine.RightIdx - diffLine.LeftIdx
- addCount = 0
- delCount = 0
- }
-
- switch lineType {
- case DiffLineDel:
- if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference {
- matchDiffLine = diffLine
- }
- case DiffLineAdd:
- if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference {
- matchDiffLine = diffLine
- }
- }
- }
-
- if addCount == delCount {
- return matchDiffLine
- }
- return nil
-}
-
func defaultDiffMatchPatch() *diffmatchpatch.DiffMatchPatch {
dmp := diffmatchpatch.New()
dmp.DiffEditCost = 100
@@ -540,10 +495,7 @@ func ParsePatch(ctx context.Context, maxLines, maxLineCharacters, maxFiles int,
// OK let's set a reasonable buffer size.
// This should be at least the size of maxLineCharacters or 4096 whichever is larger.
- readerSize := maxLineCharacters
- if readerSize < 4096 {
- readerSize = 4096
- }
+ readerSize := max(maxLineCharacters, 4096)
input := bufio.NewReaderSize(reader, readerSize)
line, err := input.ReadString('\n')
@@ -1351,13 +1303,13 @@ func SyncUserSpecificDiff(ctx context.Context, userID int64, pull *issues_model.
latestCommit = pull.HeadBranch // opts.AfterCommitID is preferred because it handles PRs from forks correctly and the branch name doesn't
}
- changedFiles, err := gitRepo.GetFilesChangedBetween(review.CommitSHA, latestCommit)
+ changedFiles, errIgnored := gitRepo.GetFilesChangedBetween(review.CommitSHA, latestCommit)
// There are way too many possible errors.
// Examples are various git errors such as the commit the review was based on was gc'ed and hence doesn't exist anymore as well as unrecoverable errors where we should serve a 500 response
// Due to the current architecture and physical limitation of needing to compare explicit error messages, we can only choose one approach without the code getting ugly
// For SOME of the errors such as the gc'ed commit, it would be best to mark all files as changed
// But as that does not work for all potential errors, we simply mark all files as unchanged and drop the error which always works, even if not as good as possible
- if err != nil {
+ if errIgnored != nil {
log.Error("Could not get changed files between %s and %s for pull request %d in repo with path %s. Assuming no changes. Error: %w", review.CommitSHA, latestCommit, pull.Index, gitRepo.Path, err)
}
@@ -1400,7 +1352,7 @@ outer:
}
}
- return review, err
+ return review, nil
}
// CommentAsDiff returns c.Patch as *Diff
diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go
index 71394b1915..b84530043a 100644
--- a/services/gitdiff/gitdiff_test.go
+++ b/services/gitdiff/gitdiff_test.go
@@ -416,7 +416,7 @@ index 0000000..6bb8f39
`
diffBuilder.WriteString(diff)
- for i := 0; i < 35; i++ {
+ for i := range 35 {
diffBuilder.WriteString("+line" + strconv.Itoa(i) + "\n")
}
diff = diffBuilder.String()
@@ -453,11 +453,11 @@ index 0000000..6bb8f39
diffBuilder.Reset()
diffBuilder.WriteString(diff)
- for i := 0; i < 33; i++ {
+ for i := range 33 {
diffBuilder.WriteString("+line" + strconv.Itoa(i) + "\n")
}
diffBuilder.WriteString("+line33")
- for i := 0; i < 512; i++ {
+ for range 512 {
diffBuilder.WriteString("0123456789ABCDEF")
}
diffBuilder.WriteByte('\n')
diff --git a/services/gitdiff/submodule_test.go b/services/gitdiff/submodule_test.go
index 3047b23103..152c5b7066 100644
--- a/services/gitdiff/submodule_test.go
+++ b/services/gitdiff/submodule_test.go
@@ -203,7 +203,6 @@ index 0000000..68972a9
}
for _, testcase := range tests {
- testcase := testcase
t.Run(testcase.name, func(t *testing.T) {
diff, err := ParsePatch(db.DefaultContext, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff), "")
assert.NoError(t, err)
diff --git a/services/issue/assignee.go b/services/issue/assignee.go
index c7e2495568..ba9c91e0ed 100644
--- a/services/issue/assignee.go
+++ b/services/issue/assignee.go
@@ -54,6 +54,8 @@ func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, do
if err != nil {
return false, nil, err
}
+ issue.AssigneeID = assigneeID
+ issue.Assignee = assignee
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, removed, comment)
@@ -302,7 +304,7 @@ func CanDoerChangeReviewRequests(ctx context.Context, doer *user_model.User, rep
// If the repo's owner is an organization, members of teams with read permission on pull requests can change reviewers
if repo.Owner.IsOrganization() {
- teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
+ teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
log.Error("GetTeamsWithAccessToRepo: %v", err)
return false
diff --git a/services/issue/issue.go b/services/issue/issue.go
index 455a1ec297..2cb5f2801d 100644
--- a/services/issue/issue.go
+++ b/services/issue/issue.go
@@ -190,9 +190,13 @@ func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Reposi
}
// delete entries in database
- if err := deleteIssue(ctx, issue); err != nil {
+ attachmentPaths, err := deleteIssue(ctx, issue)
+ if err != nil {
return err
}
+ for _, attachmentPath := range attachmentPaths {
+ system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", attachmentPath)
+ }
// delete pull request related git data
if issue.IsPull && gitRepo != nil {
@@ -256,45 +260,45 @@ func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[i
}
// deleteIssue deletes the issue
-func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
+func deleteIssue(ctx context.Context, issue *issues_model.Issue) ([]string, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
- return err
+ return nil, err
}
defer committer.Close()
- e := db.GetEngine(ctx)
- if _, err := e.ID(issue.ID).NoAutoCondition().Delete(issue); err != nil {
- return err
+ if _, err := db.GetEngine(ctx).ID(issue.ID).NoAutoCondition().Delete(issue); err != nil {
+ return nil, err
}
// update the total issue numbers
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
- return err
+ return nil, err
}
// if the issue is closed, update the closed issue numbers
if issue.IsClosed {
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
- return err
+ return nil, err
}
}
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
- return fmt.Errorf("error updating counters for milestone id %d: %w",
+ return nil, fmt.Errorf("error updating counters for milestone id %d: %w",
issue.MilestoneID, err)
}
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID, issue.Index); err != nil {
- return err
+ return nil, err
}
// find attachments related to this issue and remove them
- if err := issue.LoadAttributes(ctx); err != nil {
- return err
+ if err := issue.LoadAttachments(ctx); err != nil {
+ return nil, err
}
+ var attachmentPaths []string
for i := range issue.Attachments {
- system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", issue.Attachments[i].RelativePath())
+ attachmentPaths = append(attachmentPaths, issue.Attachments[i].RelativePath())
}
// delete all database data still assigned to this issue
@@ -318,8 +322,68 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
&issues_model.Comment{DependentIssueID: issue.ID},
&issues_model.IssuePin{IssueID: issue.ID},
); err != nil {
+ return nil, err
+ }
+
+ if err := committer.Commit(); err != nil {
+ return nil, err
+ }
+ return attachmentPaths, nil
+}
+
+// DeleteOrphanedIssues delete issues without a repo
+func DeleteOrphanedIssues(ctx context.Context) error {
+ var attachmentPaths []string
+ err := db.WithTx(ctx, func(ctx context.Context) error {
+ repoIDs, err := issues_model.GetOrphanedIssueRepoIDs(ctx)
+ if err != nil {
+ return err
+ }
+ for i := range repoIDs {
+ paths, err := DeleteIssuesByRepoID(ctx, repoIDs[i])
+ if err != nil {
+ return err
+ }
+ attachmentPaths = append(attachmentPaths, paths...)
+ }
+ return nil
+ })
+ if err != nil {
return err
}
- return committer.Commit()
+ // Remove issue attachment files.
+ for i := range attachmentPaths {
+ system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", attachmentPaths[i])
+ }
+ return nil
+}
+
+// DeleteIssuesByRepoID deletes issues by repositories id
+func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths []string, err error) {
+ for {
+ issues := make([]*issues_model.Issue, 0, db.DefaultMaxInSize)
+ if err := db.GetEngine(ctx).
+ Where("repo_id = ?", repoID).
+ OrderBy("id").
+ Limit(db.DefaultMaxInSize).
+ Find(&issues); err != nil {
+ return nil, err
+ }
+
+ if len(issues) == 0 {
+ break
+ }
+
+ for _, issue := range issues {
+ issueAttachPaths, err := deleteIssue(ctx, issue)
+ if err != nil {
+ return nil, fmt.Errorf("deleteIssue [issue_id: %d]: %w", issue.ID, err)
+ }
+
+ attachmentPaths = append(attachmentPaths, issueAttachPaths...)
+ }
+ }
+
+ return attachmentPaths, err
}
diff --git a/services/issue/issue_test.go b/services/issue/issue_test.go
index b3df8191e1..bad0d65d1e 100644
--- a/services/issue/issue_test.go
+++ b/services/issue/issue_test.go
@@ -44,7 +44,7 @@ func TestIssue_DeleteIssue(t *testing.T) {
ID: issueIDs[2],
}
- err = deleteIssue(db.DefaultContext, issue)
+ _, err = deleteIssue(db.DefaultContext, issue)
assert.NoError(t, err)
issueIDs, err = issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1)
assert.NoError(t, err)
@@ -55,7 +55,7 @@ func TestIssue_DeleteIssue(t *testing.T) {
assert.NoError(t, err)
issue, err = issues_model.GetIssueByID(db.DefaultContext, 4)
assert.NoError(t, err)
- err = deleteIssue(db.DefaultContext, issue)
+ _, err = deleteIssue(db.DefaultContext, issue)
assert.NoError(t, err)
assert.Len(t, attachments, 2)
for i := range attachments {
@@ -78,7 +78,7 @@ func TestIssue_DeleteIssue(t *testing.T) {
assert.NoError(t, err)
assert.False(t, left)
- err = deleteIssue(db.DefaultContext, issue2)
+ _, err = deleteIssue(db.DefaultContext, issue2)
assert.NoError(t, err)
left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)
assert.NoError(t, err)
diff --git a/services/issue/status.go b/services/issue/status.go
index e18b891175..f9d7dca841 100644
--- a/services/issue/status.go
+++ b/services/issue/status.go
@@ -24,14 +24,14 @@ func CloseIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model
comment, err := issues_model.CloseIssue(dbCtx, issue, doer)
if err != nil {
if issues_model.IsErrDependenciesLeft(err) {
- if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil {
+ if _, err := issues_model.FinishIssueStopwatch(dbCtx, doer, issue); err != nil {
log.Error("Unable to stop stopwatch for issue[%d]#%d: %v", issue.ID, issue.Index, err)
}
}
return err
}
- if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil {
+ if _, err := issues_model.FinishIssueStopwatch(dbCtx, doer, issue); err != nil {
return err
}
diff --git a/services/lfs/locks.go b/services/lfs/locks.go
index 1d464f4a66..264001f0f9 100644
--- a/services/lfs/locks.go
+++ b/services/lfs/locks.go
@@ -74,10 +74,7 @@ func GetListLockHandler(ctx *context.Context) {
}
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
- cursor := ctx.FormInt("cursor")
- if cursor < 0 {
- cursor = 0
- }
+ cursor := max(ctx.FormInt("cursor"), 0)
limit := ctx.FormInt("limit")
if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
limit = setting.LFS.LocksPagingNum
@@ -239,10 +236,7 @@ func VerifyLockHandler(ctx *context.Context) {
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
- cursor := ctx.FormInt("cursor")
- if cursor < 0 {
- cursor = 0
- }
+ cursor := max(ctx.FormInt("cursor"), 0)
limit := ctx.FormInt("limit")
if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
limit = setting.LFS.LocksPagingNum
diff --git a/services/lfs/server.go b/services/lfs/server.go
index 0a99287ed9..c44cc35e53 100644
--- a/services/lfs/server.go
+++ b/services/lfs/server.go
@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
+ "maps"
"net/http"
"net/url"
"path"
@@ -26,6 +27,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/auth/httpauth"
"code.gitea.io/gitea/modules/json"
lfs_module "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
@@ -202,7 +204,7 @@ func BatchHandler(ctx *context.Context) {
exists, err := contentStore.Exists(p)
if err != nil {
- log.Error("Unable to check if LFS OID[%s] exist. Error: %v", p.Oid, rc.User, rc.Repo, err)
+ log.Error("Unable to check if LFS object with ID '%s' exists for %s/%s. Error: %v", p.Oid, rc.User, rc.Repo, err)
writeStatus(ctx, http.StatusInternalServerError)
return
}
@@ -480,9 +482,7 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa
rep.Actions["upload"] = &lfs_module.Link{Href: rc.UploadLink(pointer), Header: header}
verifyHeader := make(map[string]string)
- for key, value := range header {
- verifyHeader[key] = value
- }
+ maps.Copy(verifyHeader, header)
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
verifyHeader["Accept"] = lfs_module.AcceptHeader
@@ -595,19 +595,11 @@ func parseToken(ctx stdCtx.Context, authorization string, target *repo_model.Rep
if authorization == "" {
return nil, errors.New("no token")
}
-
- parts := strings.SplitN(authorization, " ", 2)
- if len(parts) != 2 {
- return nil, errors.New("no token")
- }
- tokenSHA := parts[1]
- switch strings.ToLower(parts[0]) {
- case "bearer":
- fallthrough
- case "token":
- return handleLFSToken(ctx, tokenSHA, target, mode)
+ parsed, ok := httpauth.ParseAuthorizationHeader(authorization)
+ if !ok || parsed.BearerToken == nil {
+ return nil, errors.New("token not found")
}
- return nil, errors.New("token not found")
+ return handleLFSToken(ctx, parsed.BearerToken.Token, target, mode)
}
func requireAuth(ctx *context.Context) {
diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go
index 7a47cf3876..b15949f352 100644
--- a/services/mailer/mail_test.go
+++ b/services/mailer/mail_test.go
@@ -528,7 +528,7 @@ func TestEmbedBase64Images(t *testing.T) {
require.NoError(t, err)
mailBody := msgs[0].Body
- assert.Regexp(t, `MSG-BEFORE <a[^>]+><img src="data:image/png;base64,iVBORw0KGgo="/></a> MSG-AFTER`, mailBody)
+ assert.Regexp(t, `MSG-BEFORE <a[^>]+><img src="data:image/png;base64,iVBORw0KGgo=".*/></a> MSG-AFTER`, mailBody)
})
t.Run("EmbedInstanceImageSkipExternalImage", func(t *testing.T) {
diff --git a/services/mailer/sender/message_test.go b/services/mailer/sender/message_test.go
index 63d0bc349a..ae153ebf05 100644
--- a/services/mailer/sender/message_test.go
+++ b/services/mailer/sender/message_test.go
@@ -108,9 +108,9 @@ func extractMailHeaderAndContent(t *testing.T, mail string) (map[string]string,
}
content := strings.TrimSpace("boundary=" + parts[1])
- hParts := strings.Split(parts[0], "\n")
+ hParts := strings.SplitSeq(parts[0], "\n")
- for _, hPart := range hParts {
+ for hPart := range hParts {
parts := strings.SplitN(hPart, ":", 2)
hk := strings.TrimSpace(parts[0])
if hk != "" {
diff --git a/services/migrations/codecommit.go b/services/migrations/codecommit.go
index 4b2634ef8a..d08b2e6d4a 100644
--- a/services/migrations/codecommit.go
+++ b/services/migrations/codecommit.go
@@ -155,10 +155,7 @@ func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPag
}
startIndex := (page - 1) * perPage
- endIndex := page * perPage
- if endIndex > len(allPullRequestIDs) {
- endIndex = len(allPullRequestIDs)
- }
+ endIndex := min(page*perPage, len(allPullRequestIDs))
batch := allPullRequestIDs[startIndex:endIndex]
prs := make([]*base.PullRequest, 0, len(batch))
@@ -180,11 +177,15 @@ func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPag
continue
}
target := orig.PullRequestTargets[0]
+ description := ""
+ if orig.Description != nil {
+ description = *orig.Description
+ }
pr := &base.PullRequest{
Number: number,
Title: *orig.Title,
PosterName: c.getUsernameFromARN(*orig.AuthorArn),
- Content: *orig.Description,
+ Content: description,
State: "open",
Created: *orig.CreationDate,
Updated: *orig.LastActivityDate,
@@ -206,6 +207,10 @@ func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPag
pr.State = "closed"
pr.Closed = orig.LastActivityDate
}
+ if pr.Merged {
+ pr.MergeCommitSHA = *target.MergeMetadata.MergeCommitId
+ pr.MergedTime = orig.LastActivityDate
+ }
_ = CheckAndEnsureSafePR(pr, c.baseURL, c)
prs = append(prs, pr)
diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go
index b6caa494c6..737bff24d0 100644
--- a/services/migrations/gitea_uploader.go
+++ b/services/migrations/gitea_uploader.go
@@ -148,7 +148,7 @@ func (g *GiteaLocalUploader) CreateRepo(ctx context.Context, repo *base.Reposito
return err
}
g.repo.ObjectFormatName = objectFormat.Name()
- return repo_model.UpdateRepositoryCols(ctx, g.repo, "object_format_name")
+ return repo_model.UpdateRepositoryColsNoAutoTime(ctx, g.repo, "object_format_name")
}
// Close closes this uploader
@@ -765,7 +765,7 @@ func (g *GiteaLocalUploader) newPullRequest(ctx context.Context, pr *base.PullRe
issue := issues_model.Issue{
RepoID: g.repo.ID,
Repo: g.repo,
- Title: prTitle,
+ Title: util.TruncateRunes(prTitle, 255),
Index: pr.Number,
Content: pr.Content,
MilestoneID: milestoneID,
@@ -975,7 +975,7 @@ func (g *GiteaLocalUploader) Finish(ctx context.Context) error {
}
g.repo.Status = repo_model.RepositoryReady
- return repo_model.UpdateRepositoryCols(ctx, g.repo, "status")
+ return repo_model.UpdateRepositoryColsWithAutoTime(ctx, g.repo, "status")
}
func (g *GiteaLocalUploader) remapUser(ctx context.Context, source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error {
diff --git a/services/migrations/github.go b/services/migrations/github.go
index 662908756a..2ce11615c6 100644
--- a/services/migrations/github.go
+++ b/services/migrations/github.go
@@ -89,8 +89,8 @@ func NewGithubDownloaderV3(_ context.Context, baseURL, userName, password, token
}
if token != "" {
- tokens := strings.Split(token, ",")
- for _, token := range tokens {
+ tokens := strings.SplitSeq(token, ",")
+ for token := range tokens {
token = strings.TrimSpace(token)
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go
index 961abe16f4..eba9c79df5 100644
--- a/services/migrations/migrate.go
+++ b/services/migrations/migrate.go
@@ -75,11 +75,9 @@ func IsMigrateURLAllowed(remoteURL string, doer *user_model.User) error {
return &git.ErrInvalidCloneAddr{Host: u.Host, IsProtocolInvalid: true, IsPermissionDenied: true, IsURLError: true}
}
- hostName, _, err := net.SplitHostPort(u.Host)
- if err != nil {
- // u.Host can be "host" or "host:port"
- err = nil //nolint
- hostName = u.Host
+ hostName, _, errIgnored := net.SplitHostPort(u.Host)
+ if errIgnored != nil {
+ hostName = u.Host // u.Host can be "host" or "host:port"
}
// some users only use proxy, there is no DNS resolver. it's safe to ignore the LookupIP error
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index c43a4ef04a..cb90af5894 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -71,7 +71,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
// erase authentication before storing in database
u.User = nil
m.Repo.OriginalURL = u.String()
- return repo_model.UpdateRepositoryCols(ctx, m.Repo, "original_url")
+ return repo_model.UpdateRepositoryColsNoAutoTime(ctx, m.Repo, "original_url")
}
// mirrorSyncResult contains information of a updated reference.
@@ -653,7 +653,7 @@ func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, re
}
m.Repo.IsEmpty = false
// Update the is empty and default_branch columns
- if err := repo_model.UpdateRepositoryCols(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
+ if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err)
desc := fmt.Sprintf("Failed to update default branch of repository '%s': %v", m.Repo.RepoPath(), err)
if err = system_model.CreateRepositoryNotice(desc); err != nil {
diff --git a/services/notify/notifier.go b/services/notify/notifier.go
index 40428454be..875a70e564 100644
--- a/services/notify/notifier.go
+++ b/services/notify/notifier.go
@@ -79,5 +79,7 @@ type Notifier interface {
CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus)
+ WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun)
+
WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask)
}
diff --git a/services/notify/notify.go b/services/notify/notify.go
index 9f8be4b577..2416cbd2e0 100644
--- a/services/notify/notify.go
+++ b/services/notify/notify.go
@@ -46,10 +46,25 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
}
}
+func shouldSendCommentChangeNotification(ctx context.Context, comment *issues_model.Comment) bool {
+ if err := comment.LoadReview(ctx); err != nil {
+ log.Error("LoadReview: %v", err)
+ return false
+ } else if comment.Review != nil && comment.Review.Type == issues_model.ReviewTypePending {
+ // Pending review comments updating should not triggered
+ return false
+ }
+ return true
+}
+
// CreateIssueComment notifies issue comment related message to notifiers
func CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository,
issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
) {
+ if !shouldSendCommentChangeNotification(ctx, comment) {
+ return
+ }
+
for _, notifier := range notifiers {
notifier.CreateIssueComment(ctx, doer, repo, issue, comment, mentions)
}
@@ -156,6 +171,10 @@ func PullReviewDismiss(ctx context.Context, doer *user_model.User, review *issue
// UpdateComment notifies update comment to notifiers
func UpdateComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment, oldContent string) {
+ if !shouldSendCommentChangeNotification(ctx, c) {
+ return
+ }
+
for _, notifier := range notifiers {
notifier.UpdateComment(ctx, doer, c, oldContent)
}
@@ -163,6 +182,10 @@ func UpdateComment(ctx context.Context, doer *user_model.User, c *issues_model.C
// DeleteComment notifies delete comment to notifiers
func DeleteComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment) {
+ if !shouldSendCommentChangeNotification(ctx, c) {
+ return
+ }
+
for _, notifier := range notifiers {
notifier.DeleteComment(ctx, doer, c)
}
@@ -376,6 +399,12 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit
}
}
+func WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
+ for _, notifier := range notifiers {
+ notifier.WorkflowRunStatusUpdate(ctx, repo, sender, run)
+ }
+}
+
func WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask) {
for _, notifier := range notifiers {
notifier.WorkflowJobStatusUpdate(ctx, repo, sender, job, task)
diff --git a/services/notify/null.go b/services/notify/null.go
index 9c794a2342..c3085d7c9e 100644
--- a/services/notify/null.go
+++ b/services/notify/null.go
@@ -214,5 +214,8 @@ func (*NullNotifier) ChangeDefaultBranch(ctx context.Context, repo *repo_model.R
func (*NullNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
}
+func (*NullNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
+}
+
func (*NullNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask) {
}
diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go
index 52a73c9572..e01777031b 100644
--- a/services/oauth2_provider/access_token.go
+++ b/services/oauth2_provider/access_token.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package oauth2_provider //nolint
+package oauth2_provider
import (
"context"
@@ -16,7 +16,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
"github.com/golang-jwt/jwt/v5"
)
@@ -83,7 +85,7 @@ func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope {
}
var accessScopes []string // the scopes for access control, but not for general information
- for _, scope := range strings.Split(grantScopes, " ") {
+ for scope := range strings.SplitSeq(grantScopes, " ") {
if scope != "" && !slices.Contains(generalScopesSupported, scope) {
accessScopes = append(accessScopes, scope)
}
@@ -231,12 +233,11 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
}, nil
}
-// returns a list of "org" and "org:team" strings,
-// that the given user is a part of.
+// GetOAuthGroupsForUser returns a list of "org" and "org:team" strings, that the given user is a part of.
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) {
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
- UserID: user.ID,
- IncludePrivate: !onlyPublicGroups,
+ UserID: user.ID,
+ IncludeVisibility: util.Iif(onlyPublicGroups, api.VisibleTypePublic, api.VisibleTypePrivate),
})
if err != nil {
return nil, fmt.Errorf("GetUserOrgList: %w", err)
diff --git a/services/oauth2_provider/additional_scopes_test.go b/services/oauth2_provider/additional_scopes_test.go
index 2d4df7aea2..5f375346dc 100644
--- a/services/oauth2_provider/additional_scopes_test.go
+++ b/services/oauth2_provider/additional_scopes_test.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package oauth2_provider //nolint
+package oauth2_provider
import (
"testing"
diff --git a/services/oauth2_provider/init.go b/services/oauth2_provider/init.go
index e5958099a6..c412bd6433 100644
--- a/services/oauth2_provider/init.go
+++ b/services/oauth2_provider/init.go
@@ -1,7 +1,7 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package oauth2_provider //nolint
+package oauth2_provider
import (
"context"
diff --git a/services/oauth2_provider/jwtsigningkey.go b/services/oauth2_provider/jwtsigningkey.go
index 3bc4f49410..03c7403f75 100644
--- a/services/oauth2_provider/jwtsigningkey.go
+++ b/services/oauth2_provider/jwtsigningkey.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package oauth2_provider //nolint
+package oauth2_provider
import (
"crypto/ecdsa"
diff --git a/services/oauth2_provider/token.go b/services/oauth2_provider/token.go
index 383bcdb3eb..935c4cc01f 100644
--- a/services/oauth2_provider/token.go
+++ b/services/oauth2_provider/token.go
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package oauth2_provider //nolint
+package oauth2_provider
import (
"errors"
diff --git a/services/org/team_test.go b/services/org/team_test.go
index aa39771cd2..c1a69d8ee7 100644
--- a/services/org/team_test.go
+++ b/services/org/team_test.go
@@ -204,7 +204,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
// Create repos.
repoIDs := make([]int64, 0)
- for i := 0; i < 3; i++ {
+ for i := range 3 {
r, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(),
repo_service.CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}, true)
assert.NoError(t, err, "CreateRepository %d", i)
@@ -281,7 +281,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
}
// Remove repo and check teams repositories.
- assert.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repoIDs[0]), "DeleteRepository")
+ assert.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, repoIDs[0]), "DeleteRepository")
teamRepos[0] = repoIDs[1:]
teamRepos[1] = repoIDs[1:]
teamRepos[3] = repoIDs[1:3]
@@ -293,7 +293,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
// Wipe created items.
for i, rid := range repoIDs {
if i > 0 { // first repo already deleted.
- assert.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, rid), "DeleteRepository %d", i)
+ assert.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, rid), "DeleteRepository %d", i)
}
}
assert.NoError(t, DeleteOrganization(db.DefaultContext, org, false), "DeleteOrganization")
diff --git a/services/org/user.go b/services/org/user.go
index 3565ecc2fc..26927253d2 100644
--- a/services/org/user.go
+++ b/services/org/user.go
@@ -64,10 +64,11 @@ func RemoveOrgUser(ctx context.Context, org *organization.Organization, user *us
if err != nil {
return fmt.Errorf("AccessibleReposEnv: %w", err)
}
- repoIDs, err := env.RepoIDs(ctx, 1, org.NumRepos)
+ repoIDs, err := env.RepoIDs(ctx)
if err != nil {
return fmt.Errorf("GetUserRepositories [%d]: %w", user.ID, err)
}
+
for _, repoID := range repoIDs {
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
if err != nil {
diff --git a/services/packages/arch/vercmp.go b/services/packages/arch/vercmp.go
index 0d33dda0f1..d44aa530f0 100644
--- a/services/packages/arch/vercmp.go
+++ b/services/packages/arch/vercmp.go
@@ -34,13 +34,8 @@ func parseEVR(evr string) (epoch, version, release string) {
func compareSegments(a, b []string) int {
lenA, lenB := len(a), len(b)
- var l int
- if lenA > lenB {
- l = lenB
- } else {
- l = lenA
- }
- for i := 0; i < l; i++ {
+ l := min(lenA, lenB)
+ for i := range l {
if r := compare(a[i], b[i]); r != 0 {
return r
}
diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go
index e8a2f189c8..605335d0f1 100644
--- a/services/packages/cargo/index.go
+++ b/services/packages/cargo/index.go
@@ -310,7 +310,7 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re
}
func writeObjectToIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, path string, r io.Reader) error {
- hash, err := t.HashObject(ctx, r)
+ hash, err := t.HashObjectAndWrite(ctx, r)
if err != nil {
return err
}
diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go
index b7ba2b6ac4..959babe7cd 100644
--- a/services/packages/cleanup/cleanup.go
+++ b/services/packages/cleanup/cleanup.go
@@ -32,127 +32,136 @@ func CleanupTask(ctx context.Context, olderThan time.Duration) error {
return CleanupExpiredData(ctx, olderThan)
}
-func ExecuteCleanupRules(outerCtx context.Context) error {
- ctx, committer, err := db.TxContext(outerCtx)
+func executeCleanupOneRulePackage(ctx context.Context, pcr *packages_model.PackageCleanupRule, p *packages_model.Package) (versionDeleted bool, err error) {
+ olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays)
+ pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
+ PackageID: p.ID,
+ IsInternal: optional.Some(false),
+ Sort: packages_model.SortCreatedDesc,
+ })
if err != nil {
- return err
+ return false, fmt.Errorf("CleanupRule [%d]: SearchVersions failed: %w", pcr.ID, err)
}
- defer committer.Close()
-
- err = packages_model.IterateEnabledCleanupRules(ctx, func(ctx context.Context, pcr *packages_model.PackageCleanupRule) error {
- select {
- case <-outerCtx.Done():
- return db.ErrCancelledf("While processing package cleanup rules")
- default:
+ if pcr.KeepCount > 0 {
+ if pcr.KeepCount < len(pvs) {
+ pvs = pvs[pcr.KeepCount:]
+ } else {
+ pvs = nil
}
-
- if err := pcr.CompiledPattern(); err != nil {
- return fmt.Errorf("CleanupRule [%d]: CompilePattern failed: %w", pcr.ID, err)
+ }
+ for _, pv := range pvs {
+ if pcr.Type == packages_model.TypeContainer {
+ if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil {
+ return false, fmt.Errorf("CleanupRule [%d]: container.ShouldBeSkipped failed: %w", pcr.ID, err)
+ } else if skip {
+ log.Debug("Rule[%d]: keep '%s/%s' (container)", pcr.ID, p.Name, pv.Version)
+ continue
+ }
}
-
- olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays)
-
- packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type)
- if err != nil {
- return fmt.Errorf("CleanupRule [%d]: GetPackagesByType failed: %w", pcr.ID, err)
+ toMatch := pv.LowerVersion
+ if pcr.MatchFullName {
+ toMatch = p.LowerName + "/" + pv.LowerVersion
+ }
+ if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) {
+ log.Debug("Rule[%d]: keep '%s/%s' (keep pattern)", pcr.ID, p.Name, pv.Version)
+ continue
+ }
+ if pv.CreatedUnix.AsLocalTime().After(olderThan) {
+ log.Debug("Rule[%d]: keep '%s/%s' (remove days) %v", pcr.ID, p.Name, pv.Version, pv.CreatedUnix.FormatDate())
+ continue
}
+ if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) {
+ log.Debug("Rule[%d]: keep '%s/%s' (remove pattern)", pcr.ID, p.Name, pv.Version)
+ continue
+ }
+ log.Debug("Rule[%d]: remove '%s/%s'", pcr.ID, p.Name, pv.Version)
+ if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
+ log.Error("CleanupRule [%d]: DeletePackageVersionAndReferences failed: %v", pcr.ID, err)
+ continue
+ }
+ versionDeleted = true
+ }
+ return versionDeleted, nil
+}
- anyVersionDeleted := false
- for _, p := range packages {
- pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
- PackageID: p.ID,
- IsInternal: optional.Some(false),
- Sort: packages_model.SortCreatedDesc,
- Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
- })
- if err != nil {
- return fmt.Errorf("CleanupRule [%d]: SearchVersions failed: %w", pcr.ID, err)
- }
- versionDeleted := false
- for _, pv := range pvs {
- if pcr.Type == packages_model.TypeContainer {
- if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil {
- return fmt.Errorf("CleanupRule [%d]: container.ShouldBeSkipped failed: %w", pcr.ID, err)
- } else if skip {
- log.Debug("Rule[%d]: keep '%s/%s' (container)", pcr.ID, p.Name, pv.Version)
- continue
- }
- }
+func executeCleanupOneRule(ctx context.Context, pcr *packages_model.PackageCleanupRule) error {
+ if err := pcr.CompiledPattern(); err != nil {
+ return fmt.Errorf("CleanupRule [%d]: CompilePattern failed: %w", pcr.ID, err)
+ }
- toMatch := pv.LowerVersion
- if pcr.MatchFullName {
- toMatch = p.LowerName + "/" + pv.LowerVersion
- }
+ packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type)
+ if err != nil {
+ return fmt.Errorf("CleanupRule [%d]: GetPackagesByType failed: %w", pcr.ID, err)
+ }
- if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) {
- log.Debug("Rule[%d]: keep '%s/%s' (keep pattern)", pcr.ID, p.Name, pv.Version)
- continue
- }
- if pv.CreatedUnix.AsLocalTime().After(olderThan) {
- log.Debug("Rule[%d]: keep '%s/%s' (remove days)", pcr.ID, p.Name, pv.Version)
- continue
- }
- if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) {
- log.Debug("Rule[%d]: keep '%s/%s' (remove pattern)", pcr.ID, p.Name, pv.Version)
- continue
+ anyVersionDeleted := false
+ for _, p := range packages {
+ versionDeleted := false
+ err = db.WithTx(ctx, func(ctx context.Context) (err error) {
+ versionDeleted, err = executeCleanupOneRulePackage(ctx, pcr, p)
+ return err
+ })
+ if err != nil {
+ log.Error("CleanupRule [%d]: executeCleanupOneRulePackage(%d) failed: %v", pcr.ID, p.ID, err)
+ continue
+ }
+ anyVersionDeleted = anyVersionDeleted || versionDeleted
+ if versionDeleted {
+ if pcr.Type == packages_model.TypeCargo {
+ owner, err := user_model.GetUserByID(ctx, pcr.OwnerID)
+ if err != nil {
+ return fmt.Errorf("GetUserByID failed: %w", err)
}
-
- log.Debug("Rule[%d]: remove '%s/%s'", pcr.ID, p.Name, pv.Version)
-
- if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
- return fmt.Errorf("CleanupRule [%d]: DeletePackageVersionAndReferences failed: %w", pcr.ID, err)
+ if err := cargo_service.UpdatePackageIndexIfExists(ctx, owner, owner, p.ID); err != nil {
+ return fmt.Errorf("CleanupRule [%d]: cargo.UpdatePackageIndexIfExists failed: %w", pcr.ID, err)
}
+ }
+ }
+ }
- versionDeleted = true
- anyVersionDeleted = true
+ if anyVersionDeleted {
+ switch pcr.Type {
+ case packages_model.TypeDebian:
+ if err := debian_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
+ return fmt.Errorf("CleanupRule [%d]: debian.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
+ }
+ case packages_model.TypeAlpine:
+ if err := alpine_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
+ return fmt.Errorf("CleanupRule [%d]: alpine.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
}
+ case packages_model.TypeRpm:
+ if err := rpm_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
+ return fmt.Errorf("CleanupRule [%d]: rpm.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
+ }
+ case packages_model.TypeArch:
+ release, err := arch_service.AquireRegistryLock(ctx, pcr.OwnerID)
+ if err != nil {
+ return err
+ }
+ defer release()
- if versionDeleted {
- if pcr.Type == packages_model.TypeCargo {
- owner, err := user_model.GetUserByID(ctx, pcr.OwnerID)
- if err != nil {
- return fmt.Errorf("GetUserByID failed: %w", err)
- }
- if err := cargo_service.UpdatePackageIndexIfExists(ctx, owner, owner, p.ID); err != nil {
- return fmt.Errorf("CleanupRule [%d]: cargo.UpdatePackageIndexIfExists failed: %w", pcr.ID, err)
- }
- }
+ if err := arch_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
+ return fmt.Errorf("CleanupRule [%d]: arch.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
}
}
+ }
+ return nil
+}
- if anyVersionDeleted {
- switch pcr.Type {
- case packages_model.TypeDebian:
- if err := debian_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
- return fmt.Errorf("CleanupRule [%d]: debian.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
- }
- case packages_model.TypeAlpine:
- if err := alpine_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
- return fmt.Errorf("CleanupRule [%d]: alpine.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
- }
- case packages_model.TypeRpm:
- if err := rpm_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
- return fmt.Errorf("CleanupRule [%d]: rpm.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
- }
- case packages_model.TypeArch:
- release, err := arch_service.AquireRegistryLock(ctx, pcr.OwnerID)
- if err != nil {
- return err
- }
- defer release()
+func ExecuteCleanupRules(ctx context.Context) error {
+ return packages_model.IterateEnabledCleanupRules(ctx, func(ctx context.Context, pcr *packages_model.PackageCleanupRule) error {
+ select {
+ case <-ctx.Done():
+ return db.ErrCancelledf("While processing package cleanup rules")
+ default:
+ }
- if err := arch_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil {
- return fmt.Errorf("CleanupRule [%d]: arch.BuildAllRepositoryFiles failed: %w", pcr.ID, err)
- }
- }
+ err := executeCleanupOneRule(ctx, pcr)
+ if err != nil {
+ log.Error("CleanupRule [%d]: executeCleanupOneRule failed: %v", pcr.ID, err)
}
return nil
})
- if err != nil {
- return err
- }
-
- return committer.Commit()
}
func CleanupExpiredData(outerCtx context.Context, olderThan time.Duration) error {
diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go
index 3f5f43bbc0..263562a396 100644
--- a/services/packages/container/cleanup.go
+++ b/services/packages/container/cleanup.go
@@ -13,7 +13,7 @@ import (
container_module "code.gitea.io/gitea/modules/packages/container"
packages_service "code.gitea.io/gitea/services/packages"
- digest "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/go-digest"
)
// Cleanup removes expired container data
@@ -57,7 +57,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
Type: packages_model.TypeContainer,
Version: packages_model.SearchValue{
ExactMatch: true,
- Value: container_model.UploadVersion,
+ Value: container_module.UploadVersion,
},
IsInternal: optional.Some(true),
HasFiles: optional.Some(false),
diff --git a/services/packages/container/common.go b/services/packages/container/common.go
index 5a14ed5b7a..02cbff2286 100644
--- a/services/packages/container/common.go
+++ b/services/packages/container/common.go
@@ -5,11 +5,17 @@ package container
import (
"context"
+ "io"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
+ container_service "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/packages"
container_module "code.gitea.io/gitea/modules/packages/container"
+
+ "github.com/opencontainers/image-spec/specs-go/v1"
)
// UpdateRepositoryNames updates the repository name property for all packages of the specific owner
@@ -22,7 +28,7 @@ func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwner
newOwnerName = strings.ToLower(newOwnerName)
for _, p := range ps {
- if err := packages_model.DeletePropertyByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil {
+ if err := packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil {
return err
}
@@ -33,3 +39,26 @@ func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwner
return nil
}
+
+func ParseManifestMetadata(ctx context.Context, rd io.Reader, ownerID int64, imageName string) (*v1.Manifest, *packages_model.PackageFileDescriptor, *container_module.Metadata, error) {
+ var manifest v1.Manifest
+ if err := json.NewDecoder(rd).Decode(&manifest); err != nil {
+ return nil, nil, nil, err
+ }
+ configDescriptor, err := container_service.GetContainerBlob(ctx, &container_service.BlobSearchOptions{
+ OwnerID: ownerID,
+ Image: imageName,
+ Digest: manifest.Config.Digest.String(),
+ })
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ configReader, err := packages.NewContentStore().OpenBlob(packages.BlobHash256Key(configDescriptor.Blob.HashSHA256))
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ defer configReader.Close()
+ metadata, err := container_module.ParseImageConfig(manifest.Config.MediaType, configReader)
+ return &manifest, configDescriptor, metadata, err
+}
diff --git a/services/packages/packages.go b/services/packages/packages.go
index bd1d460fd3..517334cbc7 100644
--- a/services/packages/packages.go
+++ b/services/packages/packages.go
@@ -563,8 +563,8 @@ func DeletePackageFile(ctx context.Context, pf *packages_model.PackageFile) erro
return packages_model.DeleteFileByID(ctx, pf.ID)
}
-// GetFileStreamByPackageNameAndVersion returns the content of the specific package file
-func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
+// OpenFileForDownloadByPackageNameAndVersion returns the content of the specific package file and increases the download counter.
+func OpenFileForDownloadByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
log.Trace("Getting package file stream: %v, %v, %s, %s, %s, %s", pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version, pfi.Filename, pfi.CompositeKey)
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version)
@@ -576,32 +576,38 @@ func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo,
return nil, nil, nil, err
}
- return GetFileStreamByPackageVersion(ctx, pv, pfi)
+ return OpenFileForDownloadByPackageVersion(ctx, pv, pfi)
}
-// GetFileStreamByPackageVersion returns the content of the specific package file
-func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
+// OpenFileForDownloadByPackageVersion returns the content of the specific package file and increases the download counter.
+func OpenFileForDownloadByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, pfi.Filename, pfi.CompositeKey)
if err != nil {
return nil, nil, nil, err
}
- return GetPackageFileStream(ctx, pf)
+ return OpenFileForDownload(ctx, pf)
}
-// GetPackageFileStream returns the content of the specific package file
-func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
+// OpenFileForDownload returns the content of the specific package file and increases the download counter.
+func OpenFileForDownload(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
pb, err := packages_model.GetBlobByID(ctx, pf.BlobID)
if err != nil {
return nil, nil, nil, err
}
- return GetPackageBlobStream(ctx, pf, pb, nil)
+ return OpenBlobForDownload(ctx, pf, pb, nil)
}
-// GetPackageBlobStream returns the content of the specific package blob
+func OpenBlobStream(pb *packages_model.PackageBlob) (io.ReadSeekCloser, error) {
+ cs := packages_module.NewContentStore()
+ key := packages_module.BlobHash256Key(pb.HashSHA256)
+ return cs.OpenBlob(key)
+}
+
+// OpenBlobForDownload returns the content of the specific package blob and increases the download counter.
// If the storage supports direct serving and it's enabled, only the direct serving url is returned.
-func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
+func OpenBlobForDownload(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
key := packages_module.BlobHash256Key(pb.HashSHA256)
cs := packages_module.NewContentStore()
@@ -617,7 +623,7 @@ func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, p
}
}
if u == nil {
- s, err = cs.Get(key)
+ s, err = cs.OpenBlob(key)
}
if err == nil {
diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go
index 5027021c52..fbbf8d7dad 100644
--- a/services/packages/rpm/repository.go
+++ b/services/packages/rpm/repository.go
@@ -470,7 +470,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml
-func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl
+func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl // duplicates with buildOther
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
@@ -517,7 +517,7 @@ func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml
-func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl
+func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (*repoData, error) { //nolint:dupl // duplicates with buildFilelists
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go
index 0bfff21746..7952ca6fe3 100644
--- a/services/pull/commit_status.go
+++ b/services/pull/commit_status.go
@@ -10,93 +10,59 @@ import (
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/structs"
"github.com/gobwas/glob"
"github.com/pkg/errors"
)
// MergeRequiredContextsCommitStatus returns a commit status state for given required contexts
-func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) structs.CommitStatusState {
- // matchedCount is the number of `CommitStatus.Context` that match any context of `requiredContexts`
- matchedCount := 0
- returnedStatus := structs.CommitStatusSuccess
-
- if len(requiredContexts) > 0 {
- requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts))
- for _, ctx := range requiredContexts {
- if gp, err := glob.Compile(ctx); err != nil {
- log.Error("glob.Compile %s failed. Error: %v", ctx, err)
- } else {
- requiredContextsGlob[ctx] = gp
- }
- }
-
- for _, gp := range requiredContextsGlob {
- var targetStatus structs.CommitStatusState
- for _, commitStatus := range commitStatuses {
- if gp.Match(commitStatus.Context) {
- targetStatus = commitStatus.State
- matchedCount++
- break
- }
- }
-
- // If required rule not match any action, then it is pending
- if targetStatus == "" {
- if structs.CommitStatusPending.NoBetterThan(returnedStatus) {
- returnedStatus = structs.CommitStatusPending
- }
- break
- }
-
- if targetStatus.NoBetterThan(returnedStatus) {
- returnedStatus = targetStatus
- }
- }
+func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) commitstatus.CommitStatusState {
+ if len(commitStatuses) == 0 {
+ return commitstatus.CommitStatusPending
}
- if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
- status := git_model.CalcCommitStatus(commitStatuses)
- if status != nil {
- return status.State
- }
- return structs.CommitStatusSuccess
+ if len(requiredContexts) == 0 {
+ return git_model.CalcCommitStatus(commitStatuses).State
}
- return returnedStatus
-}
-
-// IsCommitStatusContextSuccess returns true if all required status check contexts succeed.
-func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requiredContexts []string) bool {
- // If no specific context is required, require that last commit status is a success
- if len(requiredContexts) == 0 {
- status := git_model.CalcCommitStatus(commitStatuses)
- if status == nil || status.State != structs.CommitStatusSuccess {
- return false
+ requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts))
+ for _, ctx := range requiredContexts {
+ if gp, err := glob.Compile(ctx); err != nil {
+ log.Error("glob.Compile %s failed. Error: %v", ctx, err)
+ } else {
+ requiredContextsGlob[ctx] = gp
}
- return true
}
- for _, ctx := range requiredContexts {
- var found bool
+ requiredCommitStatuses := make([]*git_model.CommitStatus, 0, len(commitStatuses))
+ allRequiredContextsMatched := true
+ for _, gp := range requiredContextsGlob {
+ requiredContextMatched := false
for _, commitStatus := range commitStatuses {
- if commitStatus.Context == ctx {
- if commitStatus.State != structs.CommitStatusSuccess {
- return false
- }
-
- found = true
- break
+ if gp.Match(commitStatus.Context) {
+ requiredCommitStatuses = append(requiredCommitStatuses, commitStatus)
+ requiredContextMatched = true
}
}
- if !found {
- return false
- }
+ allRequiredContextsMatched = allRequiredContextsMatched && requiredContextMatched
+ }
+ if len(requiredCommitStatuses) == 0 {
+ return commitstatus.CommitStatusPending
+ }
+
+ returnedStatus := git_model.CalcCommitStatus(requiredCommitStatuses).State
+ if allRequiredContextsMatched {
+ return returnedStatus
+ }
+
+ if returnedStatus == commitstatus.CommitStatusFailure {
+ return commitstatus.CommitStatusFailure
}
- return true
+ // even if part of success, return pending
+ return commitstatus.CommitStatusPending
}
// IsPullCommitStatusPass returns if all required status checks PASS
@@ -117,7 +83,7 @@ func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (
}
// GetPullRequestCommitStatusState returns pull request merged commit status state
-func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (structs.CommitStatusState, error) {
+func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (commitstatus.CommitStatusState, error) {
// Ensure HeadRepo is loaded
if err := pr.LoadHeadRepo(ctx); err != nil {
return "", errors.Wrap(err, "LoadHeadRepo")
@@ -151,7 +117,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
return "", errors.Wrap(err, "LoadBaseRepo")
}
- commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
+ commitStatuses, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
if err != nil {
return "", errors.Wrap(err, "GetLatestCommitStatus")
}
diff --git a/services/pull/commit_status_test.go b/services/pull/commit_status_test.go
index 592acdd55c..a58e788c04 100644
--- a/services/pull/commit_status_test.go
+++ b/services/pull/commit_status_test.go
@@ -8,58 +8,85 @@ import (
"testing"
git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/commitstatus"
"github.com/stretchr/testify/assert"
)
func TestMergeRequiredContextsCommitStatus(t *testing.T) {
- testCases := [][]*git_model.CommitStatus{
+ cases := []struct {
+ commitStatuses []*git_model.CommitStatus
+ requiredContexts []string
+ expected commitstatus.CommitStatusState
+ }{
{
- {Context: "Build 1", State: structs.CommitStatusSuccess},
- {Context: "Build 2", State: structs.CommitStatusSuccess},
- {Context: "Build 3", State: structs.CommitStatusSuccess},
+ commitStatuses: []*git_model.CommitStatus{},
+ requiredContexts: []string{},
+ expected: commitstatus.CommitStatusPending,
},
{
- {Context: "Build 1", State: structs.CommitStatusSuccess},
- {Context: "Build 2", State: structs.CommitStatusSuccess},
- {Context: "Build 2t", State: structs.CommitStatusPending},
+ commitStatuses: []*git_model.CommitStatus{
+ {Context: "Build xxx", State: commitstatus.CommitStatusSkipped},
+ },
+ requiredContexts: []string{"Build*"},
+ expected: commitstatus.CommitStatusSuccess,
},
{
- {Context: "Build 1", State: structs.CommitStatusSuccess},
- {Context: "Build 2", State: structs.CommitStatusSuccess},
- {Context: "Build 2t", State: structs.CommitStatusFailure},
+ commitStatuses: []*git_model.CommitStatus{
+ {Context: "Build 1", State: commitstatus.CommitStatusSkipped},
+ {Context: "Build 2", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 3", State: commitstatus.CommitStatusSuccess},
+ },
+ requiredContexts: []string{"Build*"},
+ expected: commitstatus.CommitStatusSuccess,
},
{
- {Context: "Build 1", State: structs.CommitStatusSuccess},
- {Context: "Build 2", State: structs.CommitStatusSuccess},
- {Context: "Build 2t", State: structs.CommitStatusSuccess},
+ commitStatuses: []*git_model.CommitStatus{
+ {Context: "Build 1", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2t", State: commitstatus.CommitStatusPending},
+ },
+ requiredContexts: []string{"Build*", "Build 2t*"},
+ expected: commitstatus.CommitStatusPending,
},
{
- {Context: "Build 1", State: structs.CommitStatusSuccess},
- {Context: "Build 2", State: structs.CommitStatusSuccess},
- {Context: "Build 2t", State: structs.CommitStatusSuccess},
+ commitStatuses: []*git_model.CommitStatus{
+ {Context: "Build 1", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2t", State: commitstatus.CommitStatusFailure},
+ },
+ requiredContexts: []string{"Build*", "Build 2t*"},
+ expected: commitstatus.CommitStatusFailure,
+ },
+ {
+ commitStatuses: []*git_model.CommitStatus{
+ {Context: "Build 1", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2t", State: commitstatus.CommitStatusFailure},
+ },
+ requiredContexts: []string{"Build*"},
+ expected: commitstatus.CommitStatusFailure,
+ },
+ {
+ commitStatuses: []*git_model.CommitStatus{
+ {Context: "Build 1", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2t", State: commitstatus.CommitStatusSuccess},
+ },
+ requiredContexts: []string{"Build*", "Build 2t*", "Build 3*"},
+ expected: commitstatus.CommitStatusPending,
+ },
+ {
+ commitStatuses: []*git_model.CommitStatus{
+ {Context: "Build 1", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2", State: commitstatus.CommitStatusSuccess},
+ {Context: "Build 2t", State: commitstatus.CommitStatusSuccess},
+ },
+ requiredContexts: []string{"Build*", "Build *", "Build 2t*", "Build 1*"},
+ expected: commitstatus.CommitStatusSuccess,
},
}
- testCasesRequiredContexts := [][]string{
- {"Build*"},
- {"Build*", "Build 2t*"},
- {"Build*", "Build 2t*"},
- {"Build*", "Build 2t*", "Build 3*"},
- {"Build*", "Build *", "Build 2t*", "Build 1*"},
- }
-
- testCasesExpected := []structs.CommitStatusState{
- structs.CommitStatusSuccess,
- structs.CommitStatusPending,
- structs.CommitStatusFailure,
- structs.CommitStatusPending,
- structs.CommitStatusSuccess,
- }
-
- for i, commitStatuses := range testCases {
- if MergeRequiredContextsCommitStatus(commitStatuses, testCasesRequiredContexts[i]) != testCasesExpected[i] {
- assert.Fail(t, "Test case failed", "Test case %d failed", i+1)
- }
+ for i, c := range cases {
+ assert.Equal(t, c.expected, MergeRequiredContextsCommitStatus(c.commitStatuses, c.requiredContexts), "case %d", i)
}
}
diff --git a/services/pull/merge.go b/services/pull/merge.go
index 256db847ef..cd9aeb2ad1 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -8,11 +8,13 @@ import (
"context"
"errors"
"fmt"
+ "maps"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
+ "unicode"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
@@ -94,9 +96,7 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName
vars["HeadRepoName"] = pr.HeadRepo.Name
}
- for extraKey, extraValue := range extraVars {
- vars[extraKey] = extraValue
- }
+ maps.Copy(vars, extraVars)
refs, err := pr.ResolveCrossReferences(ctx)
if err == nil {
closeIssueIndexes := make([]string, 0, len(refs))
@@ -161,6 +161,41 @@ func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr
return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil)
}
+func AddCommitMessageTailer(message, tailerKey, tailerValue string) string {
+ tailerLine := tailerKey + ": " + tailerValue
+ message = strings.ReplaceAll(message, "\r\n", "\n")
+ message = strings.ReplaceAll(message, "\r", "\n")
+ if strings.Contains(message, "\n"+tailerLine+"\n") || strings.HasSuffix(message, "\n"+tailerLine) {
+ return message
+ }
+
+ if !strings.HasSuffix(message, "\n") {
+ message += "\n"
+ }
+ pos1 := strings.LastIndexByte(message[:len(message)-1], '\n')
+ pos2 := -1
+ if pos1 != -1 {
+ pos2 = strings.IndexByte(message[pos1:], ':')
+ if pos2 != -1 {
+ pos2 += pos1
+ }
+ }
+ var lastLineKey string
+ if pos1 != -1 && pos2 != -1 {
+ lastLineKey = message[pos1+1 : pos2]
+ }
+
+ isLikelyTailerLine := lastLineKey != "" && unicode.IsUpper(rune(lastLineKey[0])) && strings.Contains(message, "-")
+ for i := 0; isLikelyTailerLine && i < len(lastLineKey); i++ {
+ r := rune(lastLineKey[i])
+ isLikelyTailerLine = unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-'
+ }
+ if !strings.HasSuffix(message, "\n\n") && !isLikelyTailerLine {
+ message += "\n"
+ }
+ return message + tailerLine
+}
+
// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy
type ErrInvalidMergeStyle struct {
ID int64
@@ -290,7 +325,7 @@ func handleCloseCrossReferences(ctx context.Context, pr *issues_model.PullReques
}
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
-func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) { //nolint:unparam
+func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) { //nolint:unparam // non-error result is never used
// Clone base repo.
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
if err != nil {
@@ -396,10 +431,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use
func commitAndSignNoAuthor(ctx *mergeContext, message string) error {
cmdCommit := git.NewCommand("commit").AddOptionFormat("--message=%s", message)
- if ctx.signKeyID == "" {
+ if ctx.signKey == nil {
cmdCommit.AddArguments("--no-gpg-sign")
} else {
- cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID)
+ if ctx.signKey.Format != "" {
+ cmdCommit.AddConfig("gpg.format", ctx.signKey.Format)
+ }
+ cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID)
}
if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil {
log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go
index 719cc6b965..31a1e13734 100644
--- a/services/pull/merge_prepare.go
+++ b/services/pull/merge_prepare.go
@@ -27,7 +27,7 @@ type mergeContext struct {
doer *user_model.User
sig *git.Signature
committer *git.Signature
- signKeyID string // empty for no-sign, non-empty to sign
+ signKey *git.SigningKey
env []string
}
@@ -99,9 +99,9 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque
mergeCtx.committer = mergeCtx.sig
// Determine if we should sign
- sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, mergeCtx.tmpBasePath, "HEAD", trackingBranch)
+ sign, key, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, mergeCtx.tmpBasePath, "HEAD", trackingBranch)
if sign {
- mergeCtx.signKeyID = keyID
+ mergeCtx.signKey = key
if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
mergeCtx.committer = signer
}
diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go
index 72660cd3c5..0049c0b117 100644
--- a/services/pull/merge_squash.go
+++ b/services/pull/merge_squash.go
@@ -5,7 +5,6 @@ package pull
import (
"fmt"
- "strings"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
@@ -66,18 +65,19 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error {
if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() {
// add trailer
- if !strings.Contains(message, "Co-authored-by: "+sig.String()) {
- message += "\nCo-authored-by: " + sig.String()
- }
- message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String())
+ message = AddCommitMessageTailer(message, "Co-authored-by", sig.String())
+ message = AddCommitMessageTailer(message, "Co-committed-by", sig.String()) // FIXME: this one should be removed, it is not really used or widely used
}
cmdCommit := git.NewCommand("commit").
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email).
AddOptionFormat("--message=%s", message)
- if ctx.signKeyID == "" {
+ if ctx.signKey == nil {
cmdCommit.AddArguments("--no-gpg-sign")
} else {
- cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID)
+ if ctx.signKey.Format != "" {
+ cmdCommit.AddConfig("gpg.format", ctx.signKey.Format)
+ }
+ cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID)
}
if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil {
log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
diff --git a/services/pull/merge_test.go b/services/pull/merge_test.go
index 6df6f55d46..91abeb9d9c 100644
--- a/services/pull/merge_test.go
+++ b/services/pull/merge_test.go
@@ -65,3 +65,28 @@ func Test_expandDefaultMergeMessage(t *testing.T) {
})
}
}
+
+func TestAddCommitMessageTailer(t *testing.T) {
+ // add tailer for empty message
+ assert.Equal(t, "\n\nTest-tailer: TestValue", AddCommitMessageTailer("", "Test-tailer", "TestValue"))
+
+ // add tailer for message without newlines
+ assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title", "Test-tailer", "TestValue"))
+ assert.Equal(t, "title\n\nNot tailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nNot tailer: xxx", "Test-tailer", "TestValue"))
+ assert.Equal(t, "title\n\nNotTailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nNotTailer: xxx", "Test-tailer", "TestValue"))
+ assert.Equal(t, "title\n\nnot-tailer: xxx\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nnot-tailer: xxx", "Test-tailer", "TestValue"))
+
+ // add tailer for message with one EOL
+ assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n", "Test-tailer", "TestValue"))
+
+ // add tailer for message with two EOLs
+ assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\n", "Test-tailer", "TestValue"))
+
+ // add tailer for message with existing tailer (won't duplicate)
+ assert.Equal(t, "title\n\nTest-tailer: TestValue", AddCommitMessageTailer("title\n\nTest-tailer: TestValue", "Test-tailer", "TestValue"))
+ assert.Equal(t, "title\n\nTest-tailer: TestValue\n", AddCommitMessageTailer("title\n\nTest-tailer: TestValue\n", "Test-tailer", "TestValue"))
+
+ // add tailer for message with existing tailer and different value (will append)
+ assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTailer("title\n\nTest-tailer: v1", "Test-tailer", "v2"))
+ assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTailer("title\n\nTest-tailer: v1\n", "Test-tailer", "v2"))
+}
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 81be797832..701c4f4d32 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -945,12 +945,6 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
return stringBuilder.String()
}
-// GetIssuesLastCommitStatus returns a map of issue ID to the most recent commit's latest status
-func GetIssuesLastCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64]*git_model.CommitStatus, error) {
- _, lastStatus, err := GetIssuesAllCommitStatus(ctx, issues)
- return lastStatus, err
-}
-
// GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status
func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatus, error) {
if err := issues.LoadPullRequests(ctx); err != nil {
@@ -1004,7 +998,7 @@ func getAllCommitStatus(ctx context.Context, gitRepo *git.Repository, pr *issues
return nil, nil, shaErr
}
- statuses, _, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
+ statuses, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err
}
diff --git a/services/pull/reviewer.go b/services/pull/reviewer.go
index bf0d8cb298..52f2f3401c 100644
--- a/services/pull/reviewer.go
+++ b/services/pull/reviewer.go
@@ -85,5 +85,5 @@ func GetReviewerTeams(ctx context.Context, repo *repo_model.Repository) ([]*orga
return nil, nil
}
- return organization.GetTeamsWithAccessToRepoUnit(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead, unit.TypePullRequests)
+ return organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead, unit.TypePullRequests)
}
diff --git a/services/pull/update.go b/services/pull/update.go
index 5cc5e2b134..b8f84e3d65 100644
--- a/services/pull/update.go
+++ b/services/pull/update.go
@@ -41,22 +41,6 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
return fmt.Errorf("HeadBranch of PR %d is up to date", pr.Index)
}
- if rebase {
- defer func() {
- go AddTestPullRequestTask(TestPullRequestOptions{
- RepoID: pr.BaseRepo.ID,
- Doer: doer,
- Branch: pr.BaseBranch,
- IsSync: false,
- IsForcePush: false,
- OldCommitID: "",
- NewCommitID: "",
- })
- }()
-
- return updateHeadByRebaseOnToBase(ctx, pr, doer)
- }
-
if err := pr.LoadBaseRepo(ctx); err != nil {
log.Error("unable to load BaseRepo for %-v during update-by-merge: %v", pr, err)
return fmt.Errorf("unable to load BaseRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
@@ -74,6 +58,22 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
}
+ defer func() {
+ go AddTestPullRequestTask(TestPullRequestOptions{
+ RepoID: pr.BaseRepo.ID,
+ Doer: doer,
+ Branch: pr.BaseBranch,
+ IsSync: false,
+ IsForcePush: false,
+ OldCommitID: "",
+ NewCommitID: "",
+ })
+ }()
+
+ if rebase {
+ return updateHeadByRebaseOnToBase(ctx, pr, doer)
+ }
+
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
// now use a fake reverse PR to switch head&base repos/branches
@@ -90,19 +90,6 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
}
_, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message, repository.PushTriggerPRUpdateWithBase)
-
- defer func() {
- go AddTestPullRequestTask(TestPullRequestOptions{
- RepoID: reversePR.HeadRepo.ID,
- Doer: doer,
- Branch: reversePR.HeadBranch,
- IsSync: false,
- IsForcePush: false,
- OldCommitID: "",
- NewCommitID: "",
- })
- }()
-
return err
}
diff --git a/services/repository/adopt.go b/services/repository/adopt.go
index 7f1954145c..2bd1c55de4 100644
--- a/services/repository/adopt.go
+++ b/services/repository/adopt.go
@@ -100,7 +100,7 @@ func AdoptRepository(ctx context.Context, doer, owner *user_model.User, opts Cre
// 4 - update repository status
repo.Status = repo_model.RepositoryReady
- if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
+ if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
}
@@ -196,8 +196,13 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr
return fmt.Errorf("setDefaultBranch: %w", err)
}
}
- if err = updateRepository(ctx, repo, false); err != nil {
- return fmt.Errorf("updateRepository: %w", err)
+
+ if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_empty", "default_branch"); err != nil {
+ return fmt.Errorf("UpdateRepositoryCols: %w", err)
+ }
+
+ if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
+ log.Error("Failed to update size for repository: %v", err)
}
return nil
@@ -260,7 +265,7 @@ func checkUnadoptedRepositories(ctx context.Context, userName string, repoNamesT
}
return err
}
- repos, _, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
+ repos, _, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: ctxUser,
Private: true,
ListOptions: db.ListOptions{
diff --git a/services/repository/adopt_test.go b/services/repository/adopt_test.go
index 6e1dc417b3..86f586c748 100644
--- a/services/repository/adopt_test.go
+++ b/services/repository/adopt_test.go
@@ -29,7 +29,7 @@ func TestCheckUnadoptedRepositories_Add(t *testing.T) {
}
total := 30
- for i := 0; i < total; i++ {
+ for range total {
unadopted.add("something")
}
diff --git a/services/repository/avatar.go b/services/repository/avatar.go
index 15e51d4a25..26bf6da465 100644
--- a/services/repository/avatar.go
+++ b/services/repository/avatar.go
@@ -40,7 +40,7 @@ func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte)
// Users can upload the same image to other repo - prefix it with ID
// Then repo will be removed - only it avatar file will be removed
repo.Avatar = newAvatar
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "avatar"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil {
return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err)
}
@@ -77,7 +77,7 @@ func DeleteAvatar(ctx context.Context, repo *repo_model.Repository) error {
defer committer.Close()
repo.Avatar = ""
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "avatar"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil {
return fmt.Errorf("DeleteAvatar: Update repository avatar: %w", err)
}
@@ -112,5 +112,5 @@ func generateAvatar(ctx context.Context, templateRepo, generateRepo *repo_model.
return err
}
- return repo_model.UpdateRepositoryCols(ctx, generateRepo, "avatar")
+ return repo_model.UpdateRepositoryColsNoAutoTime(ctx, generateRepo, "avatar")
}
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 94c47ffdc4..dd00ca7dcd 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -663,6 +663,11 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, newB
}
}
+ // clear divergence cache
+ if err := DelRepoDivergenceFromCache(ctx, repo.ID); err != nil {
+ log.Error("DelRepoDivergenceFromCache: %v", err)
+ }
+
notify_service.ChangeDefaultBranch(ctx, repo)
return nil
diff --git a/services/repository/check.go b/services/repository/check.go
index b475fbc487..ffcd5ac749 100644
--- a/services/repository/check.go
+++ b/services/repository/check.go
@@ -162,7 +162,7 @@ func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error
default:
}
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
- if err := DeleteRepositoryDirectly(ctx, doer, repo.ID); err != nil {
+ if err := DeleteRepositoryDirectly(ctx, repo.ID); err != nil {
log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err)
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
log.Error("CreateRepositoryNotice: %v", err)
diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go
index f369a303e6..fa7a89882a 100644
--- a/services/repository/commitstatus/commitstatus.go
+++ b/services/repository/commitstatus/commitstatus.go
@@ -14,17 +14,17 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
- api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/notify"
)
func getCacheKey(repoID int64, brancheName string) string {
- hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%d:%s", repoID, brancheName)))
+ hashBytes := sha256.Sum256(fmt.Appendf(nil, "%d:%s", repoID, brancheName))
return fmt.Sprintf("commit_status:%x", hashBytes)
}
@@ -47,7 +47,7 @@ func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheVal
return nil
}
-func updateCommitStatusCache(repoID int64, branchName string, state api.CommitStatusState, targetURL string) error {
+func updateCommitStatusCache(repoID int64, branchName string, state commitstatus.CommitStatusState, targetURL string) error {
c := cache.GetCache()
bs, err := json.Marshal(commitStatusCacheValue{
State: state.String(),
@@ -127,7 +127,7 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
for i, repo := range repos {
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
results[i] = &git_model.CommitStatus{
- State: api.CommitStatusState(cv.State),
+ State: commitstatus.CommitStatusState(cv.State),
TargetURL: cv.TargetURL,
}
} else {
diff --git a/services/repository/create.go b/services/repository/create.go
index c4a9dbb1b6..bed02e5d7e 100644
--- a/services/repository/create.go
+++ b/services/repository/create.go
@@ -100,8 +100,8 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir
// .gitignore
if len(opts.Gitignores) > 0 {
var buf bytes.Buffer
- names := strings.Split(opts.Gitignores, ",")
- for _, name := range names {
+ names := strings.SplitSeq(opts.Gitignores, ",")
+ for name := range names {
data, err = options.Gitignore(name)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
@@ -191,10 +191,14 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re
}
}
- if err = UpdateRepository(ctx, repo, false); err != nil {
+ if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_empty", "default_branch", "default_wiki_branch"); err != nil {
return fmt.Errorf("updateRepository: %w", err)
}
+ if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
+ log.Error("Failed to update size for repository: %v", err)
+ }
+
return nil
}
@@ -259,7 +263,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
defer func() {
if err != nil {
// we can not use the ctx because it maybe canceled or timeout
- cleanupRepository(doer, repo.ID)
+ cleanupRepository(repo.ID)
}
}()
@@ -321,7 +325,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
// 7 - update repository status to be ready
if needsUpdateToReady {
repo.Status = repo_model.RepositoryReady
- if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
+ if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
}
}
@@ -454,8 +458,8 @@ func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *r
return nil
}
-func cleanupRepository(doer *user_model.User, repoID int64) {
- if errDelete := DeleteRepositoryDirectly(db.DefaultContext, doer, repoID); errDelete != nil {
+func cleanupRepository(repoID int64) {
+ if errDelete := DeleteRepositoryDirectly(db.DefaultContext, repoID); errDelete != nil {
log.Error("cleanupRepository failed: %v", errDelete)
// add system notice
if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository: %v", errDelete); err != nil {
diff --git a/services/repository/create_test.go b/services/repository/create_test.go
index 8e3fdf88a5..fe464c1441 100644
--- a/services/repository/create_test.go
+++ b/services/repository/create_test.go
@@ -35,7 +35,7 @@ func TestCreateRepositoryDirectly(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name})
- err = DeleteRepositoryDirectly(db.DefaultContext, user2, createdRepo.ID)
+ err = DeleteRepositoryDirectly(db.DefaultContext, createdRepo.ID)
assert.NoError(t, err)
// a failed creating because some mock data
diff --git a/services/repository/delete.go b/services/repository/delete.go
index cf960af8cf..c48d6e1d56 100644
--- a/services/repository/delete.go
+++ b/services/repository/delete.go
@@ -27,7 +27,9 @@ import (
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
+ actions_service "code.gitea.io/gitea/services/actions"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ issue_service "code.gitea.io/gitea/services/issue"
"xorm.io/builder"
)
@@ -47,7 +49,7 @@ func deleteDBRepository(ctx context.Context, repoID int64) error {
// DeleteRepository deletes a repository for a user or organization.
// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock)
-func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID int64, ignoreOrgTeams ...bool) error {
+func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams ...bool) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
@@ -133,6 +135,14 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
return err
}
+ // CleanupEphemeralRunnersByPickedTaskOfRepo deletes ephemeral global/org/user that have started any task of this repo
+ // The cannot pick a second task hardening for ephemeral runners expect that task objects remain available until runner deletion
+ // This method will delete affected ephemeral global/org/user runners
+ // &actions_model.ActionRunner{RepoID: repoID} does only handle ephemeral repository runners
+ if err := actions_service.CleanupEphemeralRunnersByPickedTaskOfRepo(ctx, repoID); err != nil {
+ return fmt.Errorf("cleanupEphemeralRunners: %w", err)
+ }
+
if err := db.DeleteBeans(ctx,
&access_model.Access{RepoID: repo.ID},
&activities_model.Action{RepoID: repo.ID},
@@ -184,7 +194,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
// Delete Issues and related objects
var attachmentPaths []string
- if attachmentPaths, err = issues_model.DeleteIssuesByRepoID(ctx, repoID); err != nil {
+ if attachmentPaths, err = issue_service.DeleteIssuesByRepoID(ctx, repoID); err != nil {
return err
}
@@ -365,7 +375,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
// DeleteOwnerRepositoriesDirectly calls DeleteRepositoryDirectly for all repos of the given owner
func DeleteOwnerRepositoriesDirectly(ctx context.Context, owner *user_model.User) error {
for {
- repos, _, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
+ repos, _, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: repo_model.RepositoryListDefaultPageSize,
Page: 1,
@@ -381,7 +391,7 @@ func DeleteOwnerRepositoriesDirectly(ctx context.Context, owner *user_model.User
break
}
for _, repo := range repos {
- if err := DeleteRepositoryDirectly(ctx, owner, repo.ID); err != nil {
+ if err := DeleteRepositoryDirectly(ctx, repo.ID); err != nil {
return fmt.Errorf("unable to delete repository %s for %s[%d]. Error: %w", repo.Name, owner.Name, owner.ID, err)
}
}
diff --git a/services/repository/files/content.go b/services/repository/files/content.go
index 7a07a0ddca..2c1e88bb59 100644
--- a/services/repository/files/content.go
+++ b/services/repository/files/content.go
@@ -5,13 +5,14 @@ package files
import (
"context"
- "fmt"
+ "io"
"net/url"
"path"
+ "strings"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
@@ -34,54 +35,52 @@ func (ct *ContentType) String() string {
return string(*ct)
}
+type GetContentsOrListOptions struct {
+ TreePath string
+ IncludeSingleFileContent bool // include the file's content when the tree path is a file
+ IncludeLfsMetadata bool
+ IncludeCommitMetadata bool
+ IncludeCommitMessage bool
+}
+
// GetContentsOrList gets the metadata of a file's contents (*ContentsResponse) if treePath not a tree
// directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag
-func GetContentsOrList(ctx context.Context, repo *repo_model.Repository, refCommit *utils.RefCommit, treePath string) (any, error) {
- if repo.IsEmpty {
- return make([]any, 0), nil
- }
-
- // Check that the path given in opts.treePath is valid (not a git path)
- cleanTreePath := CleanUploadFileName(treePath)
- if cleanTreePath == "" && treePath != "" {
- return nil, ErrFilenameInvalid{
- Path: treePath,
- }
+func GetContentsOrList(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, opts GetContentsOrListOptions) (ret api.ContentsExtResponse, _ error) {
+ entry, err := prepareGetContentsEntry(refCommit, &opts.TreePath)
+ if repo.IsEmpty && opts.TreePath == "" {
+ return api.ContentsExtResponse{DirContents: make([]*api.ContentsResponse, 0)}, nil
}
- treePath = cleanTreePath
-
- // Get the commit object for the ref
- commit := refCommit.Commit
-
- entry, err := commit.GetTreeEntryByPath(treePath)
if err != nil {
- return nil, err
+ return ret, err
}
+ // get file contents
if entry.Type() != "tree" {
- return GetContents(ctx, repo, refCommit, treePath, false)
+ ret.FileContents, err = getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
+ return ret, err
}
- // We are in a directory, so we return a list of FileContentResponse objects
- var fileList []*api.ContentsResponse
-
- gitTree, err := commit.SubTree(treePath)
+ // list directory contents
+ gitTree, err := refCommit.Commit.SubTree(opts.TreePath)
if err != nil {
- return nil, err
+ return ret, err
}
entries, err := gitTree.ListEntries()
if err != nil {
- return nil, err
+ return ret, err
}
+ ret.DirContents = make([]*api.ContentsResponse, 0, len(entries))
for _, e := range entries {
- subTreePath := path.Join(treePath, e.Name())
- fileContentResponse, err := GetContents(ctx, repo, refCommit, subTreePath, true)
+ subOpts := opts
+ subOpts.TreePath = path.Join(opts.TreePath, e.Name())
+ subOpts.IncludeSingleFileContent = false // never include file content when listing a directory
+ fileContentResponse, err := GetFileContents(ctx, repo, gitRepo, refCommit, subOpts)
if err != nil {
- return nil, err
+ return ret, err
}
- fileList = append(fileList, fileContentResponse)
+ ret.DirContents = append(ret.DirContents, fileContentResponse)
}
- return fileList, nil
+ return ret, nil
}
// GetObjectTypeFromTreeEntry check what content is behind it
@@ -100,83 +99,96 @@ func GetObjectTypeFromTreeEntry(entry *git.TreeEntry) ContentType {
}
}
-// GetContents gets the metadata on a file's contents. Ref can be a branch, commit or tag
-func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *utils.RefCommit, treePath string, forList bool) (*api.ContentsResponse, error) {
+func prepareGetContentsEntry(refCommit *utils.RefCommit, treePath *string) (*git.TreeEntry, error) {
// Check that the path given in opts.treePath is valid (not a git path)
- cleanTreePath := CleanUploadFileName(treePath)
- if cleanTreePath == "" && treePath != "" {
- return nil, ErrFilenameInvalid{
- Path: treePath,
- }
- }
- treePath = cleanTreePath
-
- gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
- if err != nil {
- return nil, err
- }
- defer closer.Close()
-
- commit := refCommit.Commit
- entry, err := commit.GetTreeEntryByPath(treePath)
- if err != nil {
- return nil, err
+ cleanTreePath := CleanGitTreePath(*treePath)
+ if cleanTreePath == "" && *treePath != "" {
+ return nil, ErrFilenameInvalid{Path: *treePath}
}
+ *treePath = cleanTreePath
+ // Only allow safe ref types
refType := refCommit.RefName.RefType()
if refType != git.RefTypeBranch && refType != git.RefTypeTag && refType != git.RefTypeCommit {
- return nil, fmt.Errorf("no commit found for the ref [ref: %s]", refCommit.RefName)
+ return nil, util.NewNotExistErrorf("no commit found for the ref [ref: %s]", refCommit.RefName)
}
- selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(treePath) + "?ref=" + url.QueryEscape(refCommit.InputRef))
- if err != nil {
- return nil, err
- }
- selfURLString := selfURL.String()
+ return refCommit.Commit.GetTreeEntryByPath(*treePath)
+}
- err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
+// GetFileContents gets the metadata on a file's contents. Ref can be a branch, commit or tag
+func GetFileContents(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
+ entry, err := prepareGetContentsEntry(refCommit, &opts.TreePath)
if err != nil {
return nil, err
}
+ return getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
+}
- lastCommit, err := commit.GetCommitByPath(treePath)
+func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
+ refType := refCommit.RefName.RefType()
+ commit := refCommit.Commit
+ selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(opts.TreePath) + "?ref=" + url.QueryEscape(refCommit.InputRef))
if err != nil {
return nil, err
}
+ selfURLString := selfURL.String()
// All content types have these fields in populated
contentsResponse := &api.ContentsResponse{
- Name: entry.Name(),
- Path: treePath,
- SHA: entry.ID.String(),
- LastCommitSHA: lastCommit.ID.String(),
- Size: entry.Size(),
- URL: &selfURLString,
+ Name: entry.Name(),
+ Path: opts.TreePath,
+ SHA: entry.ID.String(),
+ Size: entry.Size(),
+ URL: &selfURLString,
Links: &api.FileLinksResponse{
Self: &selfURLString,
},
}
- // GitHub doesn't have these fields in the response, but we could follow other similar APIs to name them
- // https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
- if lastCommit.Committer != nil {
- contentsResponse.LastCommitterDate = lastCommit.Committer.When
- }
- if lastCommit.Author != nil {
- contentsResponse.LastAuthorDate = lastCommit.Author.When
+ if opts.IncludeCommitMetadata || opts.IncludeCommitMessage {
+ err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
+ if err != nil {
+ return nil, err
+ }
+
+ lastCommit, err := refCommit.Commit.GetCommitByPath(opts.TreePath)
+ if err != nil {
+ return nil, err
+ }
+
+ if opts.IncludeCommitMetadata {
+ contentsResponse.LastCommitSHA = util.ToPointer(lastCommit.ID.String())
+ // GitHub doesn't have these fields in the response, but we could follow other similar APIs to name them
+ // https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
+ if lastCommit.Committer != nil {
+ contentsResponse.LastCommitterDate = util.ToPointer(lastCommit.Committer.When)
+ }
+ if lastCommit.Author != nil {
+ contentsResponse.LastAuthorDate = util.ToPointer(lastCommit.Author.When)
+ }
+ }
+ if opts.IncludeCommitMessage {
+ contentsResponse.LastCommitMessage = util.ToPointer(lastCommit.Message())
+ }
}
- // Now populate the rest of the ContentsResponse based on entry type
+ // Now populate the rest of the ContentsResponse based on the entry type
if entry.IsRegular() || entry.IsExecutable() {
contentsResponse.Type = string(ContentTypeRegular)
// if it is listing the repo root dir, don't waste system resources on reading content
- if !forList {
- blobResponse, err := GetBlobBySHA(ctx, repo, gitRepo, entry.ID.String())
+ if opts.IncludeSingleFileContent {
+ blobResponse, err := GetBlobBySHA(repo, gitRepo, entry.ID.String())
+ if err != nil {
+ return nil, err
+ }
+ contentsResponse.Encoding, contentsResponse.Content = blobResponse.Encoding, blobResponse.Content
+ contentsResponse.LfsOid, contentsResponse.LfsSize = blobResponse.LfsOid, blobResponse.LfsSize
+ } else if opts.IncludeLfsMetadata {
+ contentsResponse.LfsOid, contentsResponse.LfsSize, err = parsePossibleLfsPointerBlob(gitRepo, entry.ID.String())
if err != nil {
return nil, err
}
- contentsResponse.Encoding = blobResponse.Encoding
- contentsResponse.Content = blobResponse.Content
}
} else if entry.IsDir() {
contentsResponse.Type = string(ContentTypeDir)
@@ -190,7 +202,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *ut
contentsResponse.Target = &targetFromContent
} else if entry.IsSubModule() {
contentsResponse.Type = string(ContentTypeSubmodule)
- submodule, err := commit.GetSubModule(treePath)
+ submodule, err := commit.GetSubModule(opts.TreePath)
if err != nil {
return nil, err
}
@@ -200,7 +212,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *ut
}
// Handle links
if entry.IsRegular() || entry.IsLink() || entry.IsExecutable() {
- downloadURL, err := url.Parse(repo.HTMLURL() + "/raw/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(treePath))
+ downloadURL, err := url.Parse(repo.HTMLURL() + "/raw/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(opts.TreePath))
if err != nil {
return nil, err
}
@@ -208,7 +220,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *ut
contentsResponse.DownloadURL = &downloadURLString
}
if !entry.IsSubModule() {
- htmlURL, err := url.Parse(repo.HTMLURL() + "/src/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(treePath))
+ htmlURL, err := url.Parse(repo.HTMLURL() + "/src/" + refCommit.RefName.RefWebLinkPath() + "/" + util.PathEscapeSegments(opts.TreePath))
if err != nil {
return nil, err
}
@@ -228,8 +240,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, refCommit *ut
return contentsResponse, nil
}
-// GetBlobBySHA get the GitBlobResponse of a repository using a sha hash.
-func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string) (*api.GitBlobResponse, error) {
+func GetBlobBySHA(repo *repo_model.Repository, gitRepo *git.Repository, sha string) (*api.GitBlobResponse, error) {
gitBlob, err := gitRepo.GetBlob(sha)
if err != nil {
return nil, err
@@ -239,12 +250,49 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()),
Size: gitBlob.Size(),
}
- if gitBlob.Size() <= setting.API.DefaultMaxBlobSize {
- content, err := gitBlob.GetBlobContentBase64()
- if err != nil {
- return nil, err
- }
- ret.Encoding, ret.Content = util.ToPointer("base64"), &content
+
+ blobSize := gitBlob.Size()
+ if blobSize > setting.API.DefaultMaxBlobSize {
+ return ret, nil
+ }
+
+ var originContent *strings.Builder
+ if 0 < blobSize && blobSize < lfs.MetaFileMaxSize {
+ originContent = &strings.Builder{}
+ }
+
+ content, err := gitBlob.GetBlobContentBase64(originContent)
+ if err != nil {
+ return nil, err
+ }
+
+ ret.Encoding, ret.Content = util.ToPointer("base64"), &content
+ if originContent != nil {
+ ret.LfsOid, ret.LfsSize = parsePossibleLfsPointerBuffer(strings.NewReader(originContent.String()))
}
return ret, nil
}
+
+func parsePossibleLfsPointerBuffer(r io.Reader) (*string, *int64) {
+ p, _ := lfs.ReadPointer(r)
+ if p.IsValid() {
+ return &p.Oid, &p.Size
+ }
+ return nil, nil
+}
+
+func parsePossibleLfsPointerBlob(gitRepo *git.Repository, sha string) (*string, *int64, error) {
+ gitBlob, err := gitRepo.GetBlob(sha)
+ if err != nil {
+ return nil, nil, err
+ }
+ if gitBlob.Size() > lfs.MetaFileMaxSize {
+ return nil, nil, nil // not a LFS pointer
+ }
+ buf, err := gitBlob.GetBlobContent(lfs.MetaFileMaxSize)
+ if err != nil {
+ return nil, nil, err
+ }
+ oid, size := parsePossibleLfsPointerBuffer(strings.NewReader(buf))
+ return oid, size, nil
+}
diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go
index eb10f5c9b1..d72f918074 100644
--- a/services/repository/files/content_test.go
+++ b/services/repository/files/content_test.go
@@ -5,57 +5,21 @@ package files
import (
"testing"
- "time"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/gitrepo"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/contexttest"
_ "code.gitea.io/gitea/models/actions"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
-func getExpectedReadmeContentsResponse() *api.ContentsResponse {
- treePath := "README.md"
- sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f"
- encoding := "base64"
- content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"
- selfURL := "https://try.gitea.io/api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
- htmlURL := "https://try.gitea.io/user2/repo1/src/branch/master/" + treePath
- gitURL := "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/" + sha
- downloadURL := "https://try.gitea.io/user2/repo1/raw/branch/master/" + treePath
- return &api.ContentsResponse{
- Name: treePath,
- Path: treePath,
- SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
- LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
- LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
- LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
- Type: "file",
- Size: 30,
- Encoding: &encoding,
- Content: &content,
- URL: &selfURL,
- HTMLURL: &htmlURL,
- GitURL: &gitURL,
- DownloadURL: &downloadURL,
- Links: &api.FileLinksResponse{
- Self: &selfURL,
- GitURL: &gitURL,
- HTMLURL: &htmlURL,
- },
- }
-}
-
func TestGetContents(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
@@ -64,146 +28,22 @@ func TestGetContents(t *testing.T) {
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- treePath := "README.md"
- refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
- require.NoError(t, err)
- expectedContentsResponse := getExpectedReadmeContentsResponse()
-
- t.Run("Get README.md contents with GetContents(ctx, )", func(t *testing.T) {
- fileContentResponse, err := GetContents(ctx, ctx.Repo.Repository, refCommit, treePath, false)
- assert.Equal(t, expectedContentsResponse, fileContentResponse)
+ // GetContentsOrList's behavior is fully tested in integration tests, so we don't need to test it here.
+
+ t.Run("GetBlobBySHA", func(t *testing.T) {
+ sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
+ ctx.SetPathParam("id", "1")
+ ctx.SetPathParam("sha", sha)
+ gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("sha"))
+ expectedGBR := &api.GitBlobResponse{
+ Content: util.ToPointer("dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"),
+ Encoding: util.ToPointer("base64"),
+ URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ Size: 180,
+ }
assert.NoError(t, err)
+ assert.Equal(t, expectedGBR, gbr)
})
}
-
-func TestGetContentsOrListForDir(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam("id", "1")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
- contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- treePath := "" // root dir
- refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
- require.NoError(t, err)
-
- readmeContentsResponse := getExpectedReadmeContentsResponse()
- // because will be in a list, doesn't have encoding and content
- readmeContentsResponse.Encoding = nil
- readmeContentsResponse.Content = nil
-
- expectedContentsListResponse := []*api.ContentsResponse{
- readmeContentsResponse,
- }
-
- t.Run("Get root dir contents with GetContentsOrList(ctx, )", func(t *testing.T) {
- fileContentResponse, err := GetContentsOrList(ctx, ctx.Repo.Repository, refCommit, treePath)
- assert.EqualValues(t, expectedContentsListResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-}
-
-func TestGetContentsOrListForFile(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam("id", "1")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
- contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- treePath := "README.md"
- refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
- require.NoError(t, err)
-
- expectedContentsResponse := getExpectedReadmeContentsResponse()
-
- t.Run("Get README.md contents with GetContentsOrList(ctx, )", func(t *testing.T) {
- fileContentResponse, err := GetContentsOrList(ctx, ctx.Repo.Repository, refCommit, treePath)
- assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-}
-
-func TestGetContentsErrors(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam("id", "1")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
- contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- repo := ctx.Repo.Repository
- refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
- require.NoError(t, err)
-
- t.Run("bad treePath", func(t *testing.T) {
- badTreePath := "bad/tree.md"
- fileContentResponse, err := GetContents(ctx, repo, refCommit, badTreePath, false)
- assert.Error(t, err)
- assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
- assert.Nil(t, fileContentResponse)
- })
-}
-
-func TestGetContentsOrListErrors(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam("id", "1")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
- contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- repo := ctx.Repo.Repository
- refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
- require.NoError(t, err)
-
- t.Run("bad treePath", func(t *testing.T) {
- badTreePath := "bad/tree.md"
- fileContentResponse, err := GetContentsOrList(ctx, repo, refCommit, badTreePath)
- assert.Error(t, err)
- assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
- assert.Nil(t, fileContentResponse)
- })
-}
-
-func TestGetBlobBySHA(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx, _ := contexttest.MockContext(t, "user2/repo1")
- contexttest.LoadRepo(t, ctx, 1)
- contexttest.LoadRepoCommit(t, ctx)
- contexttest.LoadUser(t, ctx, 2)
- contexttest.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
- ctx.SetPathParam("id", "1")
- ctx.SetPathParam("sha", sha)
-
- gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
- if err != nil {
- t.Fail()
- }
-
- gbr, err := GetBlobBySHA(ctx, ctx.Repo.Repository, gitRepo, ctx.PathParam("sha"))
- expectedGBR := &api.GitBlobResponse{
- Content: util.ToPointer("dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK"),
- Encoding: util.ToPointer("base64"),
- URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
- SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
- Size: 180,
- }
- assert.NoError(t, err)
- assert.Equal(t, expectedGBR, gbr)
-}
diff --git a/services/repository/files/diff.go b/services/repository/files/diff.go
index 0b3550452a..50d01f9d7c 100644
--- a/services/repository/files/diff.go
+++ b/services/repository/files/diff.go
@@ -29,7 +29,7 @@ func GetDiffPreview(ctx context.Context, repo *repo_model.Repository, branch, tr
}
// Add the object to the database
- objectHash, err := t.HashObject(ctx, strings.NewReader(content))
+ objectHash, err := t.HashObjectAndWrite(ctx, strings.NewReader(content))
if err != nil {
return nil, err
}
diff --git a/services/repository/files/file.go b/services/repository/files/file.go
index c4991b458d..13d171d139 100644
--- a/services/repository/files/file.go
+++ b/services/repository/files/file.go
@@ -19,12 +19,17 @@ import (
"code.gitea.io/gitea/routers/api/v1/utils"
)
-func GetContentsListFromTreePaths(ctx context.Context, repo *repo_model.Repository, refCommit *utils.RefCommit, treePaths []string) (files []*api.ContentsResponse) {
+func GetContentsListFromTreePaths(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treePaths []string) (files []*api.ContentsResponse) {
var size int64
for _, treePath := range treePaths {
- fileContents, _ := GetContents(ctx, repo, refCommit, treePath, false) // ok if fails, then will be nil
+ // ok if fails, then will be nil
+ fileContents, _ := GetFileContents(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{
+ TreePath: treePath,
+ IncludeSingleFileContent: true,
+ IncludeCommitMetadata: true,
+ })
if fileContents != nil && fileContents.Content != nil && *fileContents.Content != "" {
- // if content isn't empty (e.g. due to the single blob being too large), add file size to response size
+ // if content isn't empty (e.g., due to the single blob being too large), add file size to response size
size += int64(len(*fileContents.Content))
}
if size > setting.API.DefaultMaxResponseSize {
@@ -38,8 +43,8 @@ func GetContentsListFromTreePaths(ctx context.Context, repo *repo_model.Reposito
return files
}
-func GetFilesResponseFromCommit(ctx context.Context, repo *repo_model.Repository, refCommit *utils.RefCommit, treeNames []string) (*api.FilesResponse, error) {
- files := GetContentsListFromTreePaths(ctx, repo, refCommit, treeNames)
+func GetFilesResponseFromCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treeNames []string) (*api.FilesResponse, error) {
+ files := GetContentsListFromTreePaths(ctx, repo, gitRepo, refCommit, treeNames)
fileCommitResponse, _ := GetFileCommitResponse(repo, refCommit.Commit) // ok if fails, then will be nil
verification := GetPayloadCommitVerification(ctx, refCommit.Commit)
filesResponse := &api.FilesResponse{
@@ -134,15 +139,17 @@ func (err ErrFilenameInvalid) Unwrap() error {
return util.ErrInvalidArgument
}
-// CleanUploadFileName Trims a filename and returns empty string if it is a .git directory
-func CleanUploadFileName(name string) string {
- // Rebase the filename
+// CleanGitTreePath cleans a tree path for git, it returns an empty string the path is invalid (e.g.: contains ".git" part)
+func CleanGitTreePath(name string) string {
name = util.PathJoinRel(name)
// Git disallows any filenames to have a .git directory in them.
- for _, part := range strings.Split(name, "/") {
+ for part := range strings.SplitSeq(name, "/") {
if strings.ToLower(part) == ".git" {
return ""
}
}
+ if name == "." {
+ name = ""
+ }
return name
}
diff --git a/services/repository/files/file_test.go b/services/repository/files/file_test.go
index 169cafba0d..cdb6a266ff 100644
--- a/services/repository/files/file_test.go
+++ b/services/repository/files/file_test.go
@@ -10,17 +10,18 @@ import (
)
func TestCleanUploadFileName(t *testing.T) {
- t.Run("Clean regular file", func(t *testing.T) {
- name := "this/is/test"
- cleanName := CleanUploadFileName(name)
- expectedCleanName := name
- assert.Equal(t, expectedCleanName, cleanName)
- })
-
- t.Run("Clean a .git path", func(t *testing.T) {
- name := "this/is/test/.git"
- cleanName := CleanUploadFileName(name)
- expectedCleanName := ""
- assert.Equal(t, expectedCleanName, cleanName)
- })
+ cases := []struct {
+ input, expected string
+ }{
+ {"", ""},
+ {".", ""},
+ {"a/./b", "a/b"},
+ {"a.git", "a.git"},
+ {".git/b", ""},
+ {"a/.git", ""},
+ {"/a/../../b", "b"},
+ }
+ for _, c := range cases {
+ assert.Equal(t, c.expected, CleanGitTreePath(c.input), "input: %q", c.input)
+ }
}
diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go
index 5fbf748206..11a8744b7f 100644
--- a/services/repository/files/patch.go
+++ b/services/repository/files/patch.go
@@ -44,7 +44,6 @@ type ApplyDiffPatchOptions struct {
NewBranch string
Message string
Content string
- SHA string
Author *IdentityOptions
Committer *IdentityOptions
Dates *CommitDateOptions
diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go
index 493ff9998d..c2f61c8223 100644
--- a/services/repository/files/temp_repo.go
+++ b/services/repository/files/temp_repo.go
@@ -128,7 +128,7 @@ func (t *TemporaryUploadRepository) LsFiles(ctx context.Context, filenames ...st
}
fileList := make([]string, 0, len(filenames))
- for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) {
+ for line := range bytes.SplitSeq(stdOut.Bytes(), []byte{'\000'}) {
fileList = append(fileList, string(line))
}
@@ -164,8 +164,8 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, fi
return nil
}
-// HashObject writes the provided content to the object db and returns its hash
-func (t *TemporaryUploadRepository) HashObject(ctx context.Context, content io.Reader) (string, error) {
+// HashObjectAndWrite writes the provided content to the object db and returns its hash
+func (t *TemporaryUploadRepository) HashObjectAndWrite(ctx context.Context, content io.Reader) (string, error) {
stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer)
@@ -293,15 +293,18 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
}
var sign bool
- var keyID string
+ var key *git.SigningKey
var signer *git.Signature
if opts.ParentCommitID != "" {
- sign, keyID, signer, _ = asymkey_service.SignCRUDAction(ctx, t.repo.RepoPath(), opts.DoerUser, t.basePath, opts.ParentCommitID)
+ sign, key, signer, _ = asymkey_service.SignCRUDAction(ctx, t.repo.RepoPath(), opts.DoerUser, t.basePath, opts.ParentCommitID)
} else {
- sign, keyID, signer, _ = asymkey_service.SignInitialCommit(ctx, t.repo.RepoPath(), opts.DoerUser)
+ sign, key, signer, _ = asymkey_service.SignInitialCommit(ctx, t.repo.RepoPath(), opts.DoerUser)
}
if sign {
- cmdCommitTree.AddOptionFormat("-S%s", keyID)
+ if key.Format != "" {
+ cmdCommitTree.AddConfig("gpg.format", key.Format)
+ }
+ cmdCommitTree.AddOptionFormat("-S%s", key.KeyID)
if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
// Add trailers
diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go
index 8427fcbacc..f2cbacbf1c 100644
--- a/services/repository/files/tree.go
+++ b/services/repository/files/tree.go
@@ -94,11 +94,7 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
if len(entries) > perPage {
tree.Truncated = true
}
- if rangeStart+perPage < len(entries) {
- rangeEnd = rangeStart + perPage
- } else {
- rangeEnd = len(entries)
- }
+ rangeEnd = min(rangeStart+perPage, len(entries))
tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart)
for e := rangeStart; e < rangeEnd; e++ {
i := e - rangeStart
@@ -165,7 +161,7 @@ func newTreeViewNodeFromEntry(ctx context.Context, renderedIconPool *fileicon.Re
FullPath: path.Join(parentDir, entry.Name()),
}
- entryInfo := fileicon.EntryInfoFromGitTreeEntry(entry)
+ entryInfo := fileicon.EntryInfoFromGitTreeEntry(commit, node.FullPath, entry)
node.EntryIcon = fileicon.RenderEntryIconHTML(renderedIconPool, entryInfo)
if entryInfo.EntryMode.IsDir() {
entryInfo.IsOpen = true
diff --git a/services/repository/files/update.go b/services/repository/files/update.go
index fbf59c40ed..e871f777e5 100644
--- a/services/repository/files/update.go
+++ b/services/repository/files/update.go
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"path"
+ "slices"
"strings"
"time"
@@ -87,14 +88,32 @@ func (err ErrRepoFileDoesNotExist) Unwrap() error {
return util.ErrNotExist
}
+type LazyReadSeeker interface {
+ io.ReadSeeker
+ io.Closer
+ OpenLazyReader() error
+}
+
// ChangeRepoFiles adds, updates or removes multiple files in the given repository
-func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *ChangeRepoFilesOptions) (*structs.FilesResponse, error) {
+func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *ChangeRepoFilesOptions) (_ *structs.FilesResponse, errRet error) {
+ var addedLfsPointers []lfs.Pointer
+ defer func() {
+ if errRet != nil {
+ for _, lfsPointer := range addedLfsPointers {
+ _, err := git_model.RemoveLFSMetaObjectByOid(ctx, repo.ID, lfsPointer.Oid)
+ if err != nil {
+ log.Error("ChangeRepoFiles: RemoveLFSMetaObjectByOid failed: %v", err)
+ }
+ }
+ }
+ }()
+
err := repo.MustNotBeArchived()
if err != nil {
return nil, err
}
- // If no branch name is set, assume default branch
+ // If no branch name is set, assume the default branch
if opts.OldBranch == "" {
opts.OldBranch = repo.DefaultBranch
}
@@ -126,14 +145,14 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
}
// Check that the path given in opts.treePath is valid (not a git path)
- treePath := CleanUploadFileName(file.TreePath)
+ treePath := CleanGitTreePath(file.TreePath)
if treePath == "" {
return nil, ErrFilenameInvalid{
Path: file.TreePath,
}
}
// If there is a fromTreePath (we are copying it), also clean it up
- fromTreePath := CleanUploadFileName(file.FromTreePath)
+ fromTreePath := CleanGitTreePath(file.FromTreePath)
if fromTreePath == "" && file.FromTreePath != "" {
return nil, ErrFilenameInvalid{
Path: file.FromTreePath,
@@ -203,13 +222,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
}
// Find the file we want to delete in the index
- inFilelist := false
- for _, indexFile := range filesInIndex {
- if indexFile == file.TreePath {
- inFilelist = true
- break
- }
- }
+ inFilelist := slices.Contains(filesInIndex, file.TreePath)
if !inFilelist {
return nil, ErrRepoFileDoesNotExist{
Path: file.TreePath,
@@ -225,7 +238,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
return nil, err // Couldn't get a commit for the branch
}
- // Assigned LastCommitID in opts if it hasn't been set
+ // Assigned LastCommitID in "opts" if it hasn't been set
if opts.LastCommitID == "" {
opts.LastCommitID = commit.ID.String()
} else {
@@ -237,22 +250,25 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
}
for _, file := range opts.Files {
- if err := handleCheckErrors(file, commit, opts); err != nil {
+ if err = handleCheckErrors(file, commit, opts); err != nil {
return nil, err
}
}
}
- contentStore := lfs.NewContentStore()
+ lfsContentStore := lfs.NewContentStore()
for _, file := range opts.Files {
switch file.Operation {
- case "create", "update":
- if err := CreateOrUpdateFile(ctx, t, file, contentStore, repo.ID, hasOldBranch); err != nil {
+ case "create", "update", "rename", "upload":
+ addedLfsPointer, err := modifyFile(ctx, t, file, lfsContentStore, repo.ID)
+ if err != nil {
return nil, err
}
+ if addedLfsPointer != nil {
+ addedLfsPointers = append(addedLfsPointers, *addedLfsPointer)
+ }
case "delete":
- // Remove the file from the index
- if err := t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil {
+ if err = t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil {
return nil, err
}
default:
@@ -299,14 +315,14 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
// FIXME: this call seems not right, why it needs to read the file content again
// FIXME: why it uses the NewBranch as "ref", it should use the commit ID because the response is only for this commit
- filesResponse, err := GetFilesResponseFromCommit(ctx, repo, utils.NewRefCommit(git.RefNameFromBranch(opts.NewBranch), commit), treePaths)
+ filesResponse, err := GetFilesResponseFromCommit(ctx, repo, gitRepo, utils.NewRefCommit(git.RefNameFromBranch(opts.NewBranch), commit), treePaths)
if err != nil {
return nil, err
}
if repo.IsEmpty {
if isEmpty, err := gitRepo.IsEmpty(); err == nil && !isEmpty {
- _ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
+ _ = repo_model.UpdateRepositoryColsWithAutoTime(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
}
}
@@ -372,22 +388,33 @@ func (err ErrSHAOrCommitIDNotProvided) Error() string {
// handles the check for various issues for ChangeRepoFiles
func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions) error {
- if file.Operation == "update" || file.Operation == "delete" {
- fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath)
- if err != nil {
- return err
+ // check old entry (fromTreePath/fromEntry)
+ if file.Operation == "update" || file.Operation == "upload" || file.Operation == "delete" || file.Operation == "rename" {
+ var fromEntryIDString string
+ {
+ fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath)
+ if file.Operation == "upload" && git.IsErrNotExist(err) {
+ fromEntry = nil
+ } else if err != nil {
+ return err
+ }
+ if fromEntry != nil {
+ fromEntryIDString = fromEntry.ID.String()
+ file.Options.executable = fromEntry.IsExecutable() // FIXME: legacy hacky approach, it shouldn't prepare the "Options" in the "check" function
+ }
}
+
if file.SHA != "" {
- // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
- if file.SHA != fromEntry.ID.String() {
+ // If the SHA given doesn't match the SHA of the fromTreePath, throw error
+ if file.SHA != fromEntryIDString {
return pull_service.ErrSHADoesNotMatch{
Path: file.Options.treePath,
GivenSHA: file.SHA,
- CurrentSHA: fromEntry.ID.String(),
+ CurrentSHA: fromEntryIDString,
}
}
} else if opts.LastCommitID != "" {
- // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
+ // If a lastCommitID given doesn't match the branch head's commitID throw
// an error, but only if we aren't creating a new branch.
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
if changed, err := commit.FileChangedSinceCommit(file.Options.treePath, opts.LastCommitID); err != nil {
@@ -405,13 +432,13 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep
// haven't been made. We throw an error if one wasn't provided.
return ErrSHAOrCommitIDNotProvided{}
}
- file.Options.executable = fromEntry.IsExecutable()
}
- if file.Operation == "create" || file.Operation == "update" {
- // For the path where this file will be created/updated, we need to make
- // sure no parts of the path are existing files or links except for the last
- // item in the path which is the file name, and that shouldn't exist IF it is
- // a new file OR is being moved to a new path.
+
+ // check new entry (treePath/treeEntry)
+ if file.Operation == "create" || file.Operation == "update" || file.Operation == "upload" || file.Operation == "rename" {
+ // For operation's target path, we need to make sure no parts of the path are existing files or links
+ // except for the last item in the path (which is the file name).
+ // And that shouldn't exist IF it is a new file OR is being moved to a new path.
treePathParts := strings.Split(file.Options.treePath, "/")
subTreePath := ""
for index, part := range treePathParts {
@@ -448,7 +475,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep
Type: git.EntryModeTree,
}
} else if file.Options.fromTreePath != file.Options.treePath || file.Operation == "create" {
- // The entry shouldn't exist if we are creating new file or moving to a new path
+ // The entry shouldn't exist if we are creating the new file or moving to a new path
return ErrRepoFileAlreadyExists{
Path: file.Options.treePath,
}
@@ -459,21 +486,23 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep
return nil
}
-// CreateOrUpdateFile handles creating or updating a file for ChangeRepoFiles
-func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile, contentStore *lfs.ContentStore, repoID int64, hasOldBranch bool) error {
+func modifyFile(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile, contentStore *lfs.ContentStore, repoID int64) (addedLfsPointer *lfs.Pointer, _ error) {
+ if rd, ok := file.ContentReader.(LazyReadSeeker); ok {
+ if err := rd.OpenLazyReader(); err != nil {
+ return nil, fmt.Errorf("OpenLazyReader: %w", err)
+ }
+ defer rd.Close()
+ }
+
// Get the two paths (might be the same if not moving) from the index if they exist
filesInIndex, err := t.LsFiles(ctx, file.TreePath, file.FromTreePath)
if err != nil {
- return fmt.Errorf("UpdateRepoFile: %w", err)
+ return nil, fmt.Errorf("LsFiles: %w", err)
}
// If is a new file (not updating) then the given path shouldn't exist
if file.Operation == "create" {
- for _, indexFile := range filesInIndex {
- if indexFile == file.TreePath {
- return ErrRepoFileAlreadyExists{
- Path: file.TreePath,
- }
- }
+ if slices.Contains(filesInIndex, file.TreePath) {
+ return nil, ErrRepoFileAlreadyExists{Path: file.TreePath}
}
}
@@ -481,78 +510,178 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
if file.Options.fromTreePath != file.Options.treePath && len(filesInIndex) > 0 {
for _, indexFile := range filesInIndex {
if indexFile == file.Options.fromTreePath {
- if err := t.RemoveFilesFromIndex(ctx, file.FromTreePath); err != nil {
- return err
+ if err = t.RemoveFilesFromIndex(ctx, file.FromTreePath); err != nil {
+ return nil, err
}
}
}
}
- treeObjectContentReader := file.ContentReader
- var lfsMetaObject *git_model.LFSMetaObject
- if setting.LFS.StartServer && hasOldBranch {
- // Check there is no way this can return multiple infos
- attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
- Attributes: []string{attribute.Filter},
- Filenames: []string{file.Options.treePath},
- })
+ var writeObjectRet *writeRepoObjectRet
+ switch file.Operation {
+ case "create", "update", "upload":
+ writeObjectRet, err = writeRepoObjectForModify(ctx, t, file)
+ case "rename":
+ writeObjectRet, err = writeRepoObjectForRename(ctx, t, file)
+ default:
+ return nil, util.NewInvalidArgumentErrorf("unknown file modification operation: '%s'", file.Operation)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // Add the object to the index, the "file.Options.executable" is set in handleCheckErrors by the caller (legacy hacky approach)
+ if err = t.AddObjectToIndex(ctx, util.Iif(file.Options.executable, "100755", "100644"), writeObjectRet.ObjectHash, file.Options.treePath); err != nil {
+ return nil, err
+ }
+
+ if writeObjectRet.LfsContent == nil {
+ return nil, nil // No LFS pointer, so nothing to do
+ }
+ defer writeObjectRet.LfsContent.Close()
+
+ // Now we must store the content into an LFS object
+ lfsMetaObject, err := git_model.NewLFSMetaObject(ctx, repoID, writeObjectRet.LfsPointer)
+ if err != nil {
+ return nil, err
+ }
+ exist, err := contentStore.Exists(lfsMetaObject.Pointer)
+ if err != nil {
+ return nil, err
+ }
+ if !exist {
+ err = contentStore.Put(lfsMetaObject.Pointer, writeObjectRet.LfsContent)
if err != nil {
- return err
+ if _, errRemove := git_model.RemoveLFSMetaObjectByOid(ctx, repoID, lfsMetaObject.Oid); errRemove != nil {
+ return nil, fmt.Errorf("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)", lfsMetaObject.Oid, errRemove, err)
+ }
+ return nil, err
}
+ }
+ return &lfsMetaObject.Pointer, nil
+}
+
+func checkIsLfsFileInGitAttributes(ctx context.Context, t *TemporaryUploadRepository, paths []string) (ret []bool, err error) {
+ attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
+ Attributes: []string{attribute.Filter},
+ Filenames: paths,
+ })
+ if err != nil {
+ return nil, err
+ }
+ for _, p := range paths {
+ isLFSFile := attributesMap[p] != nil && attributesMap[p].Get(attribute.Filter).ToString().Value() == "lfs"
+ ret = append(ret, isLFSFile)
+ }
+ return ret, nil
+}
- if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" {
- // OK so we are supposed to LFS this data!
- pointer, err := lfs.GeneratePointer(treeObjectContentReader)
+type writeRepoObjectRet struct {
+ ObjectHash string
+ LfsContent io.ReadCloser // if not nil, then the caller should store its content in LfsPointer, then close it
+ LfsPointer lfs.Pointer
+}
+
+// writeRepoObjectForModify hashes the git object for create or update operations
+func writeRepoObjectForModify(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile) (ret *writeRepoObjectRet, err error) {
+ ret = &writeRepoObjectRet{}
+ treeObjectContentReader := file.ContentReader
+ if setting.LFS.StartServer {
+ checkIsLfsFiles, err := checkIsLfsFileInGitAttributes(ctx, t, []string{file.Options.treePath})
+ if err != nil {
+ return nil, err
+ }
+ if checkIsLfsFiles[0] {
+ // OK, so we are supposed to LFS this data!
+ ret.LfsPointer, err = lfs.GeneratePointer(file.ContentReader)
if err != nil {
- return err
+ return nil, err
+ }
+ if _, err = file.ContentReader.Seek(0, io.SeekStart); err != nil {
+ return nil, err
}
- lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repoID}
- treeObjectContentReader = strings.NewReader(pointer.StringContent())
+ ret.LfsContent = io.NopCloser(file.ContentReader)
+ treeObjectContentReader = strings.NewReader(ret.LfsPointer.StringContent())
}
}
- // Add the object to the database
- objectHash, err := t.HashObject(ctx, treeObjectContentReader)
+ ret.ObjectHash, err = t.HashObjectAndWrite(ctx, treeObjectContentReader)
if err != nil {
- return err
+ return nil, err
}
+ return ret, nil
+}
- // Add the object to the index
- if file.Options.executable {
- if err := t.AddObjectToIndex(ctx, "100755", objectHash, file.Options.treePath); err != nil {
- return err
- }
- } else {
- if err := t.AddObjectToIndex(ctx, "100644", objectHash, file.Options.treePath); err != nil {
- return err
+// writeRepoObjectForRename the same as writeRepoObjectForModify buf for "rename"
+func writeRepoObjectForRename(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile) (ret *writeRepoObjectRet, err error) {
+ lastCommitID, err := t.GetLastCommit(ctx)
+ if err != nil {
+ return nil, err
+ }
+ commit, err := t.GetCommit(lastCommitID)
+ if err != nil {
+ return nil, err
+ }
+ oldEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath)
+ if err != nil {
+ return nil, err
+ }
+
+ ret = &writeRepoObjectRet{ObjectHash: oldEntry.ID.String()}
+ if !setting.LFS.StartServer {
+ return ret, nil
+ }
+
+ checkIsLfsFiles, err := checkIsLfsFileInGitAttributes(ctx, t, []string{file.Options.fromTreePath, file.Options.treePath})
+ if err != nil {
+ return nil, err
+ }
+ oldIsLfs, newIsLfs := checkIsLfsFiles[0], checkIsLfsFiles[1]
+
+ // If the old and new paths are both in lfs or both not in lfs, the object hash of the old file can be used directly
+ // as the object doesn't change
+ if oldIsLfs == newIsLfs {
+ return ret, nil
+ }
+
+ oldEntryBlobPointerBy := func(f func(r io.Reader) (lfs.Pointer, error)) (lfsPointer lfs.Pointer, err error) {
+ r, err := oldEntry.Blob().DataAsync()
+ if err != nil {
+ return lfsPointer, err
}
+ defer r.Close()
+ return f(r)
}
- if lfsMetaObject != nil {
- // We have an LFS object - create it
- lfsMetaObject, err = git_model.NewLFSMetaObject(ctx, lfsMetaObject.RepositoryID, lfsMetaObject.Pointer)
+ var treeObjectContentReader io.ReadCloser
+ if oldIsLfs {
+ // If the old is in lfs but the new isn't, read the content from lfs and add it as a normal git object
+ pointer, err := oldEntryBlobPointerBy(lfs.ReadPointer)
if err != nil {
- return err
+ return nil, err
}
- exist, err := contentStore.Exists(lfsMetaObject.Pointer)
+ treeObjectContentReader, err = lfs.ReadMetaObject(pointer)
if err != nil {
- return err
+ return nil, err
}
- if !exist {
- _, err := file.ContentReader.Seek(0, io.SeekStart)
- if err != nil {
- return err
- }
- if err := contentStore.Put(lfsMetaObject.Pointer, file.ContentReader); err != nil {
- if _, err2 := git_model.RemoveLFSMetaObjectByOid(ctx, repoID, lfsMetaObject.Oid); err2 != nil {
- return fmt.Errorf("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)", lfsMetaObject.Oid, err2, err)
- }
- return err
- }
+ defer treeObjectContentReader.Close()
+ } else {
+ // If the new is in lfs but the old isn't, read the content from the git object and generate a lfs pointer of it
+ ret.LfsPointer, err = oldEntryBlobPointerBy(lfs.GeneratePointer)
+ if err != nil {
+ return nil, err
}
+ ret.LfsContent, err = oldEntry.Blob().DataAsync()
+ if err != nil {
+ return nil, err
+ }
+ treeObjectContentReader = io.NopCloser(strings.NewReader(ret.LfsPointer.StringContent()))
}
-
- return nil
+ ret.ObjectHash, err = t.HashObjectAndWrite(ctx, treeObjectContentReader)
+ if err != nil {
+ return nil, err
+ }
+ return ret, nil
}
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go
index f348cb68ab..b783cbd01d 100644
--- a/services/repository/files/upload.go
+++ b/services/repository/files/upload.go
@@ -8,15 +8,11 @@ import (
"fmt"
"os"
"path"
- "strings"
+ "sync"
- git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/git/attribute"
- "code.gitea.io/gitea/modules/lfs"
- "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/log"
)
// UploadRepoFileOptions contains the uploaded repository file options
@@ -32,201 +28,84 @@ type UploadRepoFileOptions struct {
Committer *IdentityOptions
}
-type uploadInfo struct {
- upload *repo_model.Upload
- lfsMetaObject *git_model.LFSMetaObject
+type lazyLocalFileReader struct {
+ *os.File
+ localFilename string
+ counter int
+ mu sync.Mutex
}
-func cleanUpAfterFailure(ctx context.Context, infos *[]uploadInfo, t *TemporaryUploadRepository, original error) error {
- for _, info := range *infos {
- if info.lfsMetaObject == nil {
- continue
- }
- if !info.lfsMetaObject.Existing {
- if _, err := git_model.RemoveLFSMetaObjectByOid(ctx, t.repo.ID, info.lfsMetaObject.Oid); err != nil {
- original = fmt.Errorf("%w, %v", original, err) // We wrap the original error - as this is the underlying error that required the fallback
- }
- }
- }
- return original
-}
-
-// UploadRepoFiles uploads files to the given repository
-func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *UploadRepoFileOptions) error {
- if len(opts.Files) == 0 {
- return nil
- }
+var _ LazyReadSeeker = (*lazyLocalFileReader)(nil)
- uploads, err := repo_model.GetUploadsByUUIDs(ctx, opts.Files)
- if err != nil {
- return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %w", opts.Files, err)
- }
+func (l *lazyLocalFileReader) Close() error {
+ l.mu.Lock()
+ defer l.mu.Unlock()
- names := make([]string, len(uploads))
- infos := make([]uploadInfo, len(uploads))
- for i, upload := range uploads {
- // Check file is not lfs locked, will return nil if lock setting not enabled
- filepath := path.Join(opts.TreePath, upload.Name)
- lfsLock, err := git_model.GetTreePathLock(ctx, repo.ID, filepath)
- if err != nil {
- return err
- }
- if lfsLock != nil && lfsLock.OwnerID != doer.ID {
- u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
- if err != nil {
- return err
+ if l.counter > 0 {
+ l.counter--
+ if l.counter == 0 {
+ if err := l.File.Close(); err != nil {
+ return fmt.Errorf("close file %s: %w", l.localFilename, err)
}
- return git_model.ErrLFSFileLocked{RepoID: repo.ID, Path: filepath, UserName: u.Name}
- }
-
- names[i] = upload.Name
- infos[i] = uploadInfo{upload: upload}
- }
-
- t, err := NewTemporaryUploadRepository(repo)
- if err != nil {
- return err
- }
- defer t.Close()
-
- hasOldBranch := true
- if err = t.Clone(ctx, opts.OldBranch, true); err != nil {
- if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
- return err
- }
- if err = t.Init(ctx, repo.ObjectFormatName); err != nil {
- return err
- }
- hasOldBranch = false
- opts.LastCommitID = ""
- }
- if hasOldBranch {
- if err = t.SetDefaultIndex(ctx); err != nil {
- return err
- }
- }
-
- var attributesMap map[string]*attribute.Attributes
- if setting.LFS.StartServer {
- attributesMap, err = attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
- Attributes: []string{attribute.Filter},
- Filenames: names,
- })
- if err != nil {
- return err
+ l.File = nil
}
+ return nil
}
+ return fmt.Errorf("file %s already closed", l.localFilename)
+}
- // Copy uploaded files into repository.
- for i := range infos {
- if err := copyUploadedLFSFileIntoRepository(ctx, &infos[i], attributesMap, t, opts.TreePath); err != nil {
- return err
- }
- }
+func (l *lazyLocalFileReader) OpenLazyReader() error {
+ l.mu.Lock()
+ defer l.mu.Unlock()
- // Now write the tree
- treeHash, err := t.WriteTree(ctx)
- if err != nil {
- return err
+ if l.File != nil {
+ l.counter++
+ return nil
}
- // Now commit the tree
- commitOpts := &CommitTreeUserOptions{
- ParentCommitID: opts.LastCommitID,
- TreeHash: treeHash,
- CommitMessage: opts.Message,
- SignOff: opts.Signoff,
- DoerUser: doer,
- AuthorIdentity: opts.Author,
- CommitterIdentity: opts.Committer,
- }
- commitHash, err := t.CommitTree(ctx, commitOpts)
+ file, err := os.Open(l.localFilename)
if err != nil {
return err
}
+ l.File = file
+ l.counter = 1
+ return nil
+}
- // Now deal with LFS objects
- for i := range infos {
- if infos[i].lfsMetaObject == nil {
- continue
- }
- infos[i].lfsMetaObject, err = git_model.NewLFSMetaObject(ctx, infos[i].lfsMetaObject.RepositoryID, infos[i].lfsMetaObject.Pointer)
- if err != nil {
- // OK Now we need to cleanup
- return cleanUpAfterFailure(ctx, &infos, t, err)
- }
- // Don't move the files yet - we need to ensure that
- // everything can be inserted first
- }
-
- // OK now we can insert the data into the store - there's no way to clean up the store
- // once it's in there, it's in there.
- contentStore := lfs.NewContentStore()
- for _, info := range infos {
- if err := uploadToLFSContentStore(info, contentStore); err != nil {
- return cleanUpAfterFailure(ctx, &infos, t, err)
- }
- }
-
- // Then push this tree to NewBranch
- if err := t.Push(ctx, doer, commitHash, opts.NewBranch); err != nil {
- return err
+// UploadRepoFiles uploads files to the given repository
+func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *UploadRepoFileOptions) error {
+ if len(opts.Files) == 0 {
+ return nil
}
- return repo_model.DeleteUploads(ctx, uploads...)
-}
-
-func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, attributesMap map[string]*attribute.Attributes, t *TemporaryUploadRepository, treePath string) error {
- file, err := os.Open(info.upload.LocalPath())
+ uploads, err := repo_model.GetUploadsByUUIDs(ctx, opts.Files)
if err != nil {
- return err
+ return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %w", opts.Files, err)
}
- defer file.Close()
- var objectHash string
- if setting.LFS.StartServer && attributesMap[info.upload.Name] != nil && attributesMap[info.upload.Name].Get(attribute.Filter).ToString().Value() == "lfs" {
- // Handle LFS
- // FIXME: Inefficient! this should probably happen in models.Upload
- pointer, err := lfs.GeneratePointer(file)
- if err != nil {
- return err
- }
-
- info.lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: t.repo.ID}
-
- if objectHash, err = t.HashObject(ctx, strings.NewReader(pointer.StringContent())); err != nil {
- return err
- }
- } else if objectHash, err = t.HashObject(ctx, file); err != nil {
- return err
+ changeOpts := &ChangeRepoFilesOptions{
+ LastCommitID: opts.LastCommitID,
+ OldBranch: opts.OldBranch,
+ NewBranch: opts.NewBranch,
+ Message: opts.Message,
+ Signoff: opts.Signoff,
+ Author: opts.Author,
+ Committer: opts.Committer,
+ }
+ for _, upload := range uploads {
+ changeOpts.Files = append(changeOpts.Files, &ChangeRepoFile{
+ Operation: "upload",
+ TreePath: path.Join(opts.TreePath, upload.Name),
+ ContentReader: &lazyLocalFileReader{localFilename: upload.LocalPath()},
+ })
}
- // Add the object to the index
- return t.AddObjectToIndex(ctx, "100644", objectHash, path.Join(treePath, info.upload.Name))
-}
-
-func uploadToLFSContentStore(info uploadInfo, contentStore *lfs.ContentStore) error {
- if info.lfsMetaObject == nil {
- return nil
- }
- exist, err := contentStore.Exists(info.lfsMetaObject.Pointer)
+ _, err = ChangeRepoFiles(ctx, repo, doer, changeOpts)
if err != nil {
return err
}
- if !exist {
- file, err := os.Open(info.upload.LocalPath())
- if err != nil {
- return err
- }
-
- defer file.Close()
- // FIXME: Put regenerates the hash and copies the file over.
- // I guess this strictly ensures the soundness of the store but this is inefficient.
- if err := contentStore.Put(info.lfsMetaObject.Pointer, file); err != nil {
- // OK Now we need to cleanup
- // Can't clean up the store, once uploaded there they're there.
- return err
- }
+ if err := repo_model.DeleteUploads(ctx, uploads...); err != nil {
+ log.Error("DeleteUploads: %v", err)
}
return nil
}
diff --git a/services/repository/fork.go b/services/repository/fork.go
index c16c3d598a..8bd3498b17 100644
--- a/services/repository/fork.go
+++ b/services/repository/fork.go
@@ -124,7 +124,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
defer func() {
if err != nil {
// we can not use the ctx because it maybe canceled or timeout
- cleanupRepository(doer, repo.ID)
+ cleanupRepository(repo.ID)
}
}()
@@ -198,7 +198,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
// 8 - update repository status to be ready
repo.Status = repo_model.RepositoryReady
- if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
+ if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
}
@@ -209,7 +209,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
// ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo
func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Repository) error {
- err := db.WithTx(ctx, func(ctx context.Context) error {
+ return db.WithTx(ctx, func(ctx context.Context) error {
repo, err := repo_model.GetRepositoryByID(ctx, repo.ID)
if err != nil {
return err
@@ -226,16 +226,8 @@ func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Reposit
repo.IsFork = false
repo.ForkID = 0
-
- if err := updateRepository(ctx, repo, false); err != nil {
- log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err)
- return err
- }
-
- return nil
+ return repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_fork", "fork_id")
})
-
- return err
}
type findForksOptions struct {
diff --git a/services/repository/fork_test.go b/services/repository/fork_test.go
index 9edc0aa39f..5375f79028 100644
--- a/services/repository/fork_test.go
+++ b/services/repository/fork_test.go
@@ -13,6 +13,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
@@ -38,7 +39,7 @@ func TestForkRepository(t *testing.T) {
assert.False(t, repo_model.IsErrReachLimitOfRepo(err))
// change AllowForkWithoutMaximumLimit to false for the test
- setting.Repository.AllowForkWithoutMaximumLimit = false
+ defer test.MockVariableValue(&setting.Repository.AllowForkWithoutMaximumLimit, false)()
// user has reached maximum limit of repositories
user.MaxRepoCreation = 0
fork2, err := ForkRepository(git.DefaultContext, user, user, ForkRepoOptions{
@@ -68,7 +69,7 @@ func TestForkRepositoryCleanup(t *testing.T) {
assert.NoError(t, err)
assert.True(t, exist)
- err = DeleteRepositoryDirectly(db.DefaultContext, user2, fork.ID)
+ err = DeleteRepositoryDirectly(db.DefaultContext, fork.ID)
assert.NoError(t, err)
// a failed creating because some mock data
diff --git a/services/repository/generate.go b/services/repository/generate.go
index b02f7c9482..867b5d7855 100644
--- a/services/repository/generate.go
+++ b/services/repository/generate.go
@@ -42,10 +42,8 @@ type expansion struct {
var defaultTransformers = []transformer{
{Name: "SNAKE", Transform: xstrings.ToSnakeCase},
{Name: "KEBAB", Transform: xstrings.ToKebabCase},
- {Name: "CAMEL", Transform: func(str string) string {
- return xstrings.FirstRuneToLower(xstrings.ToCamelCase(str))
- }},
- {Name: "PASCAL", Transform: xstrings.ToCamelCase},
+ {Name: "CAMEL", Transform: xstrings.ToCamelCase},
+ {Name: "PASCAL", Transform: xstrings.ToPascalCase},
{Name: "LOWER", Transform: strings.ToLower},
{Name: "UPPER", Transform: strings.ToUpper},
{Name: "TITLE", Transform: util.ToTitleCase},
@@ -255,43 +253,35 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch)
}
-func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) {
- tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-" + repo.Name)
+// GenerateGitContent generates git content from a template repository
+func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) (err error) {
+ tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-" + generateRepo.Name)
if err != nil {
- return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err)
+ return fmt.Errorf("failed to create temp dir for repository %s: %w", generateRepo.FullName(), err)
}
defer cleanup()
- if err = generateRepoCommit(ctx, repo, templateRepo, generateRepo, tmpDir); err != nil {
+ if err = generateRepoCommit(ctx, generateRepo, templateRepo, generateRepo, tmpDir); err != nil {
return fmt.Errorf("generateRepoCommit: %w", err)
}
// re-fetch repo
- if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil {
+ if generateRepo, err = repo_model.GetRepositoryByID(ctx, generateRepo.ID); err != nil {
return fmt.Errorf("getRepositoryByID: %w", err)
}
// if there was no default branch supplied when generating the repo, use the default one from the template
- if strings.TrimSpace(repo.DefaultBranch) == "" {
- repo.DefaultBranch = templateRepo.DefaultBranch
+ if strings.TrimSpace(generateRepo.DefaultBranch) == "" {
+ generateRepo.DefaultBranch = templateRepo.DefaultBranch
}
- if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
+ if err = gitrepo.SetDefaultBranch(ctx, generateRepo, generateRepo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
- if err = UpdateRepository(ctx, repo, false); err != nil {
+ if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, generateRepo, "default_branch"); err != nil {
return fmt.Errorf("updateRepository: %w", err)
}
- return nil
-}
-
-// GenerateGitContent generates git content from a template repository
-func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error {
- if err := generateGitContent(ctx, generateRepo, templateRepo, generateRepo); err != nil {
- return err
- }
-
if err := repo_module.UpdateRepoSize(ctx, generateRepo); err != nil {
return fmt.Errorf("failed to update size for repository: %w", err)
}
diff --git a/services/repository/generate_test.go b/services/repository/generate_test.go
index b0f97d0ffb..1163c392c9 100644
--- a/services/repository/generate_test.go
+++ b/services/repository/generate_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var giteaTemplate = []byte(`
@@ -65,3 +66,26 @@ func TestFileNameSanitize(t *testing.T) {
assert.Equal(t, "_", fileNameSanitize("\u0000"))
assert.Equal(t, "目标", fileNameSanitize("目标"))
}
+
+func TestTransformers(t *testing.T) {
+ cases := []struct {
+ name string
+ expected string
+ }{
+ {"SNAKE", "abc_def_xyz"},
+ {"KEBAB", "abc-def-xyz"},
+ {"CAMEL", "abcDefXyz"},
+ {"PASCAL", "AbcDefXyz"},
+ {"LOWER", "abc_def-xyz"},
+ {"UPPER", "ABC_DEF-XYZ"},
+ {"TITLE", "Abc_def-Xyz"},
+ }
+
+ input := "Abc_Def-XYZ"
+ assert.Len(t, defaultTransformers, len(cases))
+ for i, c := range cases {
+ tf := defaultTransformers[i]
+ require.Equal(t, c.name, tf.Name)
+ assert.Equal(t, c.expected, tf.Transform(input), "case %s", c.name)
+ }
+}
diff --git a/services/repository/gitgraph/graph_models.go b/services/repository/gitgraph/graph_models.go
index c45662836b..02b0268cd9 100644
--- a/services/repository/gitgraph/graph_models.go
+++ b/services/repository/gitgraph/graph_models.go
@@ -121,7 +121,7 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_
return repo_model.IsOwnerMemberCollaborator(ctx, repository, user.ID)
}, &keyMap)
- statuses, _, err := git_model.GetLatestCommitStatus(ctx, repository.ID, c.Commit.ID.String(), db.ListOptions{})
+ statuses, err := git_model.GetLatestCommitStatus(ctx, repository.ID, c.Commit.ID.String(), db.ListOptionsAll)
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
} else {
@@ -232,8 +232,8 @@ func newRefsFromRefNames(refNames []byte) []git.Reference {
continue
}
refName := string(refNameBytes)
- if strings.HasPrefix(refName, "tag: ") {
- refName = strings.TrimPrefix(refName, "tag: ")
+ if after, ok := strings.CutPrefix(refName, "tag: "); ok {
+ refName = after
} else {
refName = strings.TrimPrefix(refName, "HEAD -> ")
}
diff --git a/services/repository/gitgraph/graph_test.go b/services/repository/gitgraph/graph_test.go
index 4c48b94aa2..93fa1aec6a 100644
--- a/services/repository/gitgraph/graph_test.go
+++ b/services/repository/gitgraph/graph_test.go
@@ -6,6 +6,7 @@ package gitgraph
import (
"bytes"
"fmt"
+ "slices"
"strings"
"testing"
@@ -117,13 +118,7 @@ func TestReleaseUnusedColors(t *testing.T) {
if parser.firstAvailable == -1 {
// All in use
for _, color := range parser.availableColors {
- found := false
- for _, oldColor := range parser.oldColors {
- if oldColor == color {
- found = true
- break
- }
- }
+ found := slices.Contains(parser.oldColors, color)
if !found {
t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not",
testcase.availableColors,
@@ -141,13 +136,7 @@ func TestReleaseUnusedColors(t *testing.T) {
// Some in use
for i := parser.firstInUse; i != parser.firstAvailable; i = (i + 1) % len(parser.availableColors) {
color := parser.availableColors[i]
- found := false
- for _, oldColor := range parser.oldColors {
- if oldColor == color {
- found = true
- break
- }
- }
+ found := slices.Contains(parser.oldColors, color)
if !found {
t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not",
testcase.availableColors,
@@ -163,13 +152,7 @@ func TestReleaseUnusedColors(t *testing.T) {
}
for i := parser.firstAvailable; i != parser.firstInUse; i = (i + 1) % len(parser.availableColors) {
color := parser.availableColors[i]
- found := false
- for _, oldColor := range parser.oldColors {
- if oldColor == color {
- found = true
- break
- }
- }
+ found := slices.Contains(parser.oldColors, color)
if found {
t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should not be available but is",
testcase.availableColors,
diff --git a/services/repository/init.go b/services/repository/init.go
index bd777b8a2f..1eeeb4aa4f 100644
--- a/services/repository/init.go
+++ b/services/repository/init.go
@@ -42,9 +42,12 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
cmd := git.NewCommand("commit", "--message=Initial commit").
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)
- sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
+ sign, key, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
if sign {
- cmd.AddOptionFormat("-S%s", keyID)
+ if key.Format != "" {
+ cmd.AddConfig("gpg.format", key.Format)
+ }
+ cmd.AddOptionFormat("-S%s", key.KeyID)
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
// need to set the committer to the KeyID owner
diff --git a/services/repository/merge_upstream.go b/services/repository/merge_upstream.go
index 34e01df723..8d6f11372c 100644
--- a/services/repository/merge_upstream.go
+++ b/services/repository/merge_upstream.go
@@ -18,7 +18,7 @@ import (
)
// MergeUpstream merges the base repository's default branch into the fork repository's current branch.
-func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
+func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, branch string, ffOnly bool) (mergeStyle string, err error) {
if err = repo.MustNotBeArchived(); err != nil {
return "", err
}
@@ -45,6 +45,11 @@ func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_
return "", err
}
+ // If ff_only is requested and fast-forward failed, return error
+ if ffOnly {
+ return "", util.NewInvalidArgumentErrorf("fast-forward merge not possible: branch has diverged")
+ }
+
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
fakeIssue := &issue_model.Issue{
diff --git a/services/repository/migrate.go b/services/repository/migrate.go
index 003be1a9ab..0a3dc45339 100644
--- a/services/repository/migrate.go
+++ b/services/repository/migrate.go
@@ -149,9 +149,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
}
+ // if releases migration are not requested, we will sync all tags here
+ // otherwise, the releases sync will be done out of this function
if !opts.Releases {
- // note: this will greatly improve release (tag) sync
- // for pull-mirrors with many tags
repo.IsMirror = opts.Mirror
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
log.Error("Failed to synchronize tags to releases for repository: %v", err)
@@ -220,10 +220,14 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
}
repo.IsMirror = true
- if err = UpdateRepository(ctx, repo, false); err != nil {
+ if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "num_watches", "is_empty", "default_branch", "default_wiki_branch", "is_mirror"); err != nil {
return nil, err
}
+ if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
+ log.Error("Failed to update size for repository: %v", err)
+ }
+
// this is necessary for sync local tags from remote
configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName())
if stdout, _, err := git.NewCommand("config").
diff --git a/services/repository/push.go b/services/repository/push.go
index ba801ad019..af3c873d15 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -232,7 +232,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
}
if len(addTags)+len(delTags) > 0 {
- if err := PushUpdateAddDeleteTags(ctx, repo, gitRepo, addTags, delTags); err != nil {
+ if err := PushUpdateAddDeleteTags(ctx, repo, gitRepo, pusher, addTags, delTags); err != nil {
return fmt.Errorf("PushUpdateAddDeleteTags: %w", err)
}
}
@@ -283,7 +283,7 @@ func pushNewBranch(ctx context.Context, repo *repo_model.Repository, pusher *use
}
}
// Update the is empty and default_branch columns
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_branch", "is_empty"); err != nil {
+ if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "default_branch", "is_empty"); err != nil {
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
}
}
@@ -342,17 +342,17 @@ func pushDeleteBranch(ctx context.Context, repo *repo_model.Repository, pusher *
}
// PushUpdateAddDeleteTags updates a number of added and delete tags
-func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, addTags, delTags []string) error {
+func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, pusher *user_model.User, addTags, delTags []string) error {
return db.WithTx(ctx, func(ctx context.Context) error {
- if err := repo_model.PushUpdateDeleteTagsContext(ctx, repo, delTags); err != nil {
+ if err := repo_model.PushUpdateDeleteTags(ctx, repo, delTags); err != nil {
return err
}
- return pushUpdateAddTags(ctx, repo, gitRepo, addTags)
+ return pushUpdateAddTags(ctx, repo, gitRepo, pusher, addTags)
})
}
// pushUpdateAddTags updates a number of add tags
-func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, tags []string) error {
+func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, pusher *user_model.User, tags []string) error {
if len(tags) == 0 {
return nil
}
@@ -378,8 +378,6 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
newReleases := make([]*repo_model.Release, 0, len(lowerTags)-len(relMap))
- emailToUser := make(map[string]*user_model.User)
-
for i, lowerTag := range lowerTags {
tag, err := gitRepo.GetTag(tags[i])
if err != nil {
@@ -397,29 +395,12 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
if sig == nil {
sig = commit.Committer
}
- var author *user_model.User
- createdAt := time.Unix(1, 0)
+ createdAt := time.Unix(1, 0)
if sig != nil {
- var ok bool
- author, ok = emailToUser[sig.Email]
- if !ok {
- author, err = user_model.GetUserByEmail(ctx, sig.Email)
- if err != nil && !user_model.IsErrUserNotExist(err) {
- return fmt.Errorf("GetUserByEmail: %w", err)
- }
- if author != nil {
- emailToUser[sig.Email] = author
- }
- }
createdAt = sig.When
}
- commitsCount, err := commit.CommitsCount()
- if err != nil {
- return fmt.Errorf("CommitsCount: %w", err)
- }
-
rel, has := relMap[lowerTag]
parts := strings.SplitN(tag.Message, "\n", 2)
@@ -435,31 +416,26 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
LowerTagName: lowerTag,
Target: "",
Sha1: commit.ID.String(),
- NumCommits: commitsCount,
+ NumCommits: -1, // the commits count will be updated when the UI needs it
Note: note,
IsDraft: false,
IsPrerelease: false,
IsTag: true,
+ PublisherID: pusher.ID,
CreatedUnix: timeutil.TimeStamp(createdAt.Unix()),
}
- if author != nil {
- rel.PublisherID = author.ID
- }
newReleases = append(newReleases, rel)
} else {
rel.Sha1 = commit.ID.String()
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
- rel.NumCommits = commitsCount
if rel.IsTag {
rel.Title = parts[0]
rel.Note = note
- if author != nil {
- rel.PublisherID = author.ID
- }
} else {
rel.IsDraft = false
}
+ rel.PublisherID = pusher.ID
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
return fmt.Errorf("Update: %w", err)
}
diff --git a/services/repository/repository.go b/services/repository/repository.go
index e078a8fc3c..e574dc6c01 100644
--- a/services/repository/repository.go
+++ b/services/repository/repository.go
@@ -69,7 +69,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod
notify_service.DeleteRepository(ctx, doer, repo)
}
- return DeleteRepositoryDirectly(ctx, doer, repo.ID)
+ return DeleteRepositoryDirectly(ctx, repo.ID)
}
// PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace
@@ -127,9 +127,42 @@ func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibili
func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) {
return db.WithTx(ctx, func(ctx context.Context) error {
repo.IsPrivate = false
- if err = updateRepository(ctx, repo, true); err != nil {
- return fmt.Errorf("MakeRepoPublic: %w", err)
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
+ return err
+ }
+
+ if err = repo.LoadOwner(ctx); err != nil {
+ return fmt.Errorf("LoadOwner: %w", err)
}
+ if repo.Owner.IsOrganization() {
+ // Organization repository need to recalculate access table when visibility is changed.
+ if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
+ return fmt.Errorf("recalculateTeamAccesses: %w", err)
+ }
+ }
+
+ // Create/Remove git-daemon-export-ok for git-daemon...
+ if err := checkDaemonExportOK(ctx, repo); err != nil {
+ return err
+ }
+
+ forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
+ if err != nil {
+ return fmt.Errorf("getRepositoriesByForkID: %w", err)
+ }
+
+ if repo.Owner.Visibility != structs.VisibleTypePrivate {
+ for i := range forkRepos {
+ if err = MakeRepoPublic(ctx, forkRepos[i]); err != nil {
+ return fmt.Errorf("MakeRepoPublic[%d]: %w", forkRepos[i].ID, err)
+ }
+ }
+ }
+
+ // If visibility is changed, we need to update the issue indexer.
+ // Since the data in the issue indexer have field to indicate if the repo is public or not.
+ issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
+
return nil
})
}
@@ -137,9 +170,51 @@ func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error
func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) {
return db.WithTx(ctx, func(ctx context.Context) error {
repo.IsPrivate = true
- if err = updateRepository(ctx, repo, true); err != nil {
- return fmt.Errorf("MakeRepoPrivate: %w", err)
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
+ return err
+ }
+
+ if err = repo.LoadOwner(ctx); err != nil {
+ return fmt.Errorf("LoadOwner: %w", err)
}
+ if repo.Owner.IsOrganization() {
+ // Organization repository need to recalculate access table when visibility is changed.
+ if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
+ return fmt.Errorf("recalculateTeamAccesses: %w", err)
+ }
+ }
+
+ // If repo has become private, we need to set its actions to private.
+ _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{
+ IsPrivate: true,
+ })
+ if err != nil {
+ return err
+ }
+
+ if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
+ return err
+ }
+
+ // Create/Remove git-daemon-export-ok for git-daemon...
+ if err := checkDaemonExportOK(ctx, repo); err != nil {
+ return err
+ }
+
+ forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
+ if err != nil {
+ return fmt.Errorf("getRepositoriesByForkID: %w", err)
+ }
+ for i := range forkRepos {
+ if err = MakeRepoPrivate(ctx, forkRepos[i]); err != nil {
+ return fmt.Errorf("MakeRepoPrivate[%d]: %w", forkRepos[i].ID, err)
+ }
+ }
+
+ // If visibility is changed, we need to update the issue indexer.
+ // Since the data in the issue indexer have field to indicate if the repo is public or not.
+ issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
+
return nil
})
}
@@ -205,7 +280,7 @@ func updateRepository(ctx context.Context, repo *repo_model.Repository, visibili
e := db.GetEngine(ctx)
- if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
+ if _, err = e.ID(repo.ID).NoAutoTime().AllCols().Update(repo); err != nil {
return fmt.Errorf("update: %w", err)
}
diff --git a/services/repository/template.go b/services/repository/template.go
index 95f585cead..6906a60083 100644
--- a/services/repository/template.go
+++ b/services/repository/template.go
@@ -102,7 +102,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
defer func() {
if err != nil {
// we can not use the ctx because it maybe canceled or timeout
- cleanupRepository(doer, generateRepo.ID)
+ cleanupRepository(generateRepo.ID)
}
}()
@@ -184,7 +184,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
// 6 - update repository status to be ready
generateRepo.Status = repo_model.RepositoryReady
- if err = repo_model.UpdateRepositoryCols(ctx, generateRepo, "status"); err != nil {
+ if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, generateRepo, "status"); err != nil {
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
}
diff --git a/services/repository/transfer.go b/services/repository/transfer.go
index 86917ad285..5ad63cca67 100644
--- a/services/repository/transfer.go
+++ b/services/repository/transfer.go
@@ -160,7 +160,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
repo.OwnerName = newOwner.Name
// Update repository.
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "owner_id", "owner_name"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "owner_id", "owner_name"); err != nil {
return fmt.Errorf("update owner: %w", err)
}
@@ -304,7 +304,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
return fmt.Errorf("deleteRepositoryTransfer: %w", err)
}
repo.Status = repo_model.RepositoryReady
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
return err
}
@@ -495,7 +495,7 @@ func RejectRepositoryTransfer(ctx context.Context, repo *repo_model.Repository,
}
repo.Status = repo_model.RepositoryReady
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
return err
}
@@ -543,7 +543,7 @@ func CancelRepositoryTransfer(ctx context.Context, repoTransfer *repo_model.Repo
}
repoTransfer.Repo.Status = repo_model.RepositoryReady
- if err := repo_model.UpdateRepositoryCols(ctx, repoTransfer.Repo, "status"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repoTransfer.Repo, "status"); err != nil {
return err
}
diff --git a/services/user/update.go b/services/user/update.go
index 4a39f4f783..d7354542bf 100644
--- a/services/user/update.go
+++ b/services/user/update.go
@@ -15,6 +15,26 @@ import (
"code.gitea.io/gitea/modules/structs"
)
+type UpdateOptionField[T any] struct {
+ FieldValue T
+ FromSync bool
+}
+
+func UpdateOptionFieldFromValue[T any](value T) optional.Option[UpdateOptionField[T]] {
+ return optional.Some(UpdateOptionField[T]{FieldValue: value})
+}
+
+func UpdateOptionFieldFromSync[T any](value T) optional.Option[UpdateOptionField[T]] {
+ return optional.Some(UpdateOptionField[T]{FieldValue: value, FromSync: true})
+}
+
+func UpdateOptionFieldFromPtr[T any](value *T) optional.Option[UpdateOptionField[T]] {
+ if value == nil {
+ return optional.None[UpdateOptionField[T]]()
+ }
+ return UpdateOptionFieldFromValue(*value)
+}
+
type UpdateOptions struct {
KeepEmailPrivate optional.Option[bool]
FullName optional.Option[string]
@@ -32,7 +52,7 @@ type UpdateOptions struct {
DiffViewStyle optional.Option[string]
AllowCreateOrganization optional.Option[bool]
IsActive optional.Option[bool]
- IsAdmin optional.Option[bool]
+ IsAdmin optional.Option[UpdateOptionField[bool]]
EmailNotificationsPreference optional.Option[string]
SetLastLogin bool
RepoAdminChangeTeamAccess optional.Option[bool]
@@ -111,13 +131,18 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
cols = append(cols, "is_restricted")
}
if opts.IsAdmin.Has() {
- if !opts.IsAdmin.Value() && user_model.IsLastAdminUser(ctx, u) {
- return user_model.ErrDeleteLastAdminUser{UID: u.ID}
+ if opts.IsAdmin.Value().FieldValue /* true */ {
+ u.IsAdmin = opts.IsAdmin.Value().FieldValue // set IsAdmin=true
+ cols = append(cols, "is_admin")
+ } else if !user_model.IsLastAdminUser(ctx, u) /* not the last admin */ {
+ u.IsAdmin = opts.IsAdmin.Value().FieldValue // it's safe to change it from false to true (not the last admin)
+ cols = append(cols, "is_admin")
+ } else /* IsAdmin=false but this is the last admin user */ { //nolint:gocritic // make it easier to read
+ if !opts.IsAdmin.Value().FromSync {
+ return user_model.ErrDeleteLastAdminUser{UID: u.ID}
+ }
+ // else: syncing from external-source, this user is the last admin, so skip the "IsAdmin=false" change
}
-
- u.IsAdmin = opts.IsAdmin.Value()
-
- cols = append(cols, "is_admin")
}
if opts.Visibility.Has() {
diff --git a/services/user/update_test.go b/services/user/update_test.go
index fc24a6c212..27513e8040 100644
--- a/services/user/update_test.go
+++ b/services/user/update_test.go
@@ -22,7 +22,11 @@ func TestUpdateUser(t *testing.T) {
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
assert.Error(t, UpdateUser(db.DefaultContext, admin, &UpdateOptions{
- IsAdmin: optional.Some(false),
+ IsAdmin: UpdateOptionFieldFromValue(false),
+ }))
+
+ assert.NoError(t, UpdateUser(db.DefaultContext, admin, &UpdateOptions{
+ IsAdmin: UpdateOptionFieldFromSync(false),
}))
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})
@@ -38,7 +42,7 @@ func TestUpdateUser(t *testing.T) {
MaxRepoCreation: optional.Some(10),
IsRestricted: optional.Some(true),
IsActive: optional.Some(false),
- IsAdmin: optional.Some(true),
+ IsAdmin: UpdateOptionFieldFromValue(true),
Visibility: optional.Some(structs.VisibleTypePrivate),
KeepActivityPrivate: optional.Some(true),
Language: optional.Some("lang"),
@@ -60,7 +64,7 @@ func TestUpdateUser(t *testing.T) {
assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation)
assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted)
assert.Equal(t, opts.IsActive.Value(), user.IsActive)
- assert.Equal(t, opts.IsAdmin.Value(), user.IsAdmin)
+ assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin)
assert.Equal(t, opts.Visibility.Value(), user.Visibility)
assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate)
assert.Equal(t, opts.Language.Value(), user.Language)
@@ -80,7 +84,7 @@ func TestUpdateUser(t *testing.T) {
assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation)
assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted)
assert.Equal(t, opts.IsActive.Value(), user.IsActive)
- assert.Equal(t, opts.IsAdmin.Value(), user.IsAdmin)
+ assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin)
assert.Equal(t, opts.Visibility.Value(), user.Visibility)
assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate)
assert.Equal(t, opts.Language.Value(), user.Language)
diff --git a/services/user/user.go b/services/user/user.go
index 1aeebff142..c7252430de 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/agit"
asymkey_service "code.gitea.io/gitea/services/asymkey"
@@ -177,8 +178,8 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
PageSize: repo_model.RepositoryListDefaultPageSize,
Page: 1,
},
- UserID: u.ID,
- IncludePrivate: true,
+ UserID: u.ID,
+ IncludeVisibility: structs.VisibleTypePrivate,
})
if err != nil {
return fmt.Errorf("unable to find org list for %s[%d]. Error: %w", u.Name, u.ID, err)
diff --git a/services/versioned_migration/migration.go b/services/versioned_migration/migration.go
index daec89d7c1..b66d853531 100644
--- a/services/versioned_migration/migration.go
+++ b/services/versioned_migration/migration.go
@@ -1,7 +1,7 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package versioned_migration //nolint
+package versioned_migration
import (
"context"
diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go
index ce907bf0cb..5bbc610fe5 100644
--- a/services/webhook/dingtalk.go
+++ b/services/webhook/dingtalk.go
@@ -176,6 +176,12 @@ func (dc dingtalkConvertor) Status(p *api.CommitStatusPayload) (DingtalkPayload,
return createDingtalkPayload(text, text, "Status Changed", p.TargetURL), nil
}
+func (dingtalkConvertor) WorkflowRun(p *api.WorkflowRunPayload) (DingtalkPayload, error) {
+ text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)
+
+ return createDingtalkPayload(text, text, "Workflow Run", p.WorkflowRun.HTMLURL), nil
+}
+
func (dingtalkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DingtalkPayload, error) {
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
diff --git a/services/webhook/discord.go b/services/webhook/discord.go
index 0e8a9aa67c..0426964181 100644
--- a/services/webhook/discord.go
+++ b/services/webhook/discord.go
@@ -278,6 +278,12 @@ func (d discordConvertor) Status(p *api.CommitStatusPayload) (DiscordPayload, er
return d.createPayload(p.Sender, text, "", p.TargetURL, color), nil
}
+func (d discordConvertor) WorkflowRun(p *api.WorkflowRunPayload) (DiscordPayload, error) {
+ text, color := getWorkflowRunPayloadInfo(p, noneLinkFormatter, false)
+
+ return d.createPayload(p.Sender, text, "", p.WorkflowRun.HTMLURL, color), nil
+}
+
func (d discordConvertor) WorkflowJob(p *api.WorkflowJobPayload) (DiscordPayload, error) {
text, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
@@ -305,7 +311,7 @@ func parseHookPullRequestEventType(event webhook_module.HookEventType) (string,
case webhook_module.HookEventPullRequestReviewApproved:
return "approved", nil
case webhook_module.HookEventPullRequestReviewRejected:
- return "rejected", nil
+ return "requested changes", nil
case webhook_module.HookEventPullRequestReviewComment:
return "comment", nil
default:
diff --git a/services/webhook/feishu.go b/services/webhook/feishu.go
index 274aaf90b3..b6ee80c44c 100644
--- a/services/webhook/feishu.go
+++ b/services/webhook/feishu.go
@@ -5,9 +5,13 @@ package webhook
import (
"context"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
"fmt"
"net/http"
"strings"
+ "time"
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
@@ -16,10 +20,12 @@ import (
)
type (
- // FeishuPayload represents
+ // FeishuPayload represents the payload for Feishu webhook
FeishuPayload struct {
- MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive / file /audio / media
- Content struct {
+ Timestamp int64 `json:"timestamp,omitempty"` // Unix timestamp for signature verification
+ Sign string `json:"sign,omitempty"` // Signature for verification
+ MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive / file /audio / media
+ Content struct {
Text string `json:"text"`
} `json:"content"`
}
@@ -172,15 +178,41 @@ func (fc feishuConvertor) Status(p *api.CommitStatusPayload) (FeishuPayload, err
return newFeishuTextPayload(text), nil
}
+func (feishuConvertor) WorkflowRun(p *api.WorkflowRunPayload) (FeishuPayload, error) {
+ text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)
+
+ return newFeishuTextPayload(text), nil
+}
+
func (feishuConvertor) WorkflowJob(p *api.WorkflowJobPayload) (FeishuPayload, error) {
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
return newFeishuTextPayload(text), nil
}
+// feishuGenSign generates a signature for Feishu webhook
+// https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
+func feishuGenSign(secret string, timestamp int64) string {
+ // key="{timestamp}\n{secret}", then hmac-sha256, then base64 encode
+ stringToSign := fmt.Sprintf("%d\n%s", timestamp, secret)
+ h := hmac.New(sha256.New, []byte(stringToSign))
+ return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
+
func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
- var pc payloadConvertor[FeishuPayload] = feishuConvertor{}
- return newJSONRequest(pc, w, t, true)
+ payload, err := newPayload(feishuConvertor{}, []byte(t.PayloadContent), t.EventType)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Add timestamp and signature if secret is provided
+ if w.Secret != "" {
+ timestamp := time.Now().Unix()
+ payload.Timestamp = timestamp
+ payload.Sign = feishuGenSign(w.Secret, timestamp)
+ }
+
+ return prepareJSONRequest(payload, w, t, false /* no default headers */)
}
func init() {
diff --git a/services/webhook/feishu_test.go b/services/webhook/feishu_test.go
index c4249bdb30..7e200ea132 100644
--- a/services/webhook/feishu_test.go
+++ b/services/webhook/feishu_test.go
@@ -168,6 +168,7 @@ func TestFeishuJSONPayload(t *testing.T) {
URL: "https://feishu.example.com/",
Meta: `{}`,
HTTPMethod: "POST",
+ Secret: "secret",
}
task := &webhook_model.HookTask{
HookID: hook.ID,
@@ -183,10 +184,13 @@ func TestFeishuJSONPayload(t *testing.T) {
assert.Equal(t, "POST", req.Method)
assert.Equal(t, "https://feishu.example.com/", req.URL.String())
- assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
var body FeishuPayload
err = json.NewDecoder(req.Body).Decode(&body)
assert.NoError(t, err)
assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", body.Content.Text)
+ assert.Equal(t, feishuGenSign(hook.Secret, body.Timestamp), body.Sign)
+
+ // a separate sign test, the result is generated by official python code, so the algo must be correct
+ assert.Equal(t, "rWZ84lcag1x9aBFhn1gtV4ZN+4gme3pilfQNMk86vKg=", feishuGenSign("a", 1))
}
diff --git a/services/webhook/general.go b/services/webhook/general.go
index 251659e75e..be457e46f5 100644
--- a/services/webhook/general.go
+++ b/services/webhook/general.go
@@ -327,6 +327,37 @@ func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatte
return text, color
}
+func getWorkflowRunPayloadInfo(p *api.WorkflowRunPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
+ description := p.WorkflowRun.Conclusion
+ if description == "" {
+ description = p.WorkflowRun.Status
+ }
+ refLink := linkFormatter(p.WorkflowRun.HTMLURL, fmt.Sprintf("%s(#%d)", p.WorkflowRun.DisplayTitle, p.WorkflowRun.ID)+"["+base.ShortSha(p.WorkflowRun.HeadSha)+"]:"+description)
+
+ text = fmt.Sprintf("Workflow Run %s: %s", p.Action, refLink)
+ switch description {
+ case "waiting":
+ color = orangeColor
+ case "queued":
+ color = orangeColorLight
+ case "success":
+ color = greenColor
+ case "failure":
+ color = redColor
+ case "cancelled":
+ color = yellowColor
+ case "skipped":
+ color = purpleColor
+ default:
+ color = greyColor
+ }
+ if withSender {
+ text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)
+ }
+
+ return text, color
+}
+
func getWorkflowJobPayloadInfo(p *api.WorkflowJobPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
description := p.WorkflowJob.Conclusion
if description == "" {
diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go
index 5bc7ba097e..3e9163f78c 100644
--- a/services/webhook/matrix.go
+++ b/services/webhook/matrix.go
@@ -252,6 +252,12 @@ func (m matrixConvertor) Status(p *api.CommitStatusPayload) (MatrixPayload, erro
return m.newPayload(text)
}
+func (m matrixConvertor) WorkflowRun(p *api.WorkflowRunPayload) (MatrixPayload, error) {
+ text, _ := getWorkflowRunPayloadInfo(p, htmlLinkFormatter, true)
+
+ return m.newPayload(text)
+}
+
func (m matrixConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MatrixPayload, error) {
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go
index 07d28c3867..450a544b42 100644
--- a/services/webhook/msteams.go
+++ b/services/webhook/msteams.go
@@ -318,6 +318,20 @@ func (m msteamsConvertor) Status(p *api.CommitStatusPayload) (MSTeamsPayload, er
), nil
}
+func (msteamsConvertor) WorkflowRun(p *api.WorkflowRunPayload) (MSTeamsPayload, error) {
+ title, color := getWorkflowRunPayloadInfo(p, noneLinkFormatter, false)
+
+ return createMSTeamsPayload(
+ p.Repo,
+ p.Sender,
+ title,
+ "",
+ p.WorkflowRun.HTMLURL,
+ color,
+ &MSTeamsFact{"WorkflowRun:", p.WorkflowRun.DisplayTitle},
+ ), nil
+}
+
func (msteamsConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MSTeamsPayload, error) {
title, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go
index 9e3f21de29..672abd5c95 100644
--- a/services/webhook/notifier.go
+++ b/services/webhook/notifier.go
@@ -5,10 +5,8 @@ package webhook
import (
"context"
- "fmt"
actions_model "code.gitea.io/gitea/models/actions"
- "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
@@ -18,6 +16,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repository"
@@ -295,6 +294,43 @@ func (m *webhookNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu
}
}
+func (m *webhookNotifier) DeleteIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) {
+ permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
+ if issue.IsPull {
+ if err := issue.LoadPullRequest(ctx); err != nil {
+ log.Error("LoadPullRequest: %v", err)
+ return
+ }
+ if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{
+ Action: api.HookIssueDeleted,
+ Index: issue.Index,
+ PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
+ Repository: convert.ToRepo(ctx, issue.Repo, permission),
+ Sender: convert.ToUser(ctx, doer, nil),
+ }); err != nil {
+ log.Error("PrepareWebhooks: %v", err)
+ }
+ } else {
+ if err := issue.LoadRepo(ctx); err != nil {
+ log.Error("issue.LoadRepo: %v", err)
+ return
+ }
+ if err := issue.LoadPoster(ctx); err != nil {
+ log.Error("issue.LoadPoster: %v", err)
+ return
+ }
+ if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{
+ Action: api.HookIssueDeleted,
+ Index: issue.Index,
+ Issue: convert.ToAPIIssue(ctx, issue.Poster, issue),
+ Repository: convert.ToRepo(ctx, issue.Repo, permission),
+ Sender: convert.ToUser(ctx, doer, nil),
+ }); err != nil {
+ log.Error("PrepareWebhooks: %v", err)
+ }
+ }
+}
+
func (m *webhookNotifier) NewPullRequest(ctx context.Context, pull *issues_model.PullRequest, mentions []*user_model.User) {
if err := pull.LoadIssue(ctx); err != nil {
log.Error("pull.LoadIssue: %v", err)
@@ -956,72 +992,61 @@ func (*webhookNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_
org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
}
- err := job.LoadAttributes(ctx)
+ status, _ := convert.ToActionsStatus(job.Status)
+
+ convertedJob, err := convert.ToActionWorkflowJob(ctx, repo, task, job)
if err != nil {
- log.Error("Error loading job attributes: %v", err)
+ log.Error("ToActionWorkflowJob: %v", err)
return
}
- jobIndex := 0
- jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
+ if err := PrepareWebhooks(ctx, source, webhook_module.HookEventWorkflowJob, &api.WorkflowJobPayload{
+ Action: status,
+ WorkflowJob: convertedJob,
+ Organization: org,
+ Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
+ Sender: convert.ToUser(ctx, sender, nil),
+ }); err != nil {
+ log.Error("PrepareWebhooks: %v", err)
+ }
+}
+
+func (*webhookNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
+ source := EventSource{
+ Repository: repo,
+ Owner: repo.Owner,
+ }
+
+ var org *api.Organization
+ if repo.Owner.IsOrganization() {
+ org = convert.ToOrganization(ctx, organization.OrgFromUser(repo.Owner))
+ }
+
+ status := convert.ToWorkflowRunAction(run.Status)
+
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
- log.Error("Error loading getting run jobs: %v", err)
+ log.Error("OpenRepository: %v", err)
return
}
- for i, j := range jobs {
- if j.ID == job.ID {
- jobIndex = i
- break
- }
- }
+ defer gitRepo.Close()
- status, conclusion := toActionStatus(job.Status)
- var runnerID int64
- var runnerName string
- var steps []*api.ActionWorkflowStep
+ convertedWorkflow, err := convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
+ if err != nil {
+ log.Error("GetActionWorkflow: %v", err)
+ return
+ }
- if task != nil {
- runnerID = task.RunnerID
- if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
- runnerName = runner.Name
- }
- for i, step := range task.Steps {
- stepStatus, stepConclusion := toActionStatus(job.Status)
- steps = append(steps, &api.ActionWorkflowStep{
- Name: step.Name,
- Number: int64(i),
- Status: stepStatus,
- Conclusion: stepConclusion,
- StartedAt: step.Started.AsTime().UTC(),
- CompletedAt: step.Stopped.AsTime().UTC(),
- })
- }
+ convertedRun, err := convert.ToActionWorkflowRun(ctx, repo, run)
+ if err != nil {
+ log.Error("ToActionWorkflowRun: %v", err)
+ return
}
- if err := PrepareWebhooks(ctx, source, webhook_module.HookEventWorkflowJob, &api.WorkflowJobPayload{
- Action: status,
- WorkflowJob: &api.ActionWorkflowJob{
- ID: job.ID,
- // missing api endpoint for this location
- URL: fmt.Sprintf("%s/actions/runs/%d/jobs/%d", repo.APIURL(), job.RunID, job.ID),
- HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), jobIndex),
- RunID: job.RunID,
- // Missing api endpoint for this location, artifacts are available under a nested url
- RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID),
- Name: job.Name,
- Labels: job.RunsOn,
- RunAttempt: job.Attempt,
- HeadSha: job.Run.CommitSHA,
- HeadBranch: git.RefName(job.Run.Ref).BranchName(),
- Status: status,
- Conclusion: conclusion,
- RunnerID: runnerID,
- RunnerName: runnerName,
- Steps: steps,
- CreatedAt: job.Created.AsTime().UTC(),
- StartedAt: job.Started.AsTime().UTC(),
- CompletedAt: job.Stopped.AsTime().UTC(),
- },
+ if err := PrepareWebhooks(ctx, source, webhook_module.HookEventWorkflowRun, &api.WorkflowRunPayload{
+ Action: status,
+ Workflow: convertedWorkflow,
+ WorkflowRun: convertedRun,
Organization: org,
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
Sender: convert.ToUser(ctx, sender, nil),
@@ -1029,29 +1054,3 @@ func (*webhookNotifier) WorkflowJobStatusUpdate(ctx context.Context, repo *repo_
log.Error("PrepareWebhooks: %v", err)
}
}
-
-func toActionStatus(status actions_model.Status) (string, string) {
- var action string
- var conclusion string
- switch status {
- // This is a naming conflict of the webhook between Gitea and GitHub Actions
- case actions_model.StatusWaiting:
- action = "queued"
- case actions_model.StatusBlocked:
- action = "waiting"
- case actions_model.StatusRunning:
- action = "in_progress"
- }
- if status.IsDone() {
- action = "completed"
- switch status {
- case actions_model.StatusSuccess:
- conclusion = "success"
- case actions_model.StatusCancelled:
- conclusion = "cancelled"
- case actions_model.StatusFailure:
- conclusion = "failure"
- }
- }
- return action, conclusion
-}
diff --git a/services/webhook/packagist.go b/services/webhook/packagist.go
index 8829d95da6..e6a00b0293 100644
--- a/services/webhook/packagist.go
+++ b/services/webhook/packagist.go
@@ -114,6 +114,10 @@ func (pc packagistConvertor) Status(_ *api.CommitStatusPayload) (PackagistPayloa
return PackagistPayload{}, nil
}
+func (pc packagistConvertor) WorkflowRun(_ *api.WorkflowRunPayload) (PackagistPayload, error) {
+ return PackagistPayload{}, nil
+}
+
func (pc packagistConvertor) WorkflowJob(_ *api.WorkflowJobPayload) (PackagistPayload, error) {
return PackagistPayload{}, nil
}
diff --git a/services/webhook/payloader.go b/services/webhook/payloader.go
index adb7243fb1..b607bf3250 100644
--- a/services/webhook/payloader.go
+++ b/services/webhook/payloader.go
@@ -29,6 +29,7 @@ type payloadConvertor[T any] interface {
Wiki(*api.WikiPayload) (T, error)
Package(*api.PackagePayload) (T, error)
Status(*api.CommitStatusPayload) (T, error)
+ WorkflowRun(*api.WorkflowRunPayload) (T, error)
WorkflowJob(*api.WorkflowJobPayload) (T, error)
}
@@ -81,6 +82,8 @@ func newPayload[T any](rc payloadConvertor[T], data []byte, event webhook_module
return convertUnmarshalledJSON(rc.Package, data)
case webhook_module.HookEventStatus:
return convertUnmarshalledJSON(rc.Status, data)
+ case webhook_module.HookEventWorkflowRun:
+ return convertUnmarshalledJSON(rc.WorkflowRun, data)
case webhook_module.HookEventWorkflowJob:
return convertUnmarshalledJSON(rc.WorkflowJob, data)
}
@@ -92,7 +95,10 @@ func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t *
if err != nil {
return nil, nil, err
}
+ return prepareJSONRequest(payload, w, t, withDefaultHeaders)
+}
+func prepareJSONRequest[T any](payload T, w *webhook_model.Webhook, t *webhook_model.HookTask, withDefaultHeaders bool) (*http.Request, []byte, error) {
body, err := json.MarshalIndent(payload, "", " ")
if err != nil {
return nil, nil, err
diff --git a/services/webhook/slack.go b/services/webhook/slack.go
index 589ef3fe9b..3d645a55d0 100644
--- a/services/webhook/slack.go
+++ b/services/webhook/slack.go
@@ -173,6 +173,12 @@ func (s slackConvertor) Status(p *api.CommitStatusPayload) (SlackPayload, error)
return s.createPayload(text, nil), nil
}
+func (s slackConvertor) WorkflowRun(p *api.WorkflowRunPayload) (SlackPayload, error) {
+ text, _ := getWorkflowRunPayloadInfo(p, SlackLinkFormatter, true)
+
+ return s.createPayload(text, nil), nil
+}
+
func (s slackConvertor) WorkflowJob(p *api.WorkflowJobPayload) (SlackPayload, error) {
text, _ := getWorkflowJobPayloadInfo(p, SlackLinkFormatter, true)
diff --git a/services/webhook/telegram.go b/services/webhook/telegram.go
index ca74eabe1c..fdd428b45c 100644
--- a/services/webhook/telegram.go
+++ b/services/webhook/telegram.go
@@ -180,6 +180,12 @@ func (t telegramConvertor) Status(p *api.CommitStatusPayload) (TelegramPayload,
return createTelegramPayloadHTML(text), nil
}
+func (telegramConvertor) WorkflowRun(p *api.WorkflowRunPayload) (TelegramPayload, error) {
+ text, _ := getWorkflowRunPayloadInfo(p, htmlLinkFormatter, true)
+
+ return createTelegramPayloadHTML(text), nil
+}
+
func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload, error) {
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
@@ -189,7 +195,7 @@ func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
// https://core.telegram.org/bots/api#formatting-options
return TelegramPayload{
- Message: strings.TrimSpace(markup.Sanitize(msgHTML)),
+ Message: strings.TrimSpace(string(markup.Sanitize(msgHTML))),
ParseMode: "HTML",
DisableWebPreview: true,
}
diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go
index 6bac02712b..5a805347e3 100644
--- a/services/webhook/webhook_test.go
+++ b/services/webhook/webhook_test.go
@@ -13,6 +13,7 @@ import (
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/convert"
@@ -84,7 +85,8 @@ func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) {
func TestWebhookUserMail(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
- setting.Service.NoReplyAddress = "no-reply.com"
+ defer test.MockVariableValue(&setting.Service.NoReplyAddress, "no-reply.com")()
+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
assert.Equal(t, user.GetPlaceholderEmail(), convert.ToUser(db.DefaultContext, user, nil).Email)
assert.Equal(t, user.Email, convert.ToUser(db.DefaultContext, user, user).Email)
diff --git a/services/webhook/wechatwork.go b/services/webhook/wechatwork.go
index 2b19822caf..1875317406 100644
--- a/services/webhook/wechatwork.go
+++ b/services/webhook/wechatwork.go
@@ -181,6 +181,12 @@ func (wc wechatworkConvertor) Status(p *api.CommitStatusPayload) (WechatworkPayl
return newWechatworkMarkdownPayload(text), nil
}
+func (wc wechatworkConvertor) WorkflowRun(p *api.WorkflowRunPayload) (WechatworkPayload, error) {
+ text, _ := getWorkflowRunPayloadInfo(p, noneLinkFormatter, true)
+
+ return newWechatworkMarkdownPayload(text), nil
+}
+
func (wc wechatworkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (WechatworkPayload, error) {
text, _ := getWorkflowJobPayloadInfo(p, noneLinkFormatter, true)
diff --git a/services/webtheme/webtheme.go b/services/webtheme/webtheme.go
index 58aea3bc74..4e89d6dbac 100644
--- a/services/webtheme/webtheme.go
+++ b/services/webtheme/webtheme.go
@@ -70,11 +70,11 @@ func parseThemeMetaInfoToMap(cssContent string) map[string]string {
m := map[string]string{}
for _, item := range matchedItems {
v := item[3]
- if strings.HasPrefix(v, `"`) {
- v = strings.TrimSuffix(strings.TrimPrefix(v, `"`), `"`)
+ if after, ok := strings.CutPrefix(v, `"`); ok {
+ v = strings.TrimSuffix(after, `"`)
v = strings.ReplaceAll(v, `\"`, `"`)
- } else if strings.HasPrefix(v, `'`) {
- v = strings.TrimSuffix(strings.TrimPrefix(v, `'`), `'`)
+ } else if after, ok := strings.CutPrefix(v, `'`); ok {
+ v = strings.TrimSuffix(after, `'`)
v = strings.ReplaceAll(v, `\'`, `'`)
}
m[item[2]] = v
diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go
index 45a08dc5d6..0a955406e2 100644
--- a/services/wiki/wiki.go
+++ b/services/wiki/wiki.go
@@ -194,7 +194,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, doer)
if sign {
- commitTreeOpts.KeyID = signingKey
+ commitTreeOpts.Key = signingKey
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
committer = signer
}
@@ -316,7 +316,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, doer)
if sign {
- commitTreeOpts.KeyID = signingKey
+ commitTreeOpts.Key = signingKey
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
committer = signer
}
@@ -365,7 +365,7 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
}
return db.WithTx(ctx, func(ctx context.Context) error {
repo.DefaultWikiBranch = newBranch
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_wiki_branch"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "default_wiki_branch"); err != nil {
return fmt.Errorf("unable to update database: %w", err)
}
diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go
index f441c2939b..6ea3ca9c1b 100644
--- a/services/wiki/wiki_test.go
+++ b/services/wiki/wiki_test.go
@@ -116,9 +116,9 @@ func TestGitPathToWebPath(t *testing.T) {
func TestUserWebGitPathConsistency(t *testing.T) {
maxLen := 20
b := make([]byte, maxLen)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
l := rand.Intn(maxLen)
- for j := 0; j < l; j++ {
+ for j := range l {
r := rand.Intn(0x80-0x20) + 0x20
b[j] = byte(r)
}
diff --git a/tailwind.config.js b/tailwind.config.js
index fe285432f3..fa233a9814 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -29,11 +29,10 @@ export default {
important: true, // the frameworks are mixed together, so tailwind needs to override other framework's styles
content: [
isProduction && '!./templates/devtest/**/*',
- isProduction && '!./web_src/js/standalone/devtest.js',
+ isProduction && '!./web_src/js/standalone/devtest.ts',
'!./templates/swagger/v1_json.tmpl',
'!./templates/user/auth/oidc_wellknown.tmpl',
'!**/*_test.go',
- '!./modules/{public,options,templates}/bindata.go',
'./{build,models,modules,routers,services}/**/*.go',
'./templates/**/*.tmpl',
'./web_src/js/**/*.{ts,js,vue}',
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 15683307ed..781f514af4 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -15,7 +15,7 @@
</div>
<div class="required inline field {{if .Err_Name}}error{{end}}">
<label for="auth_name">{{ctx.Locale.Tr "admin.auths.auth_name"}}</label>
- <input id="auth_name" name="name" value="{{.Source.Name}}" autofocus required>
+ <input id="auth_name" name="name" value="{{.Source.Name}}" required>
</div>
<div class="inline field">
<div class="ui checkbox">
@@ -408,7 +408,10 @@
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "admin.auths.update"}}</button>
- <button class="ui red button delete-button" data-url="{{$.Link}}/delete" data-id="{{.Source.ID}}">{{ctx.Locale.Tr "admin.auths.delete"}}</button>
+ <button class="ui red button link-action" data-url="{{$.Link}}/delete?id={{.Source.ID}}"
+ data-modal-confirm-header="{{ctx.Locale.Tr "admin.auths.delete_auth_title"}}"
+ data-modal-confirm-content="{{ctx.Locale.Tr "admin.auths.delete_auth_desc"}}"
+ >{{ctx.Locale.Tr "admin.auths.delete"}}</button>
</div>
</form>
</div>
@@ -424,16 +427,4 @@
<p class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general.tip"}} <b id="oauth2-callback-url"></b></p>
</div>
</div>
-
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "admin.auths.delete_auth_title"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "admin.auths.delete_auth_desc"}}</p>
- </div>
- {{template "base/modal_actions_confirm" .}}
-</div>
-
{{template "admin/layout_footer" .}}
diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl
index 0c6889b599..4817f2681b 100644
--- a/templates/admin/packages/list.tmpl
+++ b/templates/admin/packages/list.tmpl
@@ -72,7 +72,12 @@
</td>
<td>{{FileSize .CalculateBlobSize}}</td>
<td>{{DateUtils.AbsoluteShort .Version.CreatedUnix}}</td>
- <td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.Version.ID}}" data-name="{{.Package.Name}}" data-data-version="{{.Version.Version}}">{{svg "octicon-trash"}}</a></td>
+ <td>
+ <a class="text red show-modal" href data-modal="#admin-package-delete-modal"
+ data-modal-form.action="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}&id={{.Version.ID}}"
+ data-modal-package-name="{{.Package.Name}}" data-modal-package-version="{{.Version.Version}}"
+ >{{svg "octicon-trash"}}</a>
+ </td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="10">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
@@ -84,15 +89,13 @@
{{template "base/paginate" .}}
</div>
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "packages.settings.delete"}}
- </div>
+<form class="ui small modal form-fetch-action" method="post" id="admin-package-delete-modal">
+ {{.CsrfTokenHtml}}
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "packages.settings.delete"}}</div>
<div class="content">
- {{ctx.Locale.Tr "packages.settings.delete.notice" (`<span class="name"></span>`|SafeHTML) (`<span class="dataVersion"></span>`|SafeHTML)}}
+ {{ctx.Locale.Tr "packages.settings.delete.notice" (HTMLFormat `<span class="%s"></span>` "package-name") (HTMLFormat `<span class="%s"></span>` "package-version")}}
</div>
{{template "base/modal_actions_confirm" .}}
-</div>
+</form>
{{template "admin/layout_footer" .}}
diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl
index 762013af47..2ad2de3a1d 100644
--- a/templates/admin/repo/list.tmpl
+++ b/templates/admin/repo/list.tmpl
@@ -7,7 +7,7 @@
</div>
</h4>
<div class="ui attached segment">
- {{template "shared/repo_search" .}}
+ {{template "shared/repo/search" .}}
</div>
<div class="ui attached table segment">
<table class="ui very basic striped table unstackable">
@@ -84,7 +84,12 @@
<td>{{FileSize .LFSSize}}</td>
<td>{{DateUtils.AbsoluteShort .UpdatedUnix}}</td>
<td>{{DateUtils.AbsoluteShort .CreatedUnix}}</td>
- <td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.ID}}" data-name="{{.Name}}">{{svg "octicon-trash"}}</a></td>
+ <td>
+ <a class="text red show-modal" href data-modal="#admin-repo-delete-modal"
+ data-modal-form.action="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}&id={{.ID}}"
+ data-modal-repo-name="{{.Name}}"
+ >{{svg "octicon-trash"}}</a>
+ </td>
</tr>
{{else}}
<tr><td class="tw-text-center" colspan="12">{{ctx.Locale.Tr "no_results_found"}}</td></tr>
@@ -96,17 +101,15 @@
{{template "base/paginate" .}}
</div>
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.settings.delete"}}
- </div>
+<form class="ui small modal form-fetch-action" id="admin-repo-delete-modal" method="post">
+ {{.CsrfTokenHtml}}
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.settings.delete"}}</div>
<div class="content">
<p>{{ctx.Locale.Tr "repo.settings.delete_desc"}}</p>
- {{ctx.Locale.Tr "repo.settings.delete_notices_2" (`<span class="name"></span>`|SafeHTML)}}<br>
+ {{ctx.Locale.Tr "repo.settings.delete_notices_2" (HTMLFormat `<span class="%s"></span>` "repo-name")}}<br>
{{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}<br>
</div>
{{template "base/modal_actions_confirm" .}}
-</div>
+</form>
{{template "admin/layout_footer" .}}
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index c04d332660..879b5cb550 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -9,7 +9,7 @@
{{.CsrfTokenHtml}}
<div class="field {{if .Err_UserName}}error{{end}}">
<label for="user_name">{{ctx.Locale.Tr "username"}}</label>
- <input id="user_name" name="user_name" value="{{.User.Name}}" autofocus {{if not .User.IsLocal}}disabled{{end}} maxlength="40">
+ <input id="user_name" name="user_name" value="{{.User.Name}}" {{if not .User.IsLocal}}disabled{{end}} maxlength="40">
</div>
<!-- Types and name -->
<div class="inline required field {{if .Err_LoginType}}error{{end}}">
@@ -55,7 +55,7 @@
<div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .User.LoginSource 0}}tw-hidden{{end}}">
<label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label>
- <input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus>
+ <input id="login_name" name="login_name" value="{{.User.LoginName}}">
</div>
<div class="field {{if .Err_FullName}}error{{end}}">
<label for="full_name">{{ctx.Locale.Tr "settings.full_name"}}</label>
@@ -63,7 +63,7 @@
</div>
<div class="required field {{if .Err_Email}}error{{end}}">
<label for="email">{{ctx.Locale.Tr "email"}}</label>
- <input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required>
+ <input id="email" name="email" type="email" value="{{.User.Email}}" required>
</div>
<div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}tw-hidden{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label>
diff --git a/templates/admin/user/view.tmpl b/templates/admin/user/view.tmpl
index 31616ffbf9..67f9148e64 100644
--- a/templates/admin/user/view.tmpl
+++ b/templates/admin/user/view.tmpl
@@ -26,7 +26,7 @@
{{ctx.Locale.Tr "admin.repositories"}} ({{ctx.Locale.Tr "admin.total" .ReposTotal}})
</h4>
<div class="ui attached segment">
- {{template "explore/repo_list" .}}
+ {{template "shared/repo/list" .}}
</div>
<h4 class="ui top attached header">
{{ctx.Locale.Tr "settings.organization"}} ({{ctx.Locale.Tr "admin.total" .OrgsTotal}})
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl
index 35e14d38d3..b721779c95 100644
--- a/templates/base/head_navbar.tmpl
+++ b/templates/base/head_navbar.tmpl
@@ -1,11 +1,3 @@
-{{$notificationUnreadCount := 0}}
-{{if and .IsSigned .NotificationUnreadCount}}
- {{$notificationUnreadCount = call .NotificationUnreadCount ctx}}
-{{end}}
-{{$activeStopwatch := NIL}}
-{{if and .IsSigned EnableTimetracking .GetActiveStopwatch}}
- {{$activeStopwatch = call .GetActiveStopwatch ctx}}
-{{end}}
<nav id="navbar" aria-label="{{ctx.Locale.Tr "aria.navbar"}}">
<div class="navbar-left">
<!-- the logo -->
@@ -15,22 +7,7 @@
<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
<div class="ui secondary menu navbar-mobile-right only-mobile">
- {{if $activeStopwatch}}
- <a id="mobile-stopwatch-icon" class="active-stopwatch item" href="{{$activeStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}" data-seconds="{{$activeStopwatch.Seconds}}">
- <div class="tw-relative">
- {{svg "octicon-stopwatch"}}
- <span class="header-stopwatch-dot"></span>
- </div>
- </a>
- {{end}}
- {{if .IsSigned}}
- <a id="mobile-notifications-icon" class="item" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
- <div class="tw-relative">
- {{svg "octicon-bell"}}
- <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
- </div>
- </a>
- {{end}}
+ {{template "base/head_navbar_icons" dict "PageGlobalData" .PageGlobalData}}
<button class="item ui icon mini button tw-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "home.nav_menu"}}">{{svg "octicon-three-bars"}}</button>
</div>
@@ -85,22 +62,7 @@
</div><!-- end content avatar menu -->
</div><!-- end dropdown avatar menu -->
{{else if .IsSigned}}
- {{if $activeStopwatch}}
- <a class="item not-mobile active-stopwatch" href="{{$activeStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}" data-seconds="{{$activeStopwatch.Seconds}}">
- <div class="tw-relative">
- {{svg "octicon-stopwatch"}}
- <span class="header-stopwatch-dot"></span>
- </div>
- </a>
- {{end}}
-
- <a class="item not-mobile" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
- <div class="tw-relative">
- {{svg "octicon-bell"}}
- <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
- </div>
- </a>
-
+ {{template "base/head_navbar_icons" dict "ItemExtraClass" "not-mobile" "PageGlobalData" .PageGlobalData}}
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}">
<span class="text">
{{svg "octicon-plus"}}
@@ -130,8 +92,6 @@
<span class="only-mobile">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span>
- {{/* do not localize it, here it needs the fixed length (width) to make UI comfortable */}}
- {{if .IsAdmin}}<span class="navbar-profile-admin">admin</span>{{end}}
<div class="menu user-menu">
<div class="header">
{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
@@ -160,14 +120,6 @@
{{svg "octicon-question"}}
{{ctx.Locale.Tr "help"}}
</a>
- {{if .IsAdmin}}
- <div class="divider"></div>
- <a class="{{if .PageIsAdmin}}active {{end}}item" href="{{AppSubUrl}}/-/admin">
- {{svg "octicon-server"}}
- {{ctx.Locale.Tr "admin_panel"}}
- </a>
- {{end}}
-
<div class="divider"></div>
<a class="item link-action" href data-url="{{AppSubUrl}}/user/logout">
{{svg "octicon-sign-out"}}
@@ -189,6 +141,7 @@
{{end}}
</div><!-- end full right menu -->
+ {{$activeStopwatch := and .PageGlobalData (call .PageGlobalData.GetActiveStopwatch)}}
{{if $activeStopwatch}}
<div class="active-stopwatch-popup tippy-target">
<div class="tw-flex tw-items-center tw-gap-2 tw-p-3">
@@ -197,7 +150,7 @@
<span class="stopwatch-issue">{{$activeStopwatch.RepoSlug}}#{{$activeStopwatch.IssueIndex}}</span>
</a>
<div class="tw-flex tw-gap-1">
- <form class="stopwatch-commit form-fetch-action" method="post" action="{{$activeStopwatch.IssueLink}}/times/stopwatch/toggle">
+ <form class="stopwatch-commit form-fetch-action" method="post" action="{{$activeStopwatch.IssueLink}}/times/stopwatch/stop">
{{.CsrfTokenHtml}}
<button
type="submit"
diff --git a/templates/base/head_navbar_icons.tmpl b/templates/base/head_navbar_icons.tmpl
new file mode 100644
index 0000000000..e3bdd27992
--- /dev/null
+++ b/templates/base/head_navbar_icons.tmpl
@@ -0,0 +1,25 @@
+{{- $itemExtraClass := .ItemExtraClass -}}
+{{- $data := .PageGlobalData -}}
+{{if and $data $data.IsSigned}}{{/* data may not exist, for example: rendering 503 page before the PageGlobalData middleware */}}
+ {{- $activeStopwatch := call $data.GetActiveStopwatch -}}
+ {{- $notificationUnreadCount := call $data.GetNotificationUnreadCount -}}
+ {{if $activeStopwatch}}
+ <a class="item active-stopwatch {{$itemExtraClass}}" href="{{$activeStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}" data-seconds="{{$activeStopwatch.Seconds}}">
+ <div class="tw-relative">
+ {{svg "octicon-stopwatch"}}
+ <span class="header-stopwatch-dot"></span>
+ </div>
+ </a>
+ {{end}}
+ <a class="item {{$itemExtraClass}}" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}">
+ <div class="tw-relative">
+ {{svg "octicon-bell"}}
+ <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
+ </div>
+ </a>
+ {{if $data.IsSiteAdmin}}
+ <a class="item {{$itemExtraClass}}" href="{{AppSubUrl}}/-/admin" data-tooltip-content="{{ctx.Locale.Tr "admin_panel"}}">
+ {{svg "octicon-server"}}
+ </a>
+ {{end}}
+{{end}}
diff --git a/templates/devtest/flex-list.tmpl b/templates/devtest/flex-list.tmpl
index 11d71d56a9..0db84b0c59 100644
--- a/templates/devtest/flex-list.tmpl
+++ b/templates/devtest/flex-list.tmpl
@@ -68,7 +68,7 @@
<a class="text primary" href="{{$.Link}}">
gitea-org / gitea
</a>
- <span data-tooltip-content="{{ctx.Locale.Tr "repo.fork"}}">{{svg "octicon-repo-forked"}}</span>
+ <span class="flex-text-inline" data-tooltip-content="{{ctx.Locale.Tr "repo.fork"}}">{{svg "octicon-repo-forked"}}</span>
</div>
<div class="flex-item-trailing">
<a class="muted" href="{{$.Link}}">
diff --git a/templates/explore/repos.tmpl b/templates/explore/repos.tmpl
index 53742bf0d9..68da398306 100644
--- a/templates/explore/repos.tmpl
+++ b/templates/explore/repos.tmpl
@@ -2,8 +2,8 @@
<div role="main" aria-label="{{.Title}}" class="page-content explore repositories">
{{template "explore/navbar" .}}
<div class="ui container">
- {{template "shared/repo_search" .}}
- {{template "explore/repo_list" .}}
+ {{template "shared/repo/search" .}}
+ {{template "shared/repo/list" .}}
{{template "base/paginate" .}}
</div>
</div>
diff --git a/templates/home.tmpl b/templates/home.tmpl
index 116dc487dc..cc9da82605 100644
--- a/templates/home.tmpl
+++ b/templates/home.tmpl
@@ -4,10 +4,10 @@
<div class="center">
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
<div class="hero">
- <h1 class="ui icon header title">
+ <h1 class="ui icon header title tw-text-balance">
{{AppName}}
</h1>
- <h2>{{ctx.Locale.Tr "startpage.app_desc"}}</h2>
+ <h2 class="tw-text-balance">{{ctx.Locale.Tr "startpage.app_desc"}}</h2>
</div>
</div>
</div>
@@ -16,7 +16,7 @@
<h1 class="hero ui icon header">
{{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
</h1>
- <p class="large">
+ <p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.install_desc" "https://docs.gitea.com/installation/install-from-binary" "https://github.com/go-gitea/gitea/tree/master/docker" "https://docs.gitea.com/installation/install-from-package"}}
</p>
</div>
@@ -24,7 +24,7 @@
<h1 class="hero ui icon header">
{{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}}
</h1>
- <p class="large">
+ <p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.platform_desc" "https://go.dev/"}}
</p>
</div>
@@ -34,7 +34,7 @@
<h1 class="hero ui icon header">
{{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}}
</h1>
- <p class="large">
+ <p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.lightweight_desc"}}
</p>
</div>
@@ -42,7 +42,7 @@
<h1 class="hero ui icon header">
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
</h1>
- <p class="large">
+ <p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.license_desc" "https://code.gitea.io/gitea" "code.gitea.io/gitea" "https://github.com/go-gitea/gitea"}}
</p>
</div>
diff --git a/templates/org/header.tmpl b/templates/org/header.tmpl
index 80519361fd..90798b5d7c 100644
--- a/templates/org/header.tmpl
+++ b/templates/org/header.tmpl
@@ -18,7 +18,7 @@
{{end}}
</span>
</div>
- {{if .RenderedDescription}}<div class="render-content markup tw-break-anywhere">{{.RenderedDescription}}</div>{{end}}
+ {{if .RenderedDescription}}<div class="render-content markup">{{.RenderedDescription}}</div>{{end}}
<div class="text light meta tw-mt-1">
{{if .Org.Location}}<div class="flex-text-block">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}}
{{if .Org.Website}}<div class="flex-text-block">{{svg "octicon-link"}} <a class="muted" target="_blank" rel="noopener noreferrer me" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}}
diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl
index cffdfabfaa..3cde3554c9 100644
--- a/templates/org/home.tmpl
+++ b/templates/org/home.tmpl
@@ -8,8 +8,8 @@
{{if .ProfileReadmeContent}}
<div id="readme_profile" class="render-content markup" data-profile-view-as-member="{{.IsViewingOrgAsMember}}">{{.ProfileReadmeContent}}</div>
{{end}}
- {{template "shared/repo_search" .}}
- {{template "explore/repo_list" .}}
+ {{template "shared/repo/search" .}}
+ {{template "shared/repo/list" .}}
{{template "base/paginate" .}}
</div>
diff --git a/templates/org/member/members.tmpl b/templates/org/member/members.tmpl
index 4388dc9520..2d0f4bc423 100644
--- a/templates/org/member/members.tmpl
+++ b/templates/org/member/members.tmpl
@@ -73,7 +73,7 @@
{{ctx.Locale.Tr "org.members.leave"}}
</div>
<div class="content">
- <p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|SafeHTML)}}</p>
+ <p>{{ctx.Locale.Tr "org.members.leave.detail" (HTMLFormat `<span class="%s"></span>` "dataOrganizationName")}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
@@ -82,7 +82,7 @@
{{ctx.Locale.Tr "org.members.remove"}}
</div>
<div class="content">
- <p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|SafeHTML) (`<span class="dataOrganizationName"></span>`|SafeHTML)}}</p>
+ <p>{{ctx.Locale.Tr "org.members.remove.detail" (HTMLFormat `<span class="%s"></span>` "name") (HTMLFormat `<span class="%s"></span>` "dataOrganizationName")}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/org/settings/delete.tmpl b/templates/org/settings/delete.tmpl
deleted file mode 100644
index e1ef471e34..0000000000
--- a/templates/org/settings/delete.tmpl
+++ /dev/null
@@ -1,35 +0,0 @@
-{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings delete")}}
-
- <div class="org-setting-content">
- <h4 class="ui top attached error header">
- {{ctx.Locale.Tr "org.settings.delete_account"}}
- </h4>
- <div class="ui attached error segment">
- <div class="ui red message">
- <p class="text left">{{svg "octicon-alert"}} {{ctx.Locale.Tr "org.settings.delete_prompt"}}</p>
- </div>
- <form class="ui form ignore-dirty" id="delete-form" action="{{.Link}}" method="post">
- {{.CsrfTokenHtml}}
- <div class="inline required field {{if .Err_OrgName}}error{{end}}">
- <label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}}</label>
- <input id="org_name" name="org_name" value="" autocomplete="off" autofocus required>
- </div>
- <button class="ui red button delete-button" data-type="form" data-form="#delete-form">
- {{ctx.Locale.Tr "org.settings.confirm_delete_account"}}
- </button>
- </form>
- </div>
- </div>
-
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "org.settings.delete_org_title"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "org.settings.delete_org_desc"}}</p>
- </div>
- {{template "base/modal_actions_confirm" .}}
-</div>
-
-{{template "org/settings/layout_footer" .}}
diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl
index ce792f667c..58475de7e7 100644
--- a/templates/org/settings/navbar.tmpl
+++ b/templates/org/settings/navbar.tmpl
@@ -41,8 +41,5 @@
</div>
</details>
{{end}}
- <a class="{{if .PageIsSettingsDelete}}active {{end}}item" href="{{.OrgLink}}/settings/delete">
- {{ctx.Locale.Tr "org.settings.delete"}}
- </a>
</div>
</div>
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl
index 76315f3eac..d94bb4c62b 100644
--- a/templates/org/settings/options.tmpl
+++ b/templates/org/settings/options.tmpl
@@ -1,101 +1,97 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings options")}}
- <div class="org-setting-content">
- <h4 class="ui top attached header">
- {{ctx.Locale.Tr "org.settings.options"}}
- </h4>
- <div class="ui attached segment">
- <form class="ui form" action="{{.Link}}" method="post">
- {{.CsrfTokenHtml}}
- <div class="required field {{if .Err_Name}}error{{end}}">
- <label for="org_name">{{ctx.Locale.Tr "org.org_name_holder"}}
- <span class="text red tw-hidden" id="org-name-change-prompt">
- <br>{{ctx.Locale.Tr "org.settings.change_orgname_prompt"}}<br>{{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}}
- </span>
- </label>
- <input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" autofocus required maxlength="40">
- </div>
- <div class="field {{if .Err_FullName}}error{{end}}">
- <label for="full_name">{{ctx.Locale.Tr "org.org_full_name_holder"}}</label>
- <input id="full_name" name="full_name" value="{{.Org.FullName}}" maxlength="100">
- </div>
- <div class="field {{if .Err_Email}}error{{end}}">
- <label for="email">{{ctx.Locale.Tr "org.settings.email"}}</label>
- <input id="email" name="email" type="email" value="{{.Org.Email}}" maxlength="255">
- </div>
- <div class="field {{if .Err_Description}}error{{end}}">
- {{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}}
- <label for="description">{{ctx.Locale.Tr "org.org_desc"}}</label>
- <textarea id="description" name="description" rows="2" maxlength="255">{{.Org.Description}}</textarea>
- </div>
- <div class="field {{if .Err_Website}}error{{end}}">
- <label for="website">{{ctx.Locale.Tr "org.settings.website"}}</label>
- <input id="website" name="website" type="url" value="{{.Org.Website}}" maxlength="255">
- </div>
- <div class="field">
- <label for="location">{{ctx.Locale.Tr "org.settings.location"}}</label>
- <input id="location" name="location" value="{{.Org.Location}}" maxlength="50">
- </div>
- <div class="divider"></div>
- <div class="field" id="visibility_box">
- <label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
- <div class="field">
- <div class="ui radio checkbox">
- <input class="enable-system-radio" name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}>
- <label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
- </div>
- </div>
- <div class="field">
- <div class="ui radio checkbox">
- <input class="enable-system-radio" name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}>
- <label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
- </div>
- </div>
- <div class="field">
- <div class="ui radio checkbox">
- <input class="enable-system-radio" name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}>
- <label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
- </div>
- </div>
- </div>
+<div class="ui segments org-setting-content">
+ <h4 class="ui top attached header">
+ {{ctx.Locale.Tr "org.settings.options"}}
+ </h4>
+ <div class="ui attached segment">
+ <form class="ui form" action="{{.Link}}" method="post">
+ {{.CsrfTokenHtml}}
+ <div class="field {{if .Err_FullName}}error{{end}}">
+ <label for="full_name">{{ctx.Locale.Tr "org.org_full_name_holder"}}</label>
+ <input id="full_name" name="full_name" value="{{.Org.FullName}}" maxlength="100">
+ </div>
+ <div class="field {{if .Err_Email}}error{{end}}">
+ <label for="email">{{ctx.Locale.Tr "org.settings.email"}}</label>
+ <input id="email" name="email" type="email" value="{{.Org.Email}}" maxlength="255">
+ </div>
+ <div class="field {{if .Err_Description}}error{{end}}">
+ {{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}}
+ <label for="description">{{ctx.Locale.Tr "org.org_desc"}}</label>
+ <textarea id="description" name="description" rows="2" maxlength="255">{{.Org.Description}}</textarea>
+ </div>
+ <div class="field {{if .Err_Website}}error{{end}}">
+ <label for="website">{{ctx.Locale.Tr "org.settings.website"}}</label>
+ <input id="website" name="website" type="url" value="{{.Org.Website}}" maxlength="255">
+ </div>
+ <div class="field">
+ <label for="location">{{ctx.Locale.Tr "org.settings.location"}}</label>
+ <input id="location" name="location" value="{{.Org.Location}}" maxlength="50">
+ </div>
+
+ <div class="divider"></div>
+ <div class="field" id="visibility_box">
+ <label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
+ <div class="field">
+ <div class="ui radio checkbox">
+ <input class="enable-system-radio" name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}>
+ <label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
+ </div>
+ </div>
+ <div class="field">
+ <div class="ui radio checkbox">
+ <input class="enable-system-radio" name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}>
+ <label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
+ </div>
+ </div>
+ <div class="field">
+ <div class="ui radio checkbox">
+ <input class="enable-system-radio" name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}>
+ <label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
+ </div>
+ </div>
+ </div>
- <div class="field" id="permission_box">
- <label>{{ctx.Locale.Tr "org.settings.permission"}}</label>
- <div class="field">
- <div class="ui checkbox">
- <input type="checkbox" name="repo_admin_change_team_access" {{if .RepoAdminChangeTeamAccess}}checked{{end}}>
- <label>{{ctx.Locale.Tr "org.settings.repoadminchangeteam"}}</label>
- </div>
- </div>
- </div>
+ <div class="field" id="permission_box">
+ <label>{{ctx.Locale.Tr "org.settings.permission"}}</label>
+ <div class="field">
+ <div class="ui checkbox">
+ <input type="checkbox" name="repo_admin_change_team_access" {{if .RepoAdminChangeTeamAccess}}checked{{end}}>
+ <label>{{ctx.Locale.Tr "org.settings.repoadminchangeteam"}}</label>
+ </div>
+ </div>
+ </div>
- {{if .SignedUser.IsAdmin}}
- <div class="divider"></div>
+ {{if .SignedUser.IsAdmin}}
+ <div class="divider"></div>
- <div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}">
- <label for="max_repo_creation">{{ctx.Locale.Tr "admin.users.max_repo_creation"}}</label>
- <input id="max_repo_creation" name="max_repo_creation" type="number" min="-1" value="{{.Org.MaxRepoCreation}}">
- <p class="help">{{ctx.Locale.Tr "admin.users.max_repo_creation_desc"}}</p>
- </div>
- {{end}}
+ <div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}">
+ <label for="max_repo_creation">{{ctx.Locale.Tr "admin.users.max_repo_creation"}}</label>
+ <input id="max_repo_creation" name="max_repo_creation" type="number" min="-1" value="{{.Org.MaxRepoCreation}}">
+ <p class="help">{{ctx.Locale.Tr "admin.users.max_repo_creation_desc"}}</p>
+ </div>
+ {{end}}
- <div class="field">
- <button class="ui primary button">{{ctx.Locale.Tr "org.settings.update_settings"}}</button>
- </div>
- </form>
+ <div class="field">
+ <button class="ui primary button">{{ctx.Locale.Tr "org.settings.update_settings"}}</button>
+ </div>
+ </form>
- <div class="divider"></div>
+ <div class="divider"></div>
- <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
- {{.CsrfTokenHtml}}
- <div class="inline field">
- {{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}}
- </div>
- <div class="field">
- <button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button>
- <button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button>
- </div>
- </form>
- </div>
+ <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
+ {{.CsrfTokenHtml}}
+ <div class="inline field">
+ {{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}}
+ </div>
+ <div class="field">
+ <button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button>
+ <button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button>
</div>
+ </form>
+ </div>
+</div>
+
+{{template "org/settings/options_dangerzone" .}}
+
{{template "org/settings/layout_footer" .}}
diff --git a/templates/org/settings/options_dangerzone.tmpl b/templates/org/settings/options_dangerzone.tmpl
new file mode 100644
index 0000000000..01cf3fd405
--- /dev/null
+++ b/templates/org/settings/options_dangerzone.tmpl
@@ -0,0 +1,93 @@
+<h4 class="ui top attached error header">
+ {{ctx.Locale.Tr "repo.settings.danger_zone"}}
+</h4>
+<div class="ui attached error danger segment">
+ <div class="flex-list">
+ <div class="flex-item tw-items-center">
+ <div class="flex-item-main">
+ <div class="flex-item-title">{{ctx.Locale.Tr "org.settings.rename"}}</div>
+ <div class="flex-item-body">{{ctx.Locale.Tr "org.settings.rename_desc"}}</div>
+ </div>
+ <div class="flex-item-trailing">
+ <button class="ui basic red show-modal button" data-modal="#rename-org-modal">{{ctx.Locale.Tr "org.settings.rename"}}</button>
+ </div>
+ </div>
+
+ <div class="flex-item">
+ <div class="flex-item-main">
+ <div class="flex-item-title">{{ctx.Locale.Tr "org.settings.delete_account"}}</div>
+ <div class="flex-item-body">{{ctx.Locale.Tr "org.settings.delete_prompt"}}</div>
+ </div>
+ <div class="flex-item-trailing">
+ <button class="ui basic red show-modal button" data-modal="#delete-org-modal">{{ctx.Locale.Tr "org.settings.delete_account"}}</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="ui small modal" id="rename-org-modal">
+ <div class="header">
+ {{ctx.Locale.Tr "org.settings.rename"}}
+ </div>
+ <div class="content">
+ <ul class="ui warning message">
+ <li>{{ctx.Locale.Tr "org.settings.rename_notices_1"}}</li>
+ <li>{{ctx.Locale.Tr "org.settings.rename_notices_2"}}</li>
+ </ul>
+ <form class="ui form form-fetch-action" action="{{.Link}}/rename" method="post">
+ {{.CsrfTokenHtml}}
+ <div class="field">
+ <label>
+ {{ctx.Locale.Tr "org.settings.name_confirm"}}
+ <span class="text red">{{.Org.Name}}</span>
+ </label>
+ </div>
+ <div class="required field">
+ <label for="org_name_to_rename">{{ctx.Locale.Tr "org.org_name_holder"}}</label>
+ <input id="org_name_to_rename" name="org_name" required>
+ </div>
+
+ <div class="required field">
+ <label>{{ctx.Locale.Tr "org.settings.rename_new_org_name"}}</label>
+ <input name="new_org_name" required>
+ </div>
+
+ <div class="actions">
+ <button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
+ <button class="ui red button">{{ctx.Locale.Tr "org.settings.rename"}}</button>
+ </div>
+ </form>
+ </div>
+</div>
+
+<div class="ui small modal" id="delete-org-modal">
+ <div class="header">
+ {{ctx.Locale.Tr "org.settings.delete_account"}}
+ </div>
+ <div class="content">
+ <ul class="ui warning message">
+ <li>{{ctx.Locale.Tr "org.settings.delete_notices_1"}}</li>
+ <li>{{ctx.Locale.Tr "org.settings.delete_notices_2" .Org.Name}}</li>
+ <li>{{ctx.Locale.Tr "org.settings.delete_notices_3" .Org.Name}}</li>
+ <li>{{ctx.Locale.Tr "org.settings.delete_notices_4" .Org.Name}}</li>
+ </ul>
+ <form class="ui form form-fetch-action" action="{{.Link}}/delete" method="post">
+ {{.CsrfTokenHtml}}
+ <div class="field">
+ <label>
+ {{ctx.Locale.Tr "org.settings.name_confirm"}}
+ <span class="text red">{{.Org.Name}}</span>
+ </label>
+ </div>
+ <div class="required field">
+ <label>{{ctx.Locale.Tr "org.org_name_holder"}}</label>
+ <input name="org_name" required>
+ </div>
+
+ <div class="actions">
+ <button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
+ <button class="ui red button">{{ctx.Locale.Tr "org.settings.delete_account"}}</button>
+ </div>
+ </form>
+ </div>
+</div>
diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl
index 5433f01530..4bc063f90c 100644
--- a/templates/org/team/members.tmpl
+++ b/templates/org/team/members.tmpl
@@ -81,7 +81,7 @@
{{ctx.Locale.Tr "org.members.remove"}}
</div>
<div class="content">
- <p>{{ctx.Locale.Tr "org.members.remove.detail" (`<span class="name"></span>`|SafeHTML) (`<span class="dataTeamName"></span>`|SafeHTML)}}</p>
+ <p>{{ctx.Locale.Tr "org.members.remove.detail" (HTMLFormat `<span class="%s"></span>` "name") (HTMLFormat `<span class="%s"></span>` "dataTeamName")}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl
index 8390bf0acd..6dd5cb3eeb 100644
--- a/templates/org/team/sidebar.tmpl
+++ b/templates/org/team/sidebar.tmpl
@@ -90,7 +90,7 @@
{{ctx.Locale.Tr "org.teams.leave"}}
</div>
<div class="content">
- <p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|SafeHTML)}}</p>
+ <p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "name")}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/org/team/teams.tmpl b/templates/org/team/teams.tmpl
index 432df10749..cdd2789128 100644
--- a/templates/org/team/teams.tmpl
+++ b/templates/org/team/teams.tmpl
@@ -49,7 +49,7 @@
{{ctx.Locale.Tr "org.teams.leave"}}
</div>
<div class="content">
- <p>{{ctx.Locale.Tr "org.teams.leave.detail" (`<span class="name"></span>`|SafeHTML)}}</p>
+ <p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "name")}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/package/content/container.tmpl b/templates/package/content/container.tmpl
index 7d89f8c6e2..1a1335aaa6 100644
--- a/templates/package/content/container.tmpl
+++ b/templates/package/content/container.tmpl
@@ -16,7 +16,17 @@
</div>
<div class="field">
<label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.container.digest"}}</label>
- <div class="markup"><pre class="code-block"><code>{{range .PackageDescriptor.Files}}{{if eq .File.LowerName "manifest.json"}}{{.Properties.GetByName "container.digest"}}{{end}}{{end}}</code></pre></div>
+ <div class="markup">
+ <div class="code-block-container code-overflow-scroll">
+ <pre class="code-block"><code>
+ {{- range .PackageDescriptor.Files -}}
+ {{- if eq .File.LowerName "manifest.json" -}}
+ {{- .Properties.GetByName "container.digest" -}}{{"\n"}}
+ {{- end -}}
+ {{- end -}}
+ </code></pre>
+ </div>
+ </div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "packages.registry.documentation" "Container" "https://docs.gitea.com/usage/packages/container/"}}</label>
@@ -39,7 +49,11 @@
{{/* "unknown/unknown" is attestation-manifest, so we should skip it */}}
{{if ne .Platform "unknown/unknown"}}
<tr>
- <td><a class="tw-font-mono" href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{StringUtils.TrimPrefix .Digest "sha256:" | ShortSha}}</a></td>
+ <td>
+ <a class="tw-font-mono" href="{{$.PackageDescriptor.PackageWebLink}}/{{$.PackageDescriptor.Version.LowerVersion}}/{{PathEscape .Digest}}">
+ {{StringUtils.TrimPrefix .Digest "sha256:" | ShortSha}}
+ </a>
+ </td>
<td>{{.Platform}}</td>
<td>{{FileSize .Size}}</td>
</tr>
@@ -55,12 +69,24 @@
{{.PackageDescriptor.Metadata.Description}}
</div>
{{end}}
- {{if .PackageDescriptor.Metadata.ImageLayers}}
- <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.container.layers"}}</h4>
+
+ {{/* a container manifest may contain sub manifests, so here we try to display some information of the sub manifest,
+ not perfect, just better than before */}}
+ {{$imageMetadata := .ContainerImageMetadata}}
+ {{if $imageMetadata.ImageLayers}}
+ <h4 class="ui top attached header flex-text-block">
+ {{ctx.Locale.Tr "packages.container.layers"}}
+ {{/* only show the platform if the image metadata is not the package's, which means that it is a sub manifest */}}
+ {{if ne .ContainerImageMetadata .PackageDescriptor.Metadata}}
+ <span class="tw-text-sm flex-text-inline" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">
+ ({{svg "octicon-cpu" 12}} {{.ContainerImageMetadata.Platform}})
+ </span>
+ {{end}}
+ </h4>
<div class="ui attached segment tw-break-anywhere">
- <table class="ui very basic compact table">
+ <table class="ui very basic compact table tw-font-mono">
<tbody>
- {{range .PackageDescriptor.Metadata.ImageLayers}}
+ {{range $imageMetadata.ImageLayers}}
<tr>
<td>{{.}}</td>
</tr>
@@ -69,10 +95,10 @@
</table>
</div>
{{end}}
- {{if .PackageDescriptor.Metadata.Labels}}
+ {{if $imageMetadata.Labels}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.container.labels"}}</h4>
<div class="ui attached segment">
- <table class="ui very basic compact table">
+ <table class="ui very basic compact table tw-font-mono">
<thead>
<tr>
<th>{{ctx.Locale.Tr "packages.container.labels.key"}}</th>
@@ -80,7 +106,7 @@
</tr>
</thead>
<tbody>
- {{range $key, $value := .PackageDescriptor.Metadata.Labels}}
+ {{range $key, $value := $imageMetadata.Labels}}
<tr>
<td class="tw-align-top">{{$key}}</td>
<td class="tw-break-anywhere">{{$value}}</td>
diff --git a/templates/package/content/pypi.tmpl b/templates/package/content/pypi.tmpl
index 2a22a6ed71..2625c160fe 100644
--- a/templates/package/content/pypi.tmpl
+++ b/templates/package/content/pypi.tmpl
@@ -4,7 +4,7 @@
<div class="ui form">
<div class="field">
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.pypi.install"}}</label>
- <div class="markup"><pre class="code-block"><code>pip install --index-url <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></origin-url> {{.PackageDescriptor.Package.Name}}</code></pre></div>
+ <div class="markup"><pre class="code-block"><code>pip install --index-url <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></origin-url> --extra-index-url https://pypi.org/ {{.PackageDescriptor.Package.Name}}</code></pre></div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "packages.registry.documentation" "PyPI" "https://docs.gitea.com/usage/packages/pypi/"}}</label>
diff --git a/templates/package/shared/view.tmpl b/templates/package/shared/view.tmpl
index 713e1bbfc5..52673accf9 100644
--- a/templates/package/shared/view.tmpl
+++ b/templates/package/shared/view.tmpl
@@ -1,4 +1,5 @@
<div class="issue-title-header">
+ {{$packageVersionLink := print $.PackageDescriptor.PackageWebLink "/" (PathEscape .PackageDescriptor.Version.LowerVersion)}}
<h1>{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</h1>
<div>
{{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}}
@@ -9,8 +10,8 @@
{{end}}
</div>
</div>
-<div class="issue-content">
- <div class="issue-content-left">
+<div class="packages-content">
+ <div class="packages-content-left">
{{template "package/content/alpine" .}}
{{template "package/content/arch" .}}
{{template "package/content/cargo" .}}
@@ -34,7 +35,7 @@
{{template "package/content/swift" .}}
{{template "package/content/vagrant" .}}
</div>
- <div class="issue-content-right ui segment">
+ <div class="ui segment packages-content-right">
<strong>{{ctx.Locale.Tr "packages.details"}}</strong>
<div class="ui relaxed list flex-items-block">
<div class="item">{{svg .PackageDescriptor.Package.Type.SVGName}} {{.PackageDescriptor.Package.Type.Name}}</div>
@@ -74,8 +75,8 @@
<div class="ui relaxed list">
{{range .PackageDescriptor.Files}}
<div class="item">
- <a href="{{$.Link}}/files/{{.File.ID}}">{{.File.Name}}</a>
- <span class="text small file-size">{{FileSize .Blob.Size}}</span>
+ <a href="{{$packageVersionLink}}/files/{{.File.ID}}">{{.File.Name}}</a>
+ <span class="text small tw-whitespace-nowrap">{{FileSize .Blob.Size}}</span>
</div>
{{end}}
</div>
@@ -98,7 +99,7 @@
<div class="item">{{svg "octicon-issue-opened"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div>
{{end}}
{{if .CanWritePackages}}
- <div class="item">{{svg "octicon-tools"}} <a href="{{.Link}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
+ <div class="item">{{svg "octicon-tools"}} <a href="{{$packageVersionLink}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
{{end}}
</div>
{{end}}
diff --git a/templates/post-install.tmpl b/templates/post-install.tmpl
index 0c9aa35c90..9baac4f84c 100644
--- a/templates/post-install.tmpl
+++ b/templates/post-install.tmpl
@@ -4,7 +4,7 @@
<!-- the "cup" has a handler, so move it a little leftward to make it visually in the center -->
<div class="tw-ml-[-30px]"><img width="160" src="{{AssetUrlPrefix}}/img/loading.png" alt aria-hidden="true"></div>
<div class="tw-my-[2em] tw-text-[18px]">
- <a id="goto-user-login" href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "install.installing_desc"}}</a>
+ <a id="goto-after-install" href="{{AppSubUrl}}{{Iif .IsAccountCreated "/user/login" "/user/sign_up"}}">{{ctx.Locale.Tr "install.installing_desc"}}</a>
</div>
</div>
</div>
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl
index f6d549a634..e769543f6a 100644
--- a/templates/projects/list.tmpl
+++ b/templates/projects/list.tmpl
@@ -67,7 +67,7 @@
{{else}}
<a class="link-action flex-text-inline" href data-url="{{.Link ctx}}/close">{{svg "octicon-skip" 14}}{{ctx.Locale.Tr "repo.projects.close"}}</a>
{{end}}
- <a class="delete-button flex-text-inline" href="#" data-url="{{.Link ctx}}/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
+ <a class="link-action flex-text-inline text red" href data-modal-confirm="#repo-project-delete-modal" data-url="{{.Link ctx}}/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
</div>
{{end}}
</div>
@@ -81,14 +81,9 @@
</div>
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.projects.deletion"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "repo.projects.deletion_desc"}}</p>
- </div>
+<div class="ui small modal" id="repo-project-delete-modal">
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.projects.deletion"}}</div>
+ <div class="content"><p>{{ctx.Locale.Tr "repo.projects.deletion_desc"}}</p></div>
{{template "base/modal_actions_confirm" .}}
</div>
{{end}}
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl
index 7e89db0005..6aa776da02 100644
--- a/templates/projects/view.tmpl
+++ b/templates/projects/view.tmpl
@@ -130,12 +130,12 @@
<input class="project-column-id" type="hidden" name="id">
<div class="required field">
<label class="project-column-title-label" for="project-column-title-input">title</label>
- <input id="project-column-title-input" name="title" value="{{.Title}}" required>
+ <input id="project-column-title-input" name="title" required>
</div>
<div class="field">
<label class="project-column-color-label" for="project-column-color-input">color</label>
<div class="js-color-picker-input column">
- <input maxlength="7" placeholder="#c320f6" id="project-column-color-input" name="color" value="{{.Color}}">
+ <input maxlength="7" placeholder="#c320f6" id="project-column-color-input" name="color">
{{template "repo/issue/label_precolors"}}
</div>
</div>
diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl
index fa1adb3e3b..23df61a43c 100644
--- a/templates/repo/actions/runs_list.tmpl
+++ b/templates/repo/actions/runs_list.tmpl
@@ -5,36 +5,55 @@
<h2>{{if $.IsFiltered}}{{ctx.Locale.Tr "actions.runs.no_results"}}{{else}}{{ctx.Locale.Tr "actions.runs.no_runs"}}{{end}}</h2>
</div>
{{end}}
- {{range .Runs}}
+ {{range $run := .Runs}}
<div class="flex-item tw-items-center">
<div class="flex-item-leading">
- {{template "repo/actions/status" (dict "status" .Status.String)}}
+ {{template "repo/actions/status" (dict "status" $run.Status.String)}}
</div>
<div class="flex-item-main">
- <a class="flex-item-title" title="{{.Title}}" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
- {{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
+ <a class="flex-item-title" title="{{$run.Title}}" href="{{$run.Link}}">
+ {{or $run.Title (ctx.Locale.Tr "actions.runs.empty_commit_message")}}
</a>
<div class="flex-item-body">
- <span><b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>:</span>
- {{- if .ScheduleID -}}
+ <span><b>{{if not $.CurWorkflow}}{{$run.WorkflowID}} {{end}}#{{$run.Index}}</b>:</span>
+ {{- if $run.ScheduleID -}}
{{ctx.Locale.Tr "actions.runs.scheduled"}}
{{- else -}}
{{ctx.Locale.Tr "actions.runs.commit"}}
- <a href="{{$.RepoLink}}/commit/{{.CommitSHA}}">{{ShortSha .CommitSHA}}</a>
+ <a href="{{$.RepoLink}}/commit/{{$run.CommitSHA}}">{{ShortSha $run.CommitSHA}}</a>
{{ctx.Locale.Tr "actions.runs.pushed_by"}}
- <a href="{{.TriggerUser.HomeLink}}">{{.TriggerUser.GetDisplayName}}</a>
+ <a href="{{$run.TriggerUser.HomeLink}}">{{$run.TriggerUser.GetDisplayName}}</a>
{{- end -}}
</div>
</div>
<div class="flex-item-trailing">
- {{if .IsRefDeleted}}
- <span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</span>
+ {{if $run.IsRefDeleted}}
+ <span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{$run.PrettyRef}}">{{$run.PrettyRef}}</span>
{{else}}
- <a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</a>
+ <a class="ui label run-list-ref gt-ellipsis" href="{{$run.RefLink}}" data-tooltip-content="{{$run.PrettyRef}}">{{$run.PrettyRef}}</a>
{{end}}
<div class="run-list-item-right">
- <div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince .Updated}}</div>
- <div class="run-list-meta">{{svg "octicon-stopwatch" 16}}{{.Duration}}</div>
+ <div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince $run.Updated}}</div>
+ <div class="run-list-meta">{{svg "octicon-stopwatch" 16}}{{$run.Duration}}</div>
+ </div>
+ <div class="ui dropdown jump tw-p-2">
+ {{svg "octicon-kebab-horizontal"}}
+ <div class="menu flex-items-menu">
+ <a class="item" href="{{$run.Link}}/workflow">{{svg "octicon-play"}}{{ctx.Locale.Tr "actions.runs.view_workflow_file"}}</a>
+ {{if and $.CanWriteRepoUnitActions (not $run.Status.IsDone)}}
+ <a class="item link-action" data-url="{{$run.Link}}/cancel">
+ {{svg "octicon-x"}}{{ctx.Locale.Tr "actions.runs.cancel"}}
+ </a>
+ {{end}}
+ {{if and $.CanWriteRepoUnitActions $run.Status.IsDone}}
+ <a class="item link-action"
+ data-url="{{$run.Link}}/delete"
+ data-modal-confirm="{{ctx.Locale.Tr "actions.runs.delete.description"}}"
+ >
+ {{svg "octicon-trash"}}{{ctx.Locale.Tr "actions.runs.delete"}}
+ </a>
+ {{end}}
+ </div>
</div>
</div>
</div>
diff --git a/templates/repo/actions/view_component.tmpl b/templates/repo/actions/view_component.tmpl
index d0741cdc0b..4e338ffcfc 100644
--- a/templates/repo/actions/view_component.tmpl
+++ b/templates/repo/actions/view_component.tmpl
@@ -4,7 +4,7 @@
data-actions-url="{{.ActionsURL}}"
data-locale-approve="{{ctx.Locale.Tr "repo.diff.review.approve"}}"
- data-locale-cancel="{{ctx.Locale.Tr "cancel"}}"
+ data-locale-cancel="{{ctx.Locale.Tr "actions.runs.cancel"}}"
data-locale-rerun="{{ctx.Locale.Tr "rerun"}}"
data-locale-rerun-all="{{ctx.Locale.Tr "rerun_all"}}"
data-locale-runs-scheduled="{{ctx.Locale.Tr "actions.runs.scheduled"}}"
diff --git a/templates/repo/actions/workflow_dispatch_inputs.tmpl b/templates/repo/actions/workflow_dispatch_inputs.tmpl
index 8b8292af1d..37538a318f 100644
--- a/templates/repo/actions/workflow_dispatch_inputs.tmpl
+++ b/templates/repo/actions/workflow_dispatch_inputs.tmpl
@@ -33,7 +33,8 @@
</div>
{{end}}
<div class="ui field">
- <button class="ui tiny primary button" type="submit">{{ctx.Locale.Tr "actions.workflow.run"}}</button>
+ {{/* use autofocus here to prevent the "branch selection" dropdown from getting focus, otherwise it will auto popup */}}
+ <button class="ui tiny primary button" autofocus type="submit">{{ctx.Locale.Tr "actions.workflow.run"}}</button>
</div>
{{end}}
{{range .workflows}}
diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl
index 9596fe837a..c4d9f0741f 100644
--- a/templates/repo/blame.tmpl
+++ b/templates/repo/blame.tmpl
@@ -82,6 +82,8 @@
</table>
{{end}}{{/* end if .IsFileTooLarge */}}
<div class="code-line-menu tippy-target">
+ {{/*FIXME: the "HasSourceRenderedToggle" is never set on blame page, it should mean "whether the file is renderable".
+ If the file is renderable, then it must has the "display=source" parameter to make sure the file view page shows the source code, then line number works. */}}
{{if $.Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
<a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}" rel="nofollow noindex">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</a>
{{end}}
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl
index 19797229bf..9e86641c6f 100644
--- a/templates/repo/branch/list.tmpl
+++ b/templates/repo/branch/list.tmpl
@@ -20,14 +20,14 @@
<tr>
<td>
<div class="flex-text-block">
- <a class="gt-ellipsis" href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
+ <a class="gt-ellipsis branch-name" href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
{{if .DefaultBranchBranch.IsProtected}}
<span data-tooltip-content="{{ctx.Locale.Tr "repo.settings.protected_branch"}}">{{svg "octicon-shield-lock"}}</span>
{{end}}
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}}
</div>
- <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeCommentMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
+ <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage .Repository}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
</td>
{{/* FIXME: here and below, the tw-overflow-visible is not quite right but it is still needed the moment: to show the important buttons when the width is narrow */}}
<td class="tw-text-right tw-overflow-visible">
@@ -90,25 +90,31 @@
<td class="eight wide">
{{if .DBBranch.IsDeleted}}
<div class="flex-text-block">
- <span class="gt-ellipsis">{{.DBBranch.Name}}</span>
+ <span class="gt-ellipsis branch-name">{{.DBBranch.Name}}</span>
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
</div>
<p class="info">{{ctx.Locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{DateUtils.TimeSince .DBBranch.DeletedUnix}}</p>
{{else}}
<div class="flex-text-block">
- <a class="gt-ellipsis" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
+ <a class="gt-ellipsis branch-name" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
{{if .IsProtected}}
<span data-tooltip-content="{{ctx.Locale.Tr "repo.settings.protected_branch"}}">{{svg "octicon-shield-lock"}}</span>
{{end}}
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}}
</div>
- <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage ($.Repository.ComposeCommentMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
+ <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage $.Repository}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
{{end}}
</td>
<td class="two wide ui">
- {{if and (not .DBBranch.IsDeleted) $.DefaultBranchBranch}}
- <div class="commit-divergence">
+ {{if and (not .DBBranch.IsDeleted) $.DefaultBranchBranch}}
+ {{$tooltipDivergence := ""}}
+ {{if or .CommitsBehind .CommitsAhead}}
+ {{$tooltipDivergence = ctx.Locale.Tr "repo.branch.commits_divergence_from" .CommitsBehind .CommitsAhead $.DefaultBranchBranch.DBBranch.Name}}
+ {{else}}
+ {{$tooltipDivergence = ctx.Locale.Tr "repo.branch.commits_no_divergence" $.DefaultBranchBranch.DBBranch.Name}}
+ {{end}}
+ <div class="commit-divergence" data-tooltip-content="{{$tooltipDivergence}}">
<div class="bar-group">
<div class="count count-behind">{{.CommitsBehind}}</div>
{{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}}
@@ -119,7 +125,7 @@
<div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
</div>
</div>
- {{end}}
+ {{end}}
</td>
<td class="two wide tw-text-right">
{{if not .LatestPullRequest}}
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
index 5639c87a82..46f641824b 100644
--- a/templates/repo/commit_page.tmpl
+++ b/templates/repo/commit_page.tmpl
@@ -5,7 +5,7 @@
<div class="ui container fluid padded">
<div class="ui top attached header clearing segment tw-relative commit-header">
<div class="tw-flex tw-mb-4 tw-gap-1">
- <h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{ctx.RenderUtils.RenderCommitMessage .Commit.Message ($.Repository.ComposeCommentMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
+ <h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{ctx.RenderUtils.RenderCommitMessage .Commit.Message $.Repository}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses "AdditionalClasses" "tw-inline"}}</h3>
{{if not $.PageIsWiki}}
<div class="commit-header-buttons">
<a class="ui primary tiny button" href="{{.SourcePath}}">
@@ -75,7 +75,7 @@
{{.CsrfTokenHtml}}
<div class="field">
<label>
- {{ctx.Locale.Tr "repo.branch.new_branch_from" (`<span class="text" id="modal-create-branch-from-span"></span>`|SafeHTML)}}
+ {{ctx.Locale.Tr "repo.branch.new_branch_from" (HTMLFormat `<span class="%s" id="%s"></span>` "text" "modal-create-branch-from-span")}}
</label>
</div>
<div class="required field">
@@ -100,7 +100,7 @@
<input type="hidden" name="create_tag" value="true">
<div class="field">
<label>
- {{ctx.Locale.Tr "repo.tag.create_tag_from" (`<span class="text" id="modal-create-tag-from-span"></span>`|SafeHTML)}}
+ {{ctx.Locale.Tr "repo.tag.create_tag_from" (HTMLFormat `<span class="%s" id="%s"></span>` "text" "modal-create-tag-from-span")}}
</label>
</div>
<div class="required field">
@@ -122,7 +122,7 @@
{{end}}
</div>
{{if IsMultilineCommitMessage .Commit.Message}}
- <pre class="commit-body">{{ctx.RenderUtils.RenderCommitBody .Commit.Message ($.Repository.ComposeCommentMetas ctx)}}</pre>
+ <pre class="commit-body">{{ctx.RenderUtils.RenderCommitBody .Commit.Message $.Repository}}</pre>
{{end}}
{{template "repo/commit_load_branches_and_tags" .}}
</div>
diff --git a/templates/repo/commit_status.tmpl b/templates/repo/commit_status.tmpl
index eb700ab2bb..7184f5f8eb 100644
--- a/templates/repo/commit_status.tmpl
+++ b/templates/repo/commit_status.tmpl
@@ -14,3 +14,6 @@
{{if eq .State "warning"}}
{{svg "gitea-exclamation" 18 "commit-status icon text yellow"}}
{{end}}
+{{if eq .State "skipped"}}
+ {{svg "octicon-skip" 18 "commit-status icon text grey"}}
+{{end}}
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl
index 17c7240ee4..959f2a9398 100644
--- a/templates/repo/commits_list.tmpl
+++ b/templates/repo/commits_list.tmpl
@@ -44,7 +44,7 @@
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | ctx.RenderUtils.RenderEmoji}}</span>
{{else}}
{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
- <span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.Repository.ComposeCommentMetas ctx)}}</span>
+ <span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink $.Repository}}</span>
{{end}}
</span>
{{if IsMultilineCommitMessage .Message}}
@@ -52,11 +52,11 @@
{{end}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
{{if IsMultilineCommitMessage .Message}}
- <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeCommentMetas ctx)}}</pre>
+ <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .Message $.Repository}}</pre>
{{end}}
{{if $.CommitsTagsMap}}
{{range (index $.CommitsTagsMap .ID.String)}}
- {{- template "repo/tag/name" dict "RepoLink" $.Repository.Link "TagName" .TagName "IsRelease" (not .IsTag) -}}
+ {{- template "repo/tag/name" dict "AdditionalClasses" "tw-py-0" "RepoLink" $.Repository.Link "TagName" .TagName "IsRelease" (not .IsTag) -}}
{{end}}
{{end}}
</td>
diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl
index b054ce19a5..ee94ad7e58 100644
--- a/templates/repo/commits_list_small.tmpl
+++ b/templates/repo/commits_list_small.tmpl
@@ -15,7 +15,7 @@
{{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}}
<span class="tw-flex-1 tw-font-mono gt-ellipsis" title="{{.Summary}}">
- {{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeCommentMetas ctx) -}}
+ {{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink $.comment.Issue.PullRequest.BaseRepo -}}
</span>
{{if IsMultilineCommitMessage .Message}}
@@ -29,7 +29,7 @@
</div>
{{if IsMultilineCommitMessage .Message}}
<pre class="commit-body tw-ml-[33px] tw-hidden" data-singular-commit-body-for="{{$tag}}">
- {{- ctx.RenderUtils.RenderCommitBody .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeCommentMetas ctx) -}}
+ {{- ctx.RenderUtils.RenderCommitBody .Message $.comment.Issue.PullRequest.BaseRepo -}}
</pre>
{{end}}
{{end}}
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index fa96d2f0e2..22abf9a219 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -148,7 +148,7 @@
<a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
{{else}}
<a class="item" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
- {{if and $.Repository.CanEnableEditor $.CanEditFile (not $file.IsLFSFile) (not $file.IsBin)}}
+ {{if and $.Repository.CanEnableEditor $.CanEditFile}}
<a class="item" rel="nofollow" href="{{$.HeadRepoLink}}/_edit/{{PathEscapeSegments $.HeadBranchName}}/{{PathEscapeSegments $file.Name}}?return_uri={{print $.BackToLink "#diff-" $file.NameHash | QueryEscape}}">{{ctx.Locale.Tr "repo.editor.edit_this_file"}}</a>
{{end}}
{{end}}
diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl
index 6f16ce3bd8..4e8ad1326c 100644
--- a/templates/repo/diff/compare.tmpl
+++ b/templates/repo/diff/compare.tmpl
@@ -189,7 +189,7 @@
<div class="ui segment flex-text-block tw-gap-4">
{{template "shared/issueicon" .}}
<div class="issue-title tw-break-anywhere">
- {{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title $.Repository}}
<span class="index">#{{.PullRequest.Issue.Index}}</span>
</div>
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
diff --git a/templates/repo/editor/cherry_pick.tmpl b/templates/repo/editor/cherry_pick.tmpl
index f9c9eef5aa..7981fd0761 100644
--- a/templates/repo/editor/cherry_pick.tmpl
+++ b/templates/repo/editor/cherry_pick.tmpl
@@ -3,15 +3,14 @@
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
- <form class="ui edit form" method="post" action="{{.RepoLink}}/_cherrypick/{{.SHA}}/{{.BranchName | PathEscapeSegments}}">
+ <form class="ui edit form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}">
{{.CsrfTokenHtml}}
- <input type="hidden" name="last_commit" value="{{.last_commit}}">
- <input type="hidden" name="page_has_posted" value="true">
+ {{template "repo/editor/common_top" .}}
<input type="hidden" name="revert" value="{{if eq .CherryPickType "revert"}}true{{else}}false{{end}}">
<div class="repo-editor-header">
- <div class="ui breadcrumb field {{if .Err_TreePath}}error{{end}}">
- {{$shaurl := printf "%s/commit/%s" $.RepoLink (PathEscape .SHA)}}
- {{$shalink := HTMLFormat `<a class="ui primary sha label" href="%s">%s</a>` $shaurl (ShortSha .SHA)}}
+ <div class="breadcrumb">
+ {{$shaurl := printf "%s/commit/%s" $.RepoLink (PathEscape .FromCommitID)}}
+ {{$shalink := HTMLFormat `<a class="ui primary sha label" href="%s">%s</a>` $shaurl (ShortSha .FromCommitID)}}
{{if eq .CherryPickType "revert"}}
{{ctx.Locale.Tr "repo.editor.revert" $shalink}}
{{else}}
diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl
index 8f46c47b96..7067614444 100644
--- a/templates/repo/editor/commit_form.tmpl
+++ b/templates/repo/editor/commit_form.tmpl
@@ -1,11 +1,11 @@
<div class="commit-form-wrapper">
{{ctx.AvatarUtils.Avatar .SignedUser 40 "commit-avatar"}}
<div class="commit-form">
- <h3>{{- if .CanCommitToBranch.WillSign}}
- <span title="{{ctx.Locale.Tr "repo.signing.will_sign" .CanCommitToBranch.SigningKey}}">{{svg "octicon-lock" 24}}</span>
+ <h3>{{- if .CommitFormOptions.WillSign}}
+ <span title="{{ctx.Locale.Tr "repo.signing.will_sign" .CommitFormOptions.SigningKey}}">{{svg "octicon-lock" 24}}</span>
{{ctx.Locale.Tr "repo.editor.commit_signed_changes"}}
{{- else}}
- <span title="{{ctx.Locale.Tr (printf "repo.signing.wont_sign.%s" .CanCommitToBranch.WontSignReason)}}">{{svg "octicon-unlock" 24}}</span>
+ <span title="{{ctx.Locale.Tr (printf "repo.signing.wont_sign.%s" .CommitFormOptions.WontSignReason)}}">{{svg "octicon-unlock" 24}}</span>
{{ctx.Locale.Tr "repo.editor.commit_changes"}}
{{- end}}</h3>
<div class="field">
@@ -22,17 +22,17 @@
</div>
<div class="quick-pull-choice js-quick-pull-choice">
<div class="field">
- <div class="ui radio checkbox {{if not .CanCommitToBranch.CanCommitToBranch}}disabled{{end}}">
+ <div class="ui radio checkbox {{if not .CommitFormOptions.CanCommitToBranch}}disabled{{end}}">
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" data-button-text="{{ctx.Locale.Tr "repo.editor.commit_changes"}}" {{if eq .commit_choice "direct"}}checked{{end}}>
<label>
{{svg "octicon-git-commit"}}
{{ctx.Locale.Tr "repo.editor.commit_directly_to_this_branch" .BranchName}}
- {{if not .CanCommitToBranch.CanCommitToBranch}}
+ {{if not .CommitFormOptions.CanCommitToBranch}}
<div class="ui visible small warning message">
{{ctx.Locale.Tr "repo.editor.no_commit_to_branch"}}
<ul>
- {{if not .CanCommitToBranch.UserCanPush}}<li>{{ctx.Locale.Tr "repo.editor.user_no_push_to_branch"}}</li>{{end}}
- {{if and .CanCommitToBranch.RequireSigned (not .CanCommitToBranch.WillSign)}}<li>{{ctx.Locale.Tr "repo.editor.require_signed_commit"}}</li>{{end}}
+ {{if not .CommitFormOptions.UserCanPush}}<li>{{ctx.Locale.Tr "repo.editor.user_no_push_to_branch"}}</li>{{end}}
+ {{if and .CommitFormOptions.RequireSigned (not .CommitFormOptions.WillSign)}}<li>{{ctx.Locale.Tr "repo.editor.require_signed_commit"}}</li>{{end}}
</ul>
</div>
{{end}}
@@ -42,14 +42,14 @@
{{if and (not .Repository.IsEmpty) (not .IsEditingFileOnly)}}
<div class="field">
<div class="ui radio checkbox">
- {{if .CanCreatePullRequest}}
+ {{if .CommitFormOptions.CanCreatePullRequest}}
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch" data-button-text="{{ctx.Locale.Tr "repo.editor.propose_file_change"}}" {{if eq .commit_choice "commit-to-new-branch"}}checked{{end}}>
{{else}}
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch" data-button-text="{{ctx.Locale.Tr "repo.editor.commit_changes"}}" {{if eq .commit_choice "commit-to-new-branch"}}checked{{end}}>
{{end}}
<label>
{{svg "octicon-git-pull-request"}}
- {{if .CanCreatePullRequest}}
+ {{if .CommitFormOptions.CanCreatePullRequest}}
{{ctx.Locale.Tr "repo.editor.create_new_branch"}}
{{else}}
{{ctx.Locale.Tr "repo.editor.create_new_branch_np"}}
@@ -58,7 +58,7 @@
</div>
</div>
<div class="quick-pull-branch-name {{if not (eq .commit_choice "commit-to-new-branch")}}tw-hidden{{end}}">
- <div class="new-branch-name-input field {{if .Err_NewBranchName}}error{{end}}">
+ <div class="new-branch-name-input field">
{{svg "octicon-git-branch"}}
<input type="text" name="new_branch_name" maxlength="100" value="{{.new_branch_name}}" class="input-contrast tw-mr-1 js-quick-pull-new-branch-name" placeholder="{{ctx.Locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{ctx.Locale.Tr "repo.editor.new_branch_name"}}">
<span class="text-muted js-quick-pull-normalization-info"></span>
@@ -67,7 +67,7 @@
{{end}}
</div>
{{if and .CommitCandidateEmails (gt (len .CommitCandidateEmails) 1)}}
- <div class="field {{if .Err_CommitEmail}}error{{end}}">
+ <div class="field">
<label>{{ctx.Locale.Tr "repo.editor.commit_email"}}</label>
<select class="ui selection dropdown" name="commit_email">
{{- range $email := .CommitCandidateEmails -}}
@@ -77,6 +77,7 @@
</div>
{{end}}
</div>
+ <input type="hidden" name="last_commit" value="{{.last_commit}}">
<button id="commit-button" type="submit" class="ui primary button">
{{if eq .commit_choice "commit-to-new-branch"}}{{ctx.Locale.Tr "repo.editor.propose_file_change"}}{{else}}{{ctx.Locale.Tr "repo.editor.commit_changes"}}{{end}}
</button>
diff --git a/templates/repo/editor/common_breadcrumb.tmpl b/templates/repo/editor/common_breadcrumb.tmpl
new file mode 100644
index 0000000000..8cfbe09d3e
--- /dev/null
+++ b/templates/repo/editor/common_breadcrumb.tmpl
@@ -0,0 +1,16 @@
+<div class="breadcrumb">
+ <a class="section" href="{{$.BranchLink}}">{{.Repository.Name}}</a>
+ {{$n := len .TreeNames}}
+ {{$l := Eval $n "-" 1}}
+ {{range $i, $v := .TreeNames}}
+ <div class="breadcrumb-divider">/</div>
+ {{if eq $i $l}}
+ <input id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr (Iif $.PageIsUpload "repo.editor.add_subdir" "repo.editor.name_your_file")}}" data-editorconfig="{{$.EditorconfigJson}}" {{Iif $.PageIsUpload "" "required"}} autofocus>
+ <span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
+ {{else}}
+ <span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
+ {{end}}
+ {{end}}
+ <span>{{ctx.Locale.Tr "repo.editor.or"}} <a href="{{or .ReturnURI (print $.BranchLink "/" (PathEscapeSegments .TreePath))}}">{{ctx.Locale.Tr "repo.editor.cancel_lower"}}</a></span>
+ <input type="hidden" id="tree_path" name="tree_path" value="{{.TreePath}}">
+</div>
diff --git a/templates/repo/editor/common_top.tmpl b/templates/repo/editor/common_top.tmpl
new file mode 100644
index 0000000000..23280ed565
--- /dev/null
+++ b/templates/repo/editor/common_top.tmpl
@@ -0,0 +1,6 @@
+{{if .CommitFormOptions.WillSubmitToFork}}
+<div class="ui blue message">
+ {{$repoLinkHTML := HTMLFormat `<a href="%s">%s</a>` .CommitFormOptions.TargetRepo.Link .CommitFormOptions.TargetRepo.FullName}}
+ {{ctx.Locale.Tr "repo.editor.fork_edit_description" $repoLinkHTML}}
+</div>
+{{end}}
diff --git a/templates/repo/editor/delete.tmpl b/templates/repo/editor/delete.tmpl
index 2c0c2fc792..bf6143f1cb 100644
--- a/templates/repo/editor/delete.tmpl
+++ b/templates/repo/editor/delete.tmpl
@@ -3,9 +3,9 @@
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
- <form class="ui form" method="post">
+ <form class="ui form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}">
{{.CsrfTokenHtml}}
- <input type="hidden" name="last_commit" value="{{.last_commit}}">
+ {{template "repo/editor/common_top" .}}
{{template "repo/editor/commit_form" .}}
</form>
</div>
diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl
index ae8a60c20c..0911d02e1f 100644
--- a/templates/repo/editor/edit.tmpl
+++ b/templates/repo/editor/edit.tmpl
@@ -3,56 +3,49 @@
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
- <form class="ui edit form" method="post"
+ <form class="ui edit form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}"
data-text-empty-confirm-header="{{ctx.Locale.Tr "repo.editor.commit_empty_file_header"}}"
data-text-empty-confirm-content="{{ctx.Locale.Tr "repo.editor.commit_empty_file_text"}}"
>
{{.CsrfTokenHtml}}
- <input type="hidden" name="last_commit" value="{{.last_commit}}">
- <input type="hidden" name="page_has_posted" value="{{.PageHasPosted}}">
+ {{template "repo/editor/common_top" .}}
<div class="repo-editor-header">
- <div class="ui breadcrumb field{{if .Err_TreePath}} error{{end}}">
- <a class="section" href="{{$.BranchLink}}">{{.Repository.Name}}</a>
- {{$n := len .TreeNames}}
- {{$l := Eval $n "-" 1}}
- {{range $i, $v := .TreeNames}}
- <div class="breadcrumb-divider">/</div>
- {{if eq $i $l}}
- <input id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.EditorconfigJson}}" required autofocus>
- <span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
- {{else}}
- <span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
- {{end}}
- {{end}}
- <span>{{ctx.Locale.Tr "repo.editor.or"}} <a href="{{if .ReturnURI}}{{.ReturnURI}}{{else}}{{$.BranchLink}}{{if not .IsNewFile}}/{{PathEscapeSegments .TreePath}}{{end}}{{end}}">{{ctx.Locale.Tr "repo.editor.cancel_lower"}}</a></span>
- <input type="hidden" id="tree_path" name="tree_path" value="{{.TreePath}}" required>
- </div>
+ {{template "repo/editor/common_breadcrumb" .}}
</div>
- <div class="field">
- <div class="ui top attached header">
- <div class="ui compact small menu small-menu-items repo-editor-menu">
- <a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
- <a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
- {{if not .IsNewFile}}
- <a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
- {{end}}
- </div>
- </div>
- <div class="ui bottom attached segment tw-p-0">
- <div class="ui active tab tw-rounded-b" data-tab="write">
- <textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
- data-previewable-extensions="{{.PreviewableExtensions}}"
- data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea>
- <div class="editor-loading is-loading"></div>
+ {{if not .NotEditableReason}}
+ <div class="field">
+ <div class="ui top attached header">
+ <div class="ui compact small menu small-menu-items repo-editor-menu">
+ <a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
+ <a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
+ {{if not .IsNewFile}}
+ <a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
+ {{end}}
+ </div>
</div>
- <div class="ui tab tw-px-4 tw-py-3" data-tab="preview">
- {{ctx.Locale.Tr "loading"}}
+ <div class="ui bottom attached segment tw-p-0">
+ <div class="ui active tab tw-rounded-b" data-tab="write">
+ <textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
+ data-previewable-extensions="{{.PreviewableExtensions}}"
+ data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea>
+ <div class="editor-loading is-loading"></div>
+ </div>
+ <div class="ui tab tw-px-4 tw-py-3" data-tab="preview">
+ {{ctx.Locale.Tr "loading"}}
+ </div>
+ <div class="ui tab" data-tab="diff">
+ <div class="tw-p-16"></div>
+ </div>
</div>
- <div class="ui tab" data-tab="diff">
- <div class="tw-p-16"></div>
+ </div>
+ {{else}}
+ <div class="field">
+ <div class="ui segment tw-text-center">
+ <h4 class="tw-font-semibold tw-mb-2">{{.NotEditableReason}}</h4>
+ <p>{{ctx.Locale.Tr "repo.editor.file_not_editable_hint"}}</p>
</div>
</div>
- </div>
+ {{end}}
{{template "repo/editor/commit_form" .}}
</form>
</div>
diff --git a/templates/repo/editor/fork.tmpl b/templates/repo/editor/fork.tmpl
new file mode 100644
index 0000000000..e28b2ba7a2
--- /dev/null
+++ b/templates/repo/editor/fork.tmpl
@@ -0,0 +1,18 @@
+{{template "base/head" .}}
+<div role="main" aria-label="{{.Title}}" class="page-content repository">
+ {{template "repo/header" .}}
+ <div class="ui container">
+ {{template "base/alert" .}}
+ <form class="ui form form-fetch-action" method="post" action="{{.RepoLink}}/_fork/{{.BranchName | PathEscapeSegments}}">
+ {{.CsrfTokenHtml}}
+ <div class="tw-text-center">
+ <div class="tw-my-[40px]">
+ <h3>{{ctx.Locale.Tr "repo.editor.fork_create"}}</h3>
+ <p>{{ctx.Locale.Tr "repo.editor.fork_create_description"}}</p>
+ </div>
+ <button class="ui primary button">{{ctx.Locale.Tr "repo.fork_repo"}}</button>
+ </div>
+ </form>
+ </div>
+</div>
+{{template "base/footer" .}}
diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl
index 33a7c2a89d..fa00edd92e 100644
--- a/templates/repo/editor/patch.tmpl
+++ b/templates/repo/editor/patch.tmpl
@@ -3,15 +3,14 @@
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
- <form class="ui edit form" method="post" action="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}"
+ <form class="ui edit form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}"
data-text-empty-confirm-header="{{ctx.Locale.Tr "repo.editor.commit_empty_file_header"}}"
data-text-empty-confirm-content="{{ctx.Locale.Tr "repo.editor.commit_empty_file_text"}}"
>
{{.CsrfTokenHtml}}
- <input type="hidden" name="last_commit" value="{{.last_commit}}">
- <input type="hidden" name="page_has_posted" value="{{.PageHasPosted}}">
+ {{template "repo/editor/common_top" .}}
<div class="repo-editor-header">
- <div class="ui breadcrumb field {{if .Err_TreePath}}error{{end}}">
+ <div class="breadcrumb">
{{ctx.Locale.Tr "repo.editor.patching"}}
<a class="section" href="{{$.RepoLink}}">{{.Repository.FullName}}</a>
<div class="breadcrumb-divider">:</div>
diff --git a/templates/repo/editor/upload.tmpl b/templates/repo/editor/upload.tmpl
index 5725020406..3e36c77b3b 100644
--- a/templates/repo/editor/upload.tmpl
+++ b/templates/repo/editor/upload.tmpl
@@ -3,25 +3,11 @@
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
- <form class="ui comment form" method="post">
+ <form class="ui comment form form-fetch-action" method="post" action="{{.CommitFormOptions.TargetFormAction}}">
{{.CsrfTokenHtml}}
+ {{template "repo/editor/common_top" .}}
<div class="repo-editor-header">
- <div class="ui breadcrumb field {{if .Err_TreePath}}error{{end}}">
- <a class="section" href="{{$.BranchLink}}">{{.Repository.Name}}</a>
- {{$n := len .TreeNames}}
- {{$l := Eval $n "-" 1}}
- {{range $i, $v := .TreeNames}}
- <div class="breadcrumb-divider">/</div>
- {{if eq $i $l}}
- <input type="text" id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.add_subdir"}}" autofocus>
- <span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
- {{else}}
- <span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
- {{end}}
- {{end}}
- <span>{{ctx.Locale.Tr "repo.editor.or"}} <a href="{{$.BranchLink}}{{if not .IsNewFile}}/{{.TreePath | PathEscapeSegments}}{{end}}">{{ctx.Locale.Tr "repo.editor.cancel_lower"}}</a></span>
- <input type="hidden" id="tree_path" name="tree_path" value="{{.TreePath}}" required>
- </div>
+ {{template "repo/editor/common_breadcrumb" .}}
</div>
<div class="field">
{{template "repo/upload" .}}
diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl
index ae3f95045b..32b5c8401b 100644
--- a/templates/repo/empty.tmpl
+++ b/templates/repo/empty.tmpl
@@ -47,7 +47,7 @@
<h3>{{ctx.Locale.Tr "repo.create_new_repo_command"}}</h3>
<div class="markup">
<pre><code>touch README.md
-git init
+git init{{if ne .Repository.ObjectFormatName "sha1"}} --object-format={{.Repository.ObjectFormatName}}{{end}}{{/* for sha256 repo, it needs to set "object-format" explicitly*/}}
{{if ne .Repository.DefaultBranch "master"}}git checkout -b {{.Repository.DefaultBranch}}{{end}}
git add README.md
git commit -m "first commit"
diff --git a/templates/repo/forks.tmpl b/templates/repo/forks.tmpl
index 725b67c651..6e46457893 100644
--- a/templates/repo/forks.tmpl
+++ b/templates/repo/forks.tmpl
@@ -1,20 +1,12 @@
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository forks">
{{template "repo/header" .}}
- <div class="ui container">
+ <div class="ui container fork-list">
<h2 class="ui dividing header">
{{ctx.Locale.Tr "repo.forks"}}
</h2>
- <div class="flex-list">
- {{range .Forks}}
- <div class="flex-item tw-border-0 repo-fork-item">
- <span>{{ctx.AvatarUtils.Avatar .Owner}}</span>
- <span><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / <a href="{{.Link}}">{{.Name}}</a></span>
- </div>
- {{end}}
- </div>
+ {{template "shared/repo/list" .}}
+ {{template "base/paginate" .}}
</div>
-
- {{template "base/paginate" .}}
</div>
{{template "base/footer" .}}
diff --git a/templates/repo/graph.tmpl b/templates/repo/graph.tmpl
index 93cb5903cf..cd115f95dc 100644
--- a/templates/repo/graph.tmpl
+++ b/templates/repo/graph.tmpl
@@ -2,7 +2,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository commits">
{{template "repo/header" .}}
<div class="ui container">
- <div id="git-graph-container" class="ui segment{{if eq .Mode "monochrome"}} monochrome{{end}}">
+ <div id="git-graph-container" class="ui segment {{if eq .Mode "monochrome"}}monochrome{{end}}">
<h2 class="ui header dividing">
{{ctx.Locale.Tr "repo.commit_graph"}}
<div class="ui icon buttons tiny color-buttons">
@@ -11,35 +11,30 @@
<div class="default text">{{ctx.Locale.Tr "repo.commit_graph.select"}}</div>
<div class="menu">
<div class="item" data-value="...flow-hide-pr-refs">
- <span class="truncate">
- {{svg "octicon-eye-closed" 16 "tw-mr-1"}}<span title="{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}">{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}</span>
- </span>
+ {{svg "octicon-eye-closed"}}
+ <span class="gt-ellipsis" title="{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}">{{ctx.Locale.Tr "repo.commit_graph.hide_pr_refs"}}</span>
</div>
{{range .AllRefs}}
{{$refGroup := .RefGroup}}
{{if eq $refGroup "pull"}}
<div class="item" data-value="{{.Name}}">
- <span class="truncate">
- {{svg "octicon-git-pull-request" 16 "tw-mr-1"}}<span title="{{.ShortName}}">#{{.ShortName}}</span>
- </span>
+ {{svg "octicon-git-pull-request"}}
+ <span class="gt-ellipsis" title="{{.ShortName}}">#{{.ShortName}}</span>
</div>
{{else if eq $refGroup "tags"}}
<div class="item" data-value="{{.Name}}">
- <span class="truncate">
- {{svg "octicon-tag" 16 "tw-mr-1"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
- </span>
+ {{svg "octicon-tag"}}
+ <span class="gt-ellipsis" title="{{.ShortName}}">{{.ShortName}}</span>
</div>
{{else if eq $refGroup "remotes"}}
<div class="item" data-value="{{.Name}}">
- <span class="truncate">
- {{svg "octicon-cross-reference" 16 "tw-mr-1"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
- </span>
+ {{svg "octicon-cross-reference"}}
+ <span class="gt-ellipsis" title="{{.ShortName}}">{{.ShortName}}</span>
</div>
{{else if eq $refGroup "heads"}}
<div class="item" data-value="{{.Name}}">
- <span class="truncate">
- {{svg "octicon-git-branch" 16 "tw-mr-1"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
- </span>
+ {{svg "octicon-git-branch"}}
+ <span class="gt-ellipsis" title="{{.ShortName}}">{{.ShortName}}</span>
</div>
{{end}}
{{end}}
@@ -49,14 +44,12 @@
<button id="flow-color-colored" class="ui icon button{{if ne .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.color"}}">{{svg "material-palette" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.commit_graph.color"}}</button>
</div>
</h2>
- <div class="ui dividing"></div>
- <div class="is-loading tw-py-32 tw-hidden" id="loading-indicator"></div>
- {{template "repo/graph/svgcontainer" .}}
- {{template "repo/graph/commits" .}}
+ <div id="git-graph-body">
+ {{template "repo/graph/svgcontainer" .}}
+ {{template "repo/graph/commits" .}}
+ {{template "base/paginate" .}}
+ </div>
</div>
</div>
</div>
-<div id="pagination">
- {{template "base/paginate" .}}
-</div>
{{template "base/footer" .}}
diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl
index 630c4579ea..07ec076697 100644
--- a/templates/repo/graph/commits.tmpl
+++ b/templates/repo/graph/commits.tmpl
@@ -5,10 +5,13 @@
{{if $commit.OnlyRelation}}
<span></span>
{{else}}
- {{template "repo/commit_sign_badge" dict "Commit" $commit.Commit "CommitBaseLink" (print $.RepoLink "/commit") "CommitSignVerification" $commit.Verification}}
+ {{/* every field must be in a span to get correctly styled */}}
+ <span>
+ {{template "repo/commit_sign_badge" dict "Commit" $commit.Commit "CommitBaseLink" (print $.RepoLink "/commit") "CommitSignVerification" $commit.Verification}}
+ </span>
<span class="message tw-inline-block gt-ellipsis">
- <span>{{ctx.RenderUtils.RenderCommitMessage $commit.Subject ($.Repository.ComposeCommentMetas ctx)}}</span>
+ {{ctx.RenderUtils.RenderCommitMessage $commit.Subject $.Repository}}
</span>
<span class="commit-refs flex-text-inline">
@@ -22,7 +25,7 @@
</a>
{{end}}
{{else if eq $refGroup "tags"}}
- {{- template "repo/tag/name" dict "RepoLink" $.Repository.Link "TagName" .ShortName -}}
+ {{- template "repo/tag/name" dict "AdditionalClasses" "tag-label" "RepoLink" $.Repository.Link "TagName" .ShortName -}}
{{else if eq $refGroup "remotes"}}
<a class="ui basic tiny button" href="{{$.RepoLink}}/src/commit/{{$commit.Rev|PathEscape}}">
{{svg "octicon-cross-reference"}} {{.ShortName}}
diff --git a/templates/repo/graph/div.tmpl b/templates/repo/graph/div.tmpl
index c0bd4e269a..12c2869cd8 100644
--- a/templates/repo/graph/div.tmpl
+++ b/templates/repo/graph/div.tmpl
@@ -1,7 +1,3 @@
-<div>
- {{template "repo/graph/svgcontainer" .}}
- {{template "repo/graph/commits" .}}
- <div id="pagination">
- {{template "base/paginate" .}}
- </div>
-</div>
+{{template "repo/graph/svgcontainer" .}}
+{{template "repo/graph/commits" .}}
+{{template "base/paginate" .}}
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index 929f41b93f..b61076ff46 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -226,11 +226,19 @@
</a>
{{end}}
</div>
- {{else if .Permission.IsAdmin}}
+ {{else}}
<div class="overflow-menu-items">
+ {{if(and .Repository.IsBeingCreated (.Permission.CanRead ctx.Consts.RepoUnitTypeCode))}}
+ <a class="{{if not .PageIsRepoSettings}}active {{end}}item" href="{{.RepoLink}}">
+ {{svg "octicon-clock"}} {{ctx.Locale.Tr "repo.migration_status"}}
+ </a>
+ {{end}}
+
+ {{if .Permission.IsAdmin}}
<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
</a>
+ {{end}}
</div>
{{end}}
</overflow-menu>
diff --git a/templates/repo/home_sidebar_bottom.tmpl b/templates/repo/home_sidebar_bottom.tmpl
index f780dc122d..01e630ccbf 100644
--- a/templates/repo/home_sidebar_bottom.tmpl
+++ b/templates/repo/home_sidebar_bottom.tmpl
@@ -4,14 +4,14 @@
<div class="flex-item">
<div class="flex-item-main">
<div class="flex-item-title">
- <a class="item muted" href="{{.Link}}/releases">
+ <a class="item muted" href="{{.RepoLink}}/releases">
{{ctx.Locale.Tr "repo.releases"}}
<span class="ui small label">{{.NumReleases}}</span>
</a>
</div>
<div class="flex-item">
- <div class="flex-item-icon">
- {{svg "octicon-tag" 16}}
+ <div class="flex-item-leading">
+ <div class="tw-mt-1">{{svg "octicon-tag" 16}}</div>
</div>
<div class="flex-item-main">
<div class="flex-item-header">
diff --git a/templates/repo/issue/card.tmpl b/templates/repo/issue/card.tmpl
index 41fe6cea8f..6dae49c455 100644
--- a/templates/repo/issue/card.tmpl
+++ b/templates/repo/issue/card.tmpl
@@ -4,7 +4,7 @@
{{if $attachments}}
<div class="card-attachment-images">
{{range $attachments}}
- <img src="{{.DownloadURL}}" alt="{{.Name}}" />
+ <img loading="lazy" src="{{.DownloadURL}}" alt="{{.Name}}" />
{{end}}
</div>
{{end}}
@@ -63,16 +63,21 @@
{{if or .Labels .Assignees}}
<div class="issue-card-bottom">
- <div class="labels-list">
- {{range .Labels}}
- <a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{ctx.RenderUtils.RenderLabel .}}</a>
+ {{/* the labels container must always be present, to help the assignees list right-aligned */}}
+ <div class="issue-card-bottom-part labels-list">
+ {{range $label := .Labels}}
+ {{$link := print $.Issue.Repo.Link "/issues"}}
+ {{$link = QueryBuild $link "labels" $label.ID}}
+ {{ctx.RenderUtils.RenderLabelWithLink $label $link}}
{{end}}
</div>
- <div class="issue-card-assignees">
+ {{if .Assignees}}
+ <div class="issue-card-bottom-part tw-justify-end">
{{range .Assignees}}
- <a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28}}</a>
+ <a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 24}}</a>
{{end}}
</div>
+ {{end}}
</div>
{{end}}
{{end}}
diff --git a/templates/repo/issue/filter_item_label.tmpl b/templates/repo/issue/filter_item_label.tmpl
index 0883d93804..04c3605a6a 100644
--- a/templates/repo/issue/filter_item_label.tmpl
+++ b/templates/repo/issue/filter_item_label.tmpl
@@ -22,7 +22,7 @@
<span class="label-filter-exclude-info">{{ctx.Locale.Tr "repo.issues.filter_label_exclude"}}</span>
<div class="divider"></div>
<a class="item label-filter-query-default" href="{{QueryBuild $queryLink "labels" NIL}}">{{ctx.Locale.Tr "repo.issues.filter_label_no_select"}}</a>
- <a class="item label-filter-query-not-set" href="{{QueryBuild $queryLink "labels" 0}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a>
+ <a class="item label-filter-query-not-set" href="{{QueryBuild $queryLink "labels" "0"}}">{{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}}</a>
{{/* The logic here is not the same as the label selector in the issue sidebar.
The one in the issue sidebar renders "repo labels | divider | org labels".
Maybe the logic should be updated to be consistent.*/}}
diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl
index 048b5f37b7..bfdf94513e 100644
--- a/templates/repo/issue/filter_list.tmpl
+++ b/templates/repo/issue/filter_list.tmpl
@@ -15,7 +15,7 @@
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_milestone"}}">
</div>
<div class="divider"></div>
- <a class="{{if not $.MilestoneID}}active selected {{end}}item" href="{{QueryBuild $queryLink "milestone" 0}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_all"}}</a>
+ <a class="{{if not $.MilestoneID}}active selected {{end}}item" href="{{QueryBuild $queryLink "milestone" NIL}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_all"}}</a>
<a class="{{if $.MilestoneID}}{{if eq $.MilestoneID -1}}active selected {{end}}{{end}}item" href="{{QueryBuild $queryLink "milestone" -1}}">{{ctx.Locale.Tr "repo.issues.filter_milestone_none"}}</a>
{{if .OpenMilestones}}
<div class="divider"></div>
@@ -106,7 +106,7 @@
</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
- <a class="{{if eq .ViewType "all"}}active {{end}}item" href="{{QueryBuild $queryLink "type" "all"}}">{{ctx.Locale.Tr "repo.issues.filter_type.all_issues"}}</a>
+ <a class="{{if eq .ViewType "all"}}active {{end}}item" href="{{QueryBuild $queryLink "type" "all"}}">{{if .PageIsPullList}}{{ctx.Locale.Tr "repo.issues.filter_type.all_pull_requests"}}{{else}}{{ctx.Locale.Tr "repo.issues.filter_type.all_issues"}}{{end}}</a>
<a class="{{if eq .ViewType "assigned"}}active {{end}}item" href="{{QueryBuild $queryLink "type" "assigned"}}">{{ctx.Locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a class="{{if eq .ViewType "created_by"}}active {{end}}item" href="{{QueryBuild $queryLink "type" "created_by"}}">{{ctx.Locale.Tr "repo.issues.filter_type.created_by_you"}}</a>
{{if .PageIsPullList}}
diff --git a/templates/repo/issue/labels/label_edit_modal.tmpl b/templates/repo/issue/labels/label_edit_modal.tmpl
index 364fc508f1..6837d66dce 100644
--- a/templates/repo/issue/labels/label_edit_modal.tmpl
+++ b/templates/repo/issue/labels/label_edit_modal.tmpl
@@ -5,7 +5,7 @@
>
<div class="header"></div>
<div class="content">
- <form class="ui form ignore-dirty" method="post">
+ <form class="ui form ignore-dirty form-fetch-action" method="post">
{{.CsrfTokenHtml}}
<input name="id" type="hidden">
<div class="required field">
@@ -50,7 +50,8 @@
<div class="field">
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
<div class="column js-color-picker-input">
- <input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7">
+ <!-- the "#" is optional because backend NormalizeColor is able to handle it, API also accepts both formats, and it is easier for users to directly copy-paste a hex value -->
+ <input name="color" value="#70c24a" placeholder="#c320f6" required pattern="^#?([\dA-Fa-f]{3}|[\dA-Fa-f]{6})$" maxlength="7">
{{template "repo/issue/label_precolors"}}
</div>
</div>
diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl
index cc231971e0..c8a6b46cc4 100644
--- a/templates/repo/issue/labels/label_list.tmpl
+++ b/templates/repo/issue/labels/label_list.tmpl
@@ -26,7 +26,7 @@
<div class="divider"></div>
{{end}}
- <ul class="issue-label-list">
+ <ul class="issue-label-list muted-links">
{{$canEditLabel := and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
{{$canEditLabel = or $canEditLabel $.PageIsOrgSettingsLabels}}
{{range .Labels}}
@@ -42,9 +42,8 @@
<a class="open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a>
{{end}}
</div>
- <div class="label-operation tw-flex">
+ <div class="label-operation">
{{template "repo/issue/labels/label_archived" .}}
- <div class="tw-flex tw-ml-auto">
{{if $canEditLabel}}
<a class="edit-label-button" href="#"
data-label-id="{{.ID}}" data-label-name="{{.Name}}" data-label-color="{{.Color}}"
@@ -57,7 +56,6 @@
data-modal-confirm-content="{{ctx.Locale.Tr "repo.issues.label_deletion_desc"}}"
>{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
{{end}}
- </div>
</div>
</li>
{{end}}
diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl
index 5701c1faa6..8805c709a0 100644
--- a/templates/repo/issue/milestones.tmpl
+++ b/templates/repo/issue/milestones.tmpl
@@ -76,7 +76,7 @@
{{else}}
<a class="link-action flex-text-inline" href data-url="{{$.Link}}/{{.ID}}/close">{{svg "octicon-x" 14}}{{ctx.Locale.Tr "repo.milestones.close"}}</a>
{{end}}
- <a class="delete-button flex-text-inline" href="#" data-url="{{$.RepoLink}}/milestones/delete" data-id="{{.ID}}">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
+ <a class="link-action flex-text-inline text red" href data-modal-confirm="#repo-milestone-delete-modal" data-url="{{$.RepoLink}}/milestones/delete?id={{.ID}}">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
</div>
{{end}}
</div>
@@ -92,15 +92,11 @@
</div>
{{if or .CanWriteIssues .CanWritePulls}}
- <div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.milestones.deletion"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "repo.milestones.deletion_desc"}}</p>
- </div>
+ <div class="ui small modal" id="repo-milestone-delete-modal">
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.milestones.deletion"}}</div>
+ <div class="content"><p>{{ctx.Locale.Tr "repo.milestones.deletion_desc"}}</p></div>
{{template "base/modal_actions_confirm" .}}
</div>
{{end}}
+
{{template "base/footer" .}}
diff --git a/templates/repo/issue/search.tmpl b/templates/repo/issue/search.tmpl
index 07732ab5e7..de6c194ee8 100644
--- a/templates/repo/issue/search.tmpl
+++ b/templates/repo/issue/search.tmpl
@@ -8,6 +8,7 @@
<input type="hidden" name="project" value="{{$.ProjectID}}">
<input type="hidden" name="assignee" value="{{$.AssigneeID}}">
<input type="hidden" name="poster" value="{{$.PosterUsername}}">
+ <input type="hidden" name="sort" value="{{$.SortType}}">
{{end}}
{{template "shared/search/input" dict "Value" .Keyword}}
{{if .PageIsIssueList}}
diff --git a/templates/repo/issue/sidebar/assignee_list.tmpl b/templates/repo/issue/sidebar/assignee_list.tmpl
index 95105dd184..1d2279a45d 100644
--- a/templates/repo/issue/sidebar/assignee_list.tmpl
+++ b/templates/repo/issue/sidebar/assignee_list.tmpl
@@ -27,7 +27,7 @@
</div>
</div>
</div>
- <div class="ui list muted-links flex-items-block tw-flex tw-flex-col tw-gap-2">
+ <div class="ui relaxed list muted-links flex-items-block">
<span class="item empty-list {{if $issueAssignees}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
{{range $issueAssignees}}
<a class="item" href="{{$pageMeta.RepoLink}}/{{if $pageMeta.IsPullRequest}}pulls{{else}}issues{{end}}?assignee={{.ID}}">
diff --git a/templates/repo/issue/sidebar/label_list.tmpl b/templates/repo/issue/sidebar/label_list.tmpl
index ed514f6725..15c8760d1a 100644
--- a/templates/repo/issue/sidebar/label_list.tmpl
+++ b/templates/repo/issue/sidebar/label_list.tmpl
@@ -43,7 +43,7 @@
</div>
</div>
- <div class="ui list labels-list tw-my-2 tw-flex tw-gap-2">
+ <div class="ui list labels-list">
<span class="item empty-list {{if $data.SelectedLabelIDs}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_label"}}</span>
{{range $data.AllLabels}}
{{if .IsChecked}}
diff --git a/templates/repo/issue/sidebar/reviewer_list.tmpl b/templates/repo/issue/sidebar/reviewer_list.tmpl
index 9cb11ec7ee..2b5ca80fe7 100644
--- a/templates/repo/issue/sidebar/reviewer_list.tmpl
+++ b/templates/repo/issue/sidebar/reviewer_list.tmpl
@@ -43,7 +43,7 @@
</div>
</div>
- <div class="ui relaxed list flex-items-block tw-my-4">
+ <div class="ui relaxed list flex-items-block">
<span class="item empty-list {{if or $data.OriginalReviews $data.CurrentPullReviewers}}tw-hidden{{end}}">
{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}
</span>
diff --git a/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl b/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl
index 0176aede7a..ab8600b068 100644
--- a/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl
+++ b/templates/repo/issue/sidebar/stopwatch_timetracker.tmpl
@@ -16,14 +16,14 @@
</a>
<div class="divider"></div>
{{if $.IsStopwatchRunning}}
- <a class="item issue-stop-time link-action" data-url="{{.Issue.Link}}/times/stopwatch/toggle">
+ <a class="item issue-stop-time link-action" data-url="{{.Issue.Link}}/times/stopwatch/stop">
{{svg "octicon-stopwatch"}} {{ctx.Locale.Tr "repo.issues.timetracker_timer_stop"}}
</a>
<a class="item issue-cancel-time link-action" data-url="{{.Issue.Link}}/times/stopwatch/cancel">
{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.timetracker_timer_discard"}}
</a>
{{else}}
- <a class="item issue-start-time link-action" data-url="{{.Issue.Link}}/times/stopwatch/toggle">
+ <a class="item issue-start-time link-action" data-url="{{.Issue.Link}}/times/stopwatch/start">
{{svg "octicon-stopwatch"}} {{ctx.Locale.Tr "repo.issues.timetracker_timer_start"}}
</a>
<a class="item issue-add-time show-modal" data-modal="#issue-time-manually-add-modal">
diff --git a/templates/repo/issue/sidebar/wip_switch.tmpl b/templates/repo/issue/sidebar/wip_switch.tmpl
index b007399deb..8c40908f62 100644
--- a/templates/repo/issue/sidebar/wip_switch.tmpl
+++ b/templates/repo/issue/sidebar/wip_switch.tmpl
@@ -1,5 +1,5 @@
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .HasMerged) (not .Issue.IsClosed) (not .IsPullWorkInProgress)}}
- <a class="toggle-wip tw-block tw-mt-2" data-title="{{.Issue.Title}}" data-wip-prefix="{{index .PullRequestWorkInProgressPrefixes 0}}" data-update-url="{{.Issue.Link}}/title">
+ <a data-global-init="initPullRequestWipToggle" data-title="{{.Issue.Title}}" data-wip-prefix="{{index .PullRequestWorkInProgressPrefixes 0}}" data-update-url="{{.Issue.Link}}/title">
{{ctx.Locale.Tr "repo.pulls.still_in_progress"}} {{ctx.Locale.Tr "repo.pulls.add_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
</a>
{{end}}
diff --git a/templates/repo/issue/view_content/attachments.tmpl b/templates/repo/issue/view_content/attachments.tmpl
index 2155f78656..e865050559 100644
--- a/templates/repo/issue/view_content/attachments.tmpl
+++ b/templates/repo/issue/view_content/attachments.tmpl
@@ -31,7 +31,7 @@
{{if FilenameIsImage .Name}}
{{if not (StringUtils.Contains (StringUtils.ToString $.RenderedContent) .UUID)}}
<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}">
- <img alt="{{.Name}}" src="{{.DownloadURL}}" title="{{ctx.Locale.Tr "repo.issues.attachment.open_tab" .Name}}">
+ <img loading="lazy" alt="{{.Name}}" src="{{.DownloadURL}}" title="{{ctx.Locale.Tr "repo.issues.attachment.open_tab" .Name}}">
</a>
{{end}}
{{end}}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 8f49bcf07e..50b8f58fc3 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -163,12 +163,13 @@
</span>
<div class="detail flex-text-block">
{{svg "octicon-git-commit"}}
+ {{/* the content is a link like <a href="{RepoLink}/commit/{CommitID}">message title</a> (from CreateRefComment) */}}
<span class="text grey muted-links">{{.Content | SanitizeHTML}}</span>
</div>
</div>
{{else if eq .Type 7}}
{{if or .AddedLabels .RemovedLabels}}
- <div class="timeline-item event" id="{{.HashTag}}">
+ <div class="timeline-item event with-labels-list-inline" id="{{.HashTag}}">
<span class="badge">{{svg "octicon-tag"}}</span>
{{template "shared/user/avatarlink" dict "user" .Poster}}
<span class="text grey muted-links">
@@ -615,7 +616,7 @@
<div class="timeline-item-group">
<div class="timeline-item event" id="{{.HashTag}}">
<a class="timeline-avatar"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>
- <img alt src="{{.Poster.AvatarLink ctx}}" width="40" height="40">
+ <img loading="lazy" alt src="{{.Poster.AvatarLink ctx}}" width="40" height="40">
</a>
<span class="badge grey">{{svg "octicon-x" 16}}</span>
<span class="text grey muted-links">
diff --git a/templates/repo/issue/view_content/pull_merge_box.tmpl b/templates/repo/issue/view_content/pull_merge_box.tmpl
index 641520247d..46bcd3b8b3 100644
--- a/templates/repo/issue/view_content/pull_merge_box.tmpl
+++ b/templates/repo/issue/view_content/pull_merge_box.tmpl
@@ -95,7 +95,7 @@
{{ctx.Locale.Tr "repo.pulls.cannot_merge_work_in_progress"}}
</div>
{{if or .HasIssuesOrPullsWritePermission .IsIssuePoster}}
- <button class="ui compact button toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefix="{{.WorkInProgressPrefix}}" data-update-url="{{.Issue.Link}}/title">
+ <button class="ui compact button" data-global-init="initPullRequestWipToggle" data-title="{{.Issue.Title}}" data-wip-prefix="{{.WorkInProgressPrefix}}" data-update-url="{{.Issue.Link}}/title">
{{ctx.Locale.Tr "repo.pulls.remove_prefix" .WorkInProgressPrefix}}
</button>
{{end}}
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index a4be598540..b8f28dfd9b 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -13,7 +13,7 @@
{{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
<div class="issue-title" id="issue-title-display">
<h1 class="tw-break-anywhere">
- {{ctx.RenderUtils.RenderIssueTitle .Issue.Title ($.Repository.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderIssueTitle .Issue.Title $.Repository}}
<span class="index">#{{.Issue.Index}}</span>
</h1>
<div class="issue-title-buttons">
diff --git a/templates/repo/latest_commit.tmpl b/templates/repo/latest_commit.tmpl
index da457e423a..cff338949f 100644
--- a/templates/repo/latest_commit.tmpl
+++ b/templates/repo/latest_commit.tmpl
@@ -21,10 +21,10 @@
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}}
{{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}}
- <span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.Message $commitLink ($.Repository.ComposeCommentMetas ctx)}}</span>
+ <span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.Message $commitLink $.Repository}}</span>
{{if IsMultilineCommitMessage .LatestCommit.Message}}
<button class="ui button ellipsis-button" aria-expanded="false" data-global-click="onRepoEllipsisButtonClick">...</button>
- <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message ($.Repository.ComposeCommentMetas ctx)}}</pre>
+ <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message $.Repository}}</pre>
{{end}}
</span>
{{end}}
diff --git a/templates/repo/migrate/migrate.tmpl b/templates/repo/migrate/migrate.tmpl
index c5c697edff..79ac5cc8a1 100644
--- a/templates/repo/migrate/migrate.tmpl
+++ b/templates/repo/migrate/migrate.tmpl
@@ -19,7 +19,7 @@
<div class="header tw-text-center">
{{.Title}}
</div>
- <div class="description tw-text-center">
+ <div class="description tw-text-center tw-text-balance">
{{ctx.Locale.Tr (printf "repo.migrate.%s.description" .Name)}}
</div>
</div>
diff --git a/templates/repo/pulls/fork.tmpl b/templates/repo/pulls/fork.tmpl
index adf9e250c1..0d775ed6a0 100644
--- a/templates/repo/pulls/fork.tmpl
+++ b/templates/repo/pulls/fork.tmpl
@@ -6,7 +6,7 @@
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
- <form class="ui form left-right-form" action="{{.Link}}" method="post">
+ <form class="ui form form-fetch-action left-right-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="inline required field {{if .Err_Owner}}error{{end}}">
<label>{{ctx.Locale.Tr "repo.owner"}}</label>
diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl
index 01ec0ff188..882ffe40b7 100644
--- a/templates/repo/release/list.tmpl
+++ b/templates/repo/release/list.tmpl
@@ -50,7 +50,11 @@
{{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "tw-mr-1"}}{{$release.OriginalAuthor}}
{{else if $release.Publisher}}
{{ctx.AvatarUtils.Avatar $release.Publisher 20 "tw-mr-1"}}
- <a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a>
+ {{if gt $release.PublisherID 0}}
+ <a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a>
+ {{else}}
+ {{$release.Publisher.GetDisplayName}}
+ {{end}}
{{else}}
Ghost
{{end}}
@@ -61,7 +65,7 @@
{{if $release.CreatedUnix}}
<span class="time">{{DateUtils.TimeSince $release.CreatedUnix}}</span>
{{end}}
- {{if and (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
+ {{if and (gt $release.NumCommits 0) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span>
{{end}}
</p>
@@ -88,7 +92,7 @@
{{end}}
{{range $att := $release.Attachments}}
<li class="item">
- <a target="_blank" class="tw-flex-grow-[2] gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}">
+ <a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}">
<strong class="flex-text-inline">{{svg "octicon-package" 16 "download-icon"}}<span class="gt-ellipsis">{{$att.Name}}</span></strong>
</a>
<div class="attachment-right-info flex-text-inline">
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl
index f61a8ef9e7..109a18fa0e 100644
--- a/templates/repo/release/new.tmpl
+++ b/templates/repo/release/new.tmpl
@@ -105,7 +105,7 @@
<a class="ui small button" href="{{.RepoLink}}/releases">
{{ctx.Locale.Tr "repo.release.cancel"}}
</a>
- <a class="ui small red button delete-button" data-url="{{$.RepoLink}}/releases/delete" data-id="{{.ID}}">
+ <a class="ui small red button link-action" data-modal-confirm="#repo-release-delete-modal" data-url="{{$.RepoLink}}/releases/delete?id={{.ID}}">
{{ctx.Locale.Tr "repo.release.delete_release"}}
</a>
{{if .IsDraft}}
@@ -129,15 +129,11 @@
</div>
{{if .PageIsEditRelease}}
- <div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.release.deletion"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "repo.release.deletion_desc"}}</p>
- </div>
+ <div class="ui small modal" id="repo-release-delete-modal">
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.release.deletion"}}</div>
+ <div class="content"><p>{{ctx.Locale.Tr "repo.release.deletion_desc"}}</p></div>
{{template "base/modal_actions_confirm" .}}
</div>
{{end}}
+
{{template "base/footer" .}}
diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl
index ef4732540b..e8e7a3f1c2 100644
--- a/templates/repo/settings/branches.tmpl
+++ b/templates/repo/settings/branches.tmpl
@@ -49,8 +49,8 @@
</div>
</div>
<div class="flex-item-trailing">
- <a class="rm ui tiny button" href="{{$.Repository.Link}}/settings/branches/edit?rule_name={{.RuleName}}">{{ctx.Locale.Tr "repo.settings.edit_protected_branch"}}</a>
- <button class="ui red tiny button delete-button" data-url="{{$.Repository.Link}}/settings/branches/{{.ID}}/delete" data-id="{{.ID}}">
+ <a class="ui tiny button" href="{{$.Repository.Link}}/settings/branches/edit?rule_name={{.RuleName}}">{{ctx.Locale.Tr "repo.settings.edit_protected_branch"}}</a>
+ <button class="ui red tiny button link-action" data-modal-confirm="#repo-branch-protection-delete-modal" data-url="{{$.Repository.Link}}/settings/branches/{{.ID}}/delete?id={{.ID}}">
{{ctx.Locale.Tr "repo.settings.protected_branch.delete_rule"}}
</button>
</div>
@@ -65,14 +65,9 @@
{{end}}
</div>
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.settings.protected_branch_deletion"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "repo.settings.protected_branch_deletion_desc"}}</p>
- </div>
+<div class="ui small modal" id="repo-branch-protection-delete-modal">
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.settings.protected_branch_deletion"}}</div>
+ <div class="content"><p>{{ctx.Locale.Tr "repo.settings.protected_branch_deletion_desc"}}</p></div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/repo/settings/collaboration.tmpl b/templates/repo/settings/collaboration.tmpl
index 4461398258..62903e1cfb 100644
--- a/templates/repo/settings/collaboration.tmpl
+++ b/templates/repo/settings/collaboration.tmpl
@@ -29,7 +29,7 @@
</div>
</div>
</div>
- <button class="ui red tiny button inline delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
+ <button class="ui red tiny button link-action" data-modal-confirm="#repo-collaborator-delete-modal" data-url="{{$.Link}}/delete?id={{.ID}}">
{{ctx.Locale.Tr "repo.settings.delete_collaborator"}}
</button>
</div>
@@ -63,19 +63,39 @@
{{.Name}}
</a>
<div class="flex-item-body flex-text-block">
+ {{/*FIXME: TEAM-UNIT-PERMISSION this display is not right, search the fixme keyword to see more details */}}
{{svg "octicon-shield-lock"}}
- {{if eq .AccessMode 1}}{{ctx.Locale.Tr "repo.settings.collaboration.read"}}{{else if eq .AccessMode 2}}{{ctx.Locale.Tr "repo.settings.collaboration.write"}}{{else if eq .AccessMode 3}}{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}{{else if eq .AccessMode 4}}{{ctx.Locale.Tr "repo.settings.collaboration.owner"}}{{else}}{{ctx.Locale.Tr "repo.settings.collaboration.undefined"}}{{end}}
+ {{if eq .AccessMode 0}}
+ {{ctx.Locale.Tr "repo.settings.collaboration.per_unit"}}
+ {{else if eq .AccessMode 1}}
+ {{ctx.Locale.Tr "repo.settings.collaboration.read"}}
+ {{else if eq .AccessMode 2}}
+ {{ctx.Locale.Tr "repo.settings.collaboration.write"}}
+ {{else if eq .AccessMode 3}}
+ {{ctx.Locale.Tr "repo.settings.collaboration.admin"}}
+ {{else if eq .AccessMode 4}}
+ {{ctx.Locale.Tr "repo.settings.collaboration.owner"}}
+ {{else}}
+ {{ctx.Locale.Tr "repo.settings.collaboration.undefined"}}
+ {{end}}
</div>
- {{if or (eq .AccessMode 1) (eq .AccessMode 2)}}
+ {{if or (eq .AccessMode 0) (eq .AccessMode 1) (eq .AccessMode 2)}}
{{$first := true}}
<div class="flex-item-body" data-tooltip-content="{{ctx.Locale.Tr "repo.settings.change_team_permission_tip"}}">
- Sections: {{range $u, $unit := $.Units}}{{if and ($.Repo.UnitEnabled ctx $unit.Type) ($team.UnitEnabled ctx $unit.Type)}}{{if $first}}{{$first = false}}{{else}}, {{end}}{{ctx.Locale.Tr $unit.NameKey}}{{end}}{{end}} {{if $first}}None{{end}}
+ Units:
+ {{range $u, $unit := $.Units}}
+ {{- if and ($.Repo.UnitEnabled ctx $unit.Type) ($team.UnitEnabled ctx $unit.Type) -}}
+ {{- Iif $first "" ", "}}{{ctx.Locale.Tr $unit.NameKey -}}
+ {{- $first = false -}}
+ {{- end -}}
+ {{end}}
+ {{if $first}}None{{end}}
</div>
{{end}}
</div>
{{if $allowedToChangeTeams}}
<div class="flex-item-trailing" {{if .IncludesAllRepositories}} data-tooltip-content="{{ctx.Locale.Tr "repo.settings.delete_team_tip"}}"{{end}}>
- <button class="ui red tiny button inline delete-button {{if .IncludesAllRepositories}}disabled{{end}}" data-url="{{$.Link}}/team/delete" data-id="{{.ID}}">
+ <button class="ui red tiny button link-action {{if .IncludesAllRepositories}}disabled{{end}}" data-modal-confirm="#repo-collaborator-delete-modal" data-url="{{$.Link}}/team/delete?id={{.ID}}">
{{ctx.Locale.Tr "repo.settings.delete_collaborator"}}
</button>
</div>
@@ -90,7 +110,7 @@
<form class="ui form" id="repo-collab-team-form" action="{{.Link}}/team" method="post">
{{.CsrfTokenHtml}}
<div id="search-team-box" class="ui search input tw-align-middle" data-org-name="{{.OrgName}}">
- <input class="prompt" name="team" placeholder="{{ctx.Locale.Tr "search.team_kind"}}" autocomplete="off" autofocus required>
+ <input class="prompt" name="team" placeholder="{{ctx.Locale.Tr "search.team_kind"}}" autocomplete="off" required>
</div>
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.add_team"}}</button>
</form>
@@ -103,14 +123,9 @@
{{end}}
</div>
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.settings.collaborator_deletion"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "repo.settings.collaborator_deletion_desc"}}</p>
- </div>
+<div class="ui small modal" id="repo-collaborator-delete-modal">
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.settings.collaborator_deletion"}}</div>
+ <div class="content"><p>{{ctx.Locale.Tr "repo.settings.collaborator_deletion_desc"}}</p></div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl
index 5eb2a47e5a..b82d584b22 100644
--- a/templates/repo/settings/deploy_keys.tmpl
+++ b/templates/repo/settings/deploy_keys.tmpl
@@ -59,7 +59,7 @@
</div>
</div>
<div class="flex-item-trailing">
- <button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
+ <button class="ui red tiny button link-action" data-modal-confirm="#repo-deploy-key-delete-modal" data-url="{{$.Link}}/delete?id={{.ID}}">
{{ctx.Locale.Tr "settings.delete_key"}}
</button>
</div>
@@ -72,14 +72,9 @@
</div>
</div>
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.settings.deploy_key_deletion"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "repo.settings.deploy_key_deletion_desc"}}</p>
- </div>
+<div class="ui small modal" id="repo-deploy-key-delete-modal">
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.settings.deploy_key_deletion"}}</div>
+ <div class="content"><p>{{ctx.Locale.Tr "repo.settings.deploy_key_deletion_desc"}}</p></div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/repo/settings/lfs_file.tmpl b/templates/repo/settings/lfs_file.tmpl
index 7698f77b2a..cd1b168401 100644
--- a/templates/repo/settings/lfs_file.tmpl
+++ b/templates/repo/settings/lfs_file.tmpl
@@ -21,7 +21,7 @@
{{else if not .IsTextFile}}
<div class="view-raw">
{{if .IsImageFile}}
- <img alt="{{$.RawFileLink}}" src="{{$.RawFileLink}}">
+ <img loading="lazy" alt="{{$.RawFileLink}}" src="{{$.RawFileLink}}">
{{else if .IsVideoFile}}
<video controls src="{{$.RawFileLink}}">
<strong>{{ctx.Locale.Tr "repo.video_not_supported_in_browser"}}</strong>
@@ -30,8 +30,6 @@
<audio controls src="{{$.RawFileLink}}">
<strong>{{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}}</strong>
</audio>
- {{else if .IsPDFFile}}
- <div class="pdf-content is-loading" data-global-init="initPdfViewer" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "diff.view_file"}}"></div>
{{else}}
<a href="{{$.RawFileLink}}" rel="nofollow" class="tw-p-4">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
{{end}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 4d61604612..fc42056e0a 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -10,7 +10,7 @@
<input type="hidden" name="action" value="update">
<div class="required field {{if .Err_RepoName}}error{{end}}">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
- <input name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" autofocus required>
+ <input name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" required>
</div>
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.repo_size"}}</label>
diff --git a/templates/repo/settings/webhook/history.tmpl b/templates/repo/settings/webhook/history.tmpl
index ea3c037813..953ba69670 100644
--- a/templates/repo/settings/webhook/history.tmpl
+++ b/templates/repo/settings/webhook/history.tmpl
@@ -26,7 +26,7 @@
{{else}}
<span class="text red">{{svg "octicon-alert"}}</span>
{{end}}
- <a class="ui primary sha label toggle button show-panel" data-panel="#info-{{.ID}}">{{.UUID}}</a>
+ <button class="btn interact-bg tw-p-2 toggle show-panel" data-panel="#info-{{.ID}}">{{.UUID}}</button>
</div>
<span class="text grey">
{{DateUtils.TimeSince .Delivered}}
diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl
index 16ad263e42..a330448c9e 100644
--- a/templates/repo/settings/webhook/settings.tmpl
+++ b/templates/repo/settings/webhook/settings.tmpl
@@ -263,6 +263,16 @@
<div class="fourteen wide column">
<label>{{ctx.Locale.Tr "repo.settings.event_header_workflow"}}</label>
</div>
+ <!-- Workflow Run Event -->
+ <div class="seven wide column">
+ <div class="field">
+ <div class="ui checkbox">
+ <input name="workflow_run" type="checkbox" {{if .Webhook.HookEvents.Get "workflow_run"}}checked{{end}}>
+ <label>{{ctx.Locale.Tr "repo.settings.event_workflow_run"}}</label>
+ <span class="help">{{ctx.Locale.Tr "repo.settings.event_workflow_run_desc"}}</span>
+ </div>
+ </div>
+ </div>
<!-- Workflow Job Event -->
<div class="seven wide column">
<div class="field">
@@ -288,7 +298,7 @@
<label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label>
<input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}>
{{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}}
- <span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | SafeHTML)}}</span>
+ <span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" (HTMLFormat "<code>%s</code>, <code>%s</code>" "Bearer token123456" "Basic YWxhZGRpbjpvcGVuc2VzYW1l")}}</span>
{{end}}
</div>
diff --git a/templates/repo/tag/list.tmpl b/templates/repo/tag/list.tmpl
index 9789943b49..8b33b96f86 100644
--- a/templates/repo/tag/list.tmpl
+++ b/templates/repo/tag/list.tmpl
@@ -5,9 +5,7 @@
{{template "base/alert" .}}
{{template "repo/release_tag_header" .}}
<h4 class="ui top attached header">
- <div class="five wide column tw-flex tw-items-center">
- {{.TagCount}} {{ctx.Locale.Tr "repo.release.tags"}}
- </div>
+ {{.TagCount}} {{ctx.Locale.Tr "repo.release.tags"}}
</h4>
{{$canReadReleases := $.Permission.CanRead ctx.Consts.RepoUnitTypeReleases}}
<div class="ui attached segment">
@@ -15,53 +13,49 @@
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.tag_kind") "Tooltip" (ctx.Locale.Tr "search.tag_tooltip")}}
</form>
</div>
- <div class="ui attached table segment">
+ <div class="ui attached segment tw-p-0">
{{if .Releases}}
- <table class="ui very basic striped fixed table single line" id="tags-table">
- <tbody class="tag-list">
- {{range $idx, $release := .Releases}}
- <tr>
- <td class="tag-list-row">
- <h3 class="tag-list-row-title tw-mb-2">
- {{if $canReadReleases}}
- <a class="tag-list-row-link tw-flex tw-items-center" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
- {{else}}
- <a class="tag-list-row-link tw-flex tw-items-center" href="{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
- {{end}}
- </h3>
- <div class="download tw-flex tw-items-center">
- {{if $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
- {{if .CreatedUnix}}
- <span class="tw-mr-2">{{svg "octicon-clock" 16 "tw-mr-1"}}{{DateUtils.TimeSince .CreatedUnix}}</span>
- {{end}}
+ <div class="ui divided list" id="tags-table">
+ {{range $idx, $release := .Releases}}
+ <div class="item tag-list-row tw-p-4">
+ <h3 class="tag-list-row-title tw-mb-2">
+ {{if $canReadReleases}}
+ <a class="tag-list-row-link" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
+ {{else}}
+ <a class="tag-list-row-link" href="{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
+ {{end}}
+ </h3>
+ <div class="flex-text-block muted-links tw-gap-4 tw-flex-wrap">
+ {{if $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
+ {{if .CreatedUnix}}
+ <span class="flex-text-inline">{{svg "octicon-clock"}}{{DateUtils.TimeSince .CreatedUnix}}</span>
+ {{end}}
- <a class="tw-mr-2 tw-font-mono muted" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .Sha1}}</a>
+ <a class="flex-text-inline tw-font-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit"}}{{ShortSha .Sha1}}</a>
- {{if not $.DisableDownloadSourceArchives}}
- <a class="archive-link tw-mr-2 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-1"}}ZIP</a>
- <a class="archive-link tw-mr-2 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-1"}}TAR.GZ</a>
- {{end}}
+ {{if not $.DisableDownloadSourceArchives}}
+ <a class="archive-link flex-text-inline" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}ZIP</a>
+ <a class="archive-link flex-text-inline" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}TAR.GZ</a>
+ {{end}}
- {{if (and $canReadReleases $.CanCreateRelease $release.IsTag)}}
- <a class="tw-mr-2 muted" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.new_release"}}</a>
- {{end}}
+ {{if (and $canReadReleases $.CanCreateRelease $release.IsTag)}}
+ <a class="flex-text-inline" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag"}}{{ctx.Locale.Tr "repo.release.new_release"}}</a>
+ {{end}}
- {{if (and ($.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) $release.IsTag)}}
- <a class="ui delete-button tw-mr-2 muted" data-url="{{$.RepoLink}}/tags/delete" data-id="{{.ID}}">
- {{svg "octicon-trash" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.delete_tag"}}
- </a>
- {{end}}
+ {{if (and ($.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) $release.IsTag)}}
+ <a class="flex-text-inline link-action" data-url="{{$.RepoLink}}/tags/delete?id={{.ID}}" data-modal-confirm="#confirm-delete-tag-modal">
+ {{svg "octicon-trash"}}{{ctx.Locale.Tr "repo.release.delete_tag"}}
+ </a>
+ {{end}}
- {{if and $canReadReleases (not $release.IsTag)}}
- <a class="tw-mr-2 muted" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.detail"}}</a>
- {{end}}
- {{end}}
- </div>
- </td>
- </tr>
- {{end}}
- </tbody>
- </table>
+ {{if and $canReadReleases (not $release.IsTag)}}
+ <a class="flex-text-inline" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag"}}{{ctx.Locale.Tr "repo.release.detail"}}</a>
+ {{end}}
+ {{end}}
+ </div>
+ </div>
+ {{end}}
+ </div>
{{else}}
{{if .NumTags}}
<p class="tw-p-4">{{ctx.Locale.Tr "no_results_found"}}</p>
@@ -73,9 +67,8 @@
</div>
{{if $.Permission.CanWrite ctx.Consts.RepoUnitTypeCode}}
-<div class="ui g-modal-confirm delete modal">
+<div id="confirm-delete-tag-modal" class="ui small modal">
<div class="header">
- {{svg "octicon-trash"}}
{{ctx.Locale.Tr "repo.release.delete_tag"}}
</div>
<div class="content">
diff --git a/templates/repo/tag/name.tmpl b/templates/repo/tag/name.tmpl
index b6692dfc53..24e7de046d 100644
--- a/templates/repo/tag/name.tmpl
+++ b/templates/repo/tag/name.tmpl
@@ -1,3 +1,3 @@
-<a class="ui basic label tw-p-1 {{if .IsRelease}}primary{{end}}" href="{{.RepoLink}}/src/tag/{{.TagName|PathEscape}}">
+<a class="ui basic label {{if .IsRelease}}primary{{end}} {{.AdditionalClasses}}" href="{{.RepoLink}}/src/tag/{{.TagName|PathEscape}}">
{{svg "octicon-tag"}} {{.TagName}}
</a>
diff --git a/templates/repo/unicode_escape_prompt.tmpl b/templates/repo/unicode_escape_prompt.tmpl
index 8bceafa8bb..f8226ec728 100644
--- a/templates/repo/unicode_escape_prompt.tmpl
+++ b/templates/repo/unicode_escape_prompt.tmpl
@@ -1,22 +1,22 @@
{{if .EscapeStatus}}
{{if .EscapeStatus.HasInvisible}}
- <div class="ui warning message unicode-escape-prompt tw-text-left">
+ <div class="ui warning message unicode-escape-prompt">
<button class="btn close icon hide-panel" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button>
<div class="header">
{{ctx.Locale.Tr "repo.invisible_runes_header"}}
</div>
- <p>{{ctx.Locale.Tr "repo.invisible_runes_description"}}</p>
+ <div>{{ctx.Locale.Tr "repo.invisible_runes_description"}}</div>
{{if .EscapeStatus.HasAmbiguous}}
- <p>{{ctx.Locale.Tr "repo.ambiguous_runes_description"}}</p>
+ <div>{{ctx.Locale.Tr "repo.ambiguous_runes_description"}}</div>
{{end}}
</div>
{{else if .EscapeStatus.HasAmbiguous}}
- <div class="ui warning message unicode-escape-prompt tw-text-left">
+ <div class="ui warning message unicode-escape-prompt">
<button class="btn close icon hide-panel" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button>
<div class="header">
{{ctx.Locale.Tr "repo.ambiguous_runes_header"}}
</div>
- <p>{{ctx.Locale.Tr "repo.ambiguous_runes_description"}}</p>
+ <div>{{ctx.Locale.Tr "repo.ambiguous_runes_description"}}</div>
</div>
{{end}}
{{end}}
diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl
index 292a2f878c..3ba04a9974 100644
--- a/templates/repo/view_content.tmpl
+++ b/templates/repo/view_content.tmpl
@@ -41,8 +41,8 @@
<a href="{{.Repository.Link}}/find/{{.RefTypeNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a>
{{end}}
- {{if and .CanWriteCode .RefFullName.IsBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
- <button class="ui dropdown basic compact jump button"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
+ {{if and .RefFullName.IsBranch (not .IsViewFile)}}
+ <button class="ui dropdown basic compact jump button repo-add-file" {{if not .Repository.CanEnableEditor}}disabled{{end}}>
{{ctx.Locale.Tr "repo.editor.add_file"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
@@ -69,7 +69,7 @@
{{if not $isTreePathRoot}}
{{$treeNameIdxLast := Eval (len .TreeNames) "-" 1}}
- <span class="breadcrumb repo-path tw-ml-1">
+ <span class="breadcrumb">
<a class="section" href="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
{{- range $i, $v := .TreeNames -}}
<span class="breadcrumb-divider">/</span>
diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl
index dc789a2648..1486d7181d 100644
--- a/templates/repo/view_file.tmpl
+++ b/templates/repo/view_file.tmpl
@@ -1,4 +1,6 @@
-<div {{if .ReadmeInList}}id="readme" {{end}}class="{{TabSizeClass .Editorconfig .FileTreePath}} non-diff-file-content">
+<div {{if .ReadmeInList}}id="readme"{{end}} class="{{TabSizeClass .Editorconfig .FileTreePath}} non-diff-file-content"
+ data-global-init="initRepoFileView" data-raw-file-link="{{.RawFileLink}}">
+
{{- if .FileError}}
<div class="ui error message">
<div class="text left tw-whitespace-pre">{{.FileError}}</div>
@@ -27,18 +29,19 @@
<div class="file-header-left tw-flex tw-items-center tw-py-2 tw-pr-4">
{{if .ReadmeInList}}
{{svg "octicon-book" 16 "tw-mr-2"}}
- <strong><a class="muted" href="#readme">{{.FileTreePath}}</a></strong>
+ <strong><a class="muted" href="#readme">{{.ReadmeInList}}</a></strong>
{{else}}
{{template "repo/file_info" .}}
{{end}}
</div>
- <div class="file-header-right file-actions tw-flex tw-items-center tw-flex-wrap">
- {{if .HasSourceRenderedToggle}}
- <div class="ui compact icon buttons">
- <a href="?display=source" class="ui mini basic button {{if .IsDisplayingSource}}active{{end}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_source"}}">{{svg "octicon-code" 15}}</a>
- <a href="{{$.Link}}" class="ui mini basic button {{if .IsDisplayingRendered}}active{{end}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_rendered"}}">{{svg "octicon-file" 15}}</a>
- </div>
- {{end}}
+ <div class="file-header-right file-actions flex-text-block tw-flex-wrap">
+ {{/* this componment is also controlled by frontend plugin renders */}}
+ <div class="ui compact icon buttons file-view-toggle-buttons {{Iif .HasSourceRenderedToggle "" "tw-hidden"}}">
+ {{if .IsRepresentableAsText}}
+ <a href="?display=source" class="ui mini basic button file-view-toggle-source {{if .IsDisplayingSource}}active{{end}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_source"}}">{{svg "octicon-code" 15}}</a>
+ {{end}}
+ <a href="?display=rendered" class="ui mini basic button file-view-toggle-rendered {{if not .IsDisplayingSource}}active{{end}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_rendered"}}">{{svg "octicon-file" 15}}</a>
+ </div>
{{if not .ReadmeInList}}
<div class="ui buttons tw-mr-1">
<a class="ui mini basic button" href="{{$.RawFileLink}}">{{ctx.Locale.Tr "repo.file_raw"}}</a>
@@ -55,7 +58,10 @@
{{end}}
</div>
<a download class="btn-octicon" data-tooltip-content="{{ctx.Locale.Tr "repo.download_file"}}" href="{{$.RawFileLink}}">{{svg "octicon-download"}}</a>
- <a class="btn-octicon {{if not .CanCopyContent}} disabled{{end}}" data-global-click="onCopyContentButtonClick" {{if or .IsImageFile (and .HasSourceRenderedToggle (not .IsDisplayingSource))}} data-link="{{$.RawFileLink}}"{{end}} data-tooltip-content="{{if .CanCopyContent}}{{ctx.Locale.Tr "copy_content"}}{{else}}{{ctx.Locale.Tr "copy_type_unsupported"}}{{end}}">{{svg "octicon-copy"}}</a>
+ <a class="btn-octicon {{if not .CanCopyContent}}disabled{{end}}" data-global-click="onCopyContentButtonClick"
+ {{if not .IsDisplayingSource}}data-raw-file-link="{{$.RawFileLink}}"{{end}}
+ data-tooltip-content="{{if .CanCopyContent}}{{ctx.Locale.Tr "copy_content"}}{{else}}{{ctx.Locale.Tr "copy_type_unsupported"}}{{end}}"
+ >{{svg "octicon-copy"}}</a>
{{if .EnableFeed}}
<a class="btn-octicon" href="{{$.RepoLink}}/rss/{{$.RefTypeNameSubURL}}/{{PathEscapeSegments .TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
{{svg "octicon-rss"}}
@@ -82,41 +88,24 @@
{{end}}
</div>
</h4>
+
<div class="ui bottom attached table unstackable segment">
- {{if not (or .IsMarkup .IsRenderedHTML)}}
- {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
+ {{if not .IsMarkup}}
+ {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus}}
{{end}}
- <div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextSource}} code-view{{end}}">
+ <div class="file-view {{if .IsMarkup}}markup {{.MarkupType}}{{else if .IsPlainText}}plain-text{{else if .IsDisplayingSource}}code-view{{end}}">
{{if .IsFileTooLarge}}
{{template "shared/filetoolarge" dict "RawFileLink" .RawFileLink}}
{{else if not .FileSize}}
{{template "shared/fileisempty"}}
{{else if .IsMarkup}}
- {{if .FileContent}}{{.FileContent}}{{end}}
+ {{.FileContent}}
{{else if .IsPlainText}}
<pre>{{if .FileContent}}{{.FileContent}}{{end}}</pre>
- {{else if not .IsTextSource}}
- <div class="view-raw">
- {{if .IsImageFile}}
- <img alt="{{$.RawFileLink}}" src="{{$.RawFileLink}}">
- {{else if .IsVideoFile}}
- <video controls src="{{$.RawFileLink}}">
- <strong>{{ctx.Locale.Tr "repo.video_not_supported_in_browser"}}</strong>
- </video>
- {{else if .IsAudioFile}}
- <audio controls src="{{$.RawFileLink}}">
- <strong>{{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}}</strong>
- </audio>
- {{else if .IsPDFFile}}
- <div class="pdf-content is-loading" data-global-init="initPdfViewer" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "repo.diff.view_file"}}"></div>
- {{else}}
- <a href="{{$.RawFileLink}}" rel="nofollow" class="tw-p-4">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
- {{end}}
- </div>
- {{else if .FileSize}}
+ {{else if .FileContent}}
<table>
<tbody>
- {{range $idx, $code := .FileContent}}
+ {{range $idx, $code := .FileContent}}
{{$line := Eval $idx "+" 1}}
<tr>
<td id="L{{$line}}" class="lines-num"><span id="L{{$line}}" data-line-number="{{$line}}"></span></td>
@@ -125,17 +114,38 @@
{{end}}
<td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code}}</code></td>
</tr>
- {{end}}
+ {{end}}
</tbody>
</table>
- <div class="code-line-menu tippy-target">
- {{if $.Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
- <a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}" rel="nofollow noindex">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</a>
+ {{else}}
+ <div class="view-raw">
+ {{if .IsImageFile}}
+ <img alt="{{$.RawFileLink}}" src="{{$.RawFileLink}}">
+ {{else if .IsVideoFile}}
+ <video controls src="{{$.RawFileLink}}">
+ <strong>{{ctx.Locale.Tr "repo.video_not_supported_in_browser"}}</strong>
+ </video>
+ {{else if .IsAudioFile}}
+ <audio controls src="{{$.RawFileLink}}">
+ <strong>{{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}}</strong>
+ </audio>
+ {{else}}
+ <div class="file-view-render-container">
+ <div class="file-view-raw-prompt tw-p-4">
+ <a href="{{$.RawFileLink}}" rel="nofollow">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
+ </div>
+ </div>
{{end}}
- <a class="item view_git_blame" role="menuitem" href="{{.Repository.Link}}/blame/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.view_git_blame"}}</a>
- <a class="item copy-line-permalink" role="menuitem" data-url="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}">{{ctx.Locale.Tr "repo.file_copy_permalink"}}</a>
</div>
{{end}}
</div>
+
+ <div class="code-line-menu tippy-target">
+ {{if $.Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
+ <a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}" rel="nofollow noindex">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</a>
+ {{end}}
+ <a class="item view_git_blame" role="menuitem" href="{{.Repository.Link}}/blame/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.view_git_blame"}}</a>
+ <a class="item copy-line-permalink" role="menuitem" data-url="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}">{{ctx.Locale.Tr "repo.file_copy_permalink"}}</a>
+ </div>
</div>
</div>
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index cd832498b4..b655f735a3 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -41,13 +41,16 @@
</a>
{{else}}
<a class="entry-name" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
+ {{if $entry.IsLink}}
+ <a class="entry-symbol-link flex-text-inline" data-tooltip-content title="{{ctx.Locale.Tr "repo.find_file.follow_symlink"}}" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}?follow_symlink=1">{{svg "octicon-link" 12}}</a>
+ {{end}}
{{end}}
{{end}}
</div>
<div class="repo-file-cell message loading-icon-2px">
{{if $commit}}
{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
- {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink $.Repository}}
{{else}}
… {{/* will be loaded again by LastCommitLoaderURL */}}
{{end}}
diff --git a/templates/repo/wiki/new.tmpl b/templates/repo/wiki/new.tmpl
index 5ebccc69e9..12f0983904 100644
--- a/templates/repo/wiki/new.tmpl
+++ b/templates/repo/wiki/new.tmpl
@@ -18,7 +18,7 @@
{{ctx.Locale.Tr "repo.wiki.page_name_desc"}}
</div>
- {{$content := .content}}
+ {{$content := .WikiEditContent}}
{{if not .PageIsWikiEdit}}
{{$content = ctx.Locale.Tr "repo.wiki.welcome"}}
{{end}}
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl
index 89befcd7c5..4c7ef364d2 100644
--- a/templates/repo/wiki/view.tmpl
+++ b/templates/repo/wiki/view.tmpl
@@ -50,7 +50,7 @@
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}</a>
<a class="ui small primary button" href="{{.RepoLink}}/wiki?action=_new">{{ctx.Locale.Tr "repo.wiki.new_page_button"}}</a>
- <a class="ui small red button delete-button" href="" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete" data-id="{{.PageURL}}">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
+ <a class="ui small red button link-action" href data-modal-confirm="#repo-wiki-delete-page-modal" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
{{end}}
</div>
</div>
@@ -62,50 +62,43 @@
{{end}}
<div class="wiki-content-parts">
- {{if .sidebarTocContent}}
+ {{if .WikiSidebarTocHTML}}
<div class="render-content markup wiki-content-sidebar wiki-content-toc">
- {{.sidebarTocContent | SafeHTML}}
+ {{.WikiSidebarTocHTML}}
</div>
{{end}}
- <div class="render-content markup wiki-content-main {{if or .sidebarTocContent .sidebarPresent}}with-sidebar{{end}}">
- {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
- {{.content | SafeHTML}}
+ <div class="render-content markup wiki-content-main {{if or .WikiSidebarTocHTML .WikiSidebarHTML}}with-sidebar{{end}}">
+ {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus}}
+ {{.WikiContentHTML}}
</div>
- {{if .sidebarPresent}}
+ {{if .WikiSidebarHTML}}
<div class="render-content markup wiki-content-sidebar">
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="tw-float-right muted" href="{{.RepoLink}}/wiki/_Sidebar?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a>
{{end}}
- {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .sidebarEscapeStatus "root" $}}
- {{.sidebarContent | SafeHTML}}
+ {{.WikiSidebarHTML}}
</div>
{{end}}
<div class="tw-clear-both"></div>
- {{if .footerPresent}}
+ {{if .WikiFooterHTML}}
<div class="render-content markup wiki-content-footer">
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="tw-float-right muted" href="{{.RepoLink}}/wiki/_Footer?action=_edit" aria-label="{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a>
{{end}}
- {{template "repo/unicode_escape_prompt" dict "footerEscapeStatus" .sidebarEscapeStatus "root" $}}
- {{.footerContent | SafeHTML}}
+ {{.WikiFooterHTML}}
</div>
{{end}}
</div>
</div>
</div>
-<div class="ui g-modal-confirm delete modal">
- <div class="header">
- {{svg "octicon-trash"}}
- {{ctx.Locale.Tr "repo.wiki.delete_page_button"}}
- </div>
- <div class="content">
- <p>{{ctx.Locale.Tr "repo.wiki.delete_page_notice_1" $title}}</p>
- </div>
+<div class="ui small modal" id="repo-wiki-delete-page-modal">
+ <div class="header">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</div>
+ <div class="content"><p>{{ctx.Locale.Tr "repo.wiki.delete_page_notice_1" $title}}</p></div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl
index 30670c3b0f..98c26b32dc 100644
--- a/templates/shared/issuelist.tmpl
+++ b/templates/shared/issuelist.tmpl
@@ -3,11 +3,14 @@
{{range .Issues}}
<div class="flex-item">
- <div class="flex-item-icon">
- {{if $.CanWriteIssuesOrPulls}}
- <input type="checkbox" autocomplete="off" class="issue-checkbox tw-mr-4" data-issue-id={{.ID}} aria-label="{{ctx.Locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;">
- {{end}}
- {{template "shared/issueicon" .}}
+ <div class="flex-item-leading">
+ {{/* using some tw helpers is the only way to align the checkbox */}}
+ <div class="flex-text-inline tw-mt-[2px]">
+ {{if $.CanWriteIssuesOrPulls}}
+ <input type="checkbox" autocomplete="off" class="issue-checkbox tw-mr-[14px]" data-issue-id={{.ID}} aria-label="{{ctx.Locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;">
+ {{end}}
+ {{template "shared/issueicon" .}}
+ </div>
</div>
<div class="flex-item-main">
@@ -19,7 +22,7 @@
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}}
{{end}}
{{end}}
- <span class="labels-list tw-ml-1">
+ <span class="labels-list">
{{range .Labels}}
<a href="?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{ctx.RenderUtils.RenderLabel .}}</a>
{{end}}
diff --git a/templates/explore/repo_list.tmpl b/templates/shared/repo/list.tmpl
index ad190b89b2..2c8af14f9c 100644
--- a/templates/explore/repo_list.tmpl
+++ b/templates/shared/repo/list.tmpl
@@ -2,12 +2,16 @@
{{range .Repos}}
<div class="flex-item">
<div class="flex-item-leading">
- {{template "repo/icon" .}}
+ {{if $.ShowRepoOwnerAvatar}}
+ {{ctx.AvatarUtils.Avatar .Owner 24}}
+ {{else}}
+ {{template "repo/icon" .}}
+ {{end}}
</div>
<div class="flex-item-main">
<div class="flex-item-header">
<div class="flex-item-title">
- {{if and (or $.PageIsExplore $.PageIsProfileStarList) .Owner}}
+ {{if and $.ShowRepoOwnerOnList .Owner}}
<a class="text primary name" href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a>/
{{end}}
<a class="text primary name" href="{{.Link}}">{{.Name}}</a>
diff --git a/templates/shared/repo_search.tmpl b/templates/shared/repo/search.tmpl
index a909061184..a909061184 100644
--- a/templates/shared/repo_search.tmpl
+++ b/templates/shared/repo/search.tmpl
diff --git a/templates/shared/secrets/add_list.tmpl b/templates/shared/secrets/add_list.tmpl
index 977f308b71..44305e9502 100644
--- a/templates/shared/secrets/add_list.tmpl
+++ b/templates/shared/secrets/add_list.tmpl
@@ -4,9 +4,13 @@
<button class="ui primary tiny button show-modal"
data-modal="#add-secret-modal"
data-modal-form.action="{{.Link}}"
- data-modal-header="{{ctx.Locale.Tr "secrets.creation"}}"
+ data-modal-header="{{ctx.Locale.Tr "secrets.add_secret"}}"
+ data-modal-secret-name.value=""
+ data-modal-secret-name.read-only="false"
+ data-modal-secret-data=""
+ data-modal-secret-description=""
>
- {{ctx.Locale.Tr "secrets.creation"}}
+ {{ctx.Locale.Tr "secrets.add_secret"}}
</button>
</div>
</h4>
@@ -33,7 +37,19 @@
<span class="color-text-light-2">
{{ctx.Locale.Tr "settings.added_on" (DateUtils.AbsoluteShort .CreatedUnix)}}
</span>
- <button class="ui btn interact-bg link-action tw-p-2"
+ <button class="btn interact-bg show-modal tw-p-2"
+ data-modal="#add-secret-modal"
+ data-modal-form.action="{{$.Link}}"
+ data-modal-header="{{ctx.Locale.Tr "secrets.edit_secret"}}"
+ data-tooltip-content="{{ctx.Locale.Tr "secrets.edit_secret"}}"
+ data-modal-secret-name.value="{{.Name}}"
+ data-modal-secret-name.read-only="true"
+ data-modal-secret-data=""
+ data-modal-secret-description="{{if .Description}}{{.Description}}{{end}}"
+ >
+ {{svg "octicon-pencil"}}
+ </button>
+ <button class="btn interact-bg link-action tw-p-2"
data-url="{{$.Link}}/delete?id={{.ID}}"
data-modal-confirm="{{ctx.Locale.Tr "secrets.deletion.description"}}"
data-tooltip-content="{{ctx.Locale.Tr "secrets.deletion"}}"
@@ -51,9 +67,7 @@
{{/* Add secret dialog */}}
<div class="ui small modal" id="add-secret-modal">
- <div class="header">
- <span id="actions-modal-header"></span>
- </div>
+ <div class="header"></div>
<form class="ui form form-fetch-action" method="post">
<div class="content">
{{.CsrfTokenHtml}}
diff --git a/templates/shared/user/profile_big_avatar.tmpl b/templates/shared/user/profile_big_avatar.tmpl
index 91f04e0b53..1190dc54ec 100644
--- a/templates/shared/user/profile_big_avatar.tmpl
+++ b/templates/shared/user/profile_big_avatar.tmpl
@@ -103,7 +103,7 @@
<ul class="user-badges">
{{range .Badges}}
<li>
- <img width="64" height="64" src="{{.ImageURL}}" alt="{{.Description}}" data-tooltip-content="{{.Description}}">
+ <img loading="lazy" width="64" height="64" src="{{.ImageURL}}" alt="{{.Description}}" data-tooltip-content="{{.Description}}">
</li>
{{end}}
</ul>
diff --git a/templates/swagger/ui.tmpl b/templates/swagger/ui.tmpl
index 9935ab9c5a..c48ba82fe0 100644
--- a/templates/swagger/ui.tmpl
+++ b/templates/swagger/ui.tmpl
@@ -7,6 +7,7 @@
<body>
<a class="swagger-back-link" href="{{AppSubUrl}}/">{{svg "octicon-reply"}}{{ctx.Locale.Tr "return_to_gitea"}}</a>
<div id="swagger-ui" data-source="{{AppSubUrl}}/swagger.{{.APIJSONVersion}}.json"></div>
+ <footer class="page-footer"></footer>
<script src="{{AssetUrlPrefix}}/js/swagger.js?v={{AssetVersion}}"></script>
</body>
</html>
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 223a2e8410..879f59df2e 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -75,6 +75,49 @@
}
}
},
+ "/admin/actions/jobs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "admin"
+ ],
+ "summary": "Lists all jobs",
+ "operationId": "listAdminWorkflowJobs",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowJobsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/admin/actions/runners": {
"get": {
"produces": [
@@ -177,6 +220,73 @@
}
}
},
+ "/admin/actions/runs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "admin"
+ ],
+ "summary": "Lists all runs",
+ "operationId": "listAdminWorkflowRuns",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "workflow event name",
+ "name": "event",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow branch",
+ "name": "branch",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggered by user",
+ "name": "actor",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggering sha of the workflow run",
+ "name": "head_sha",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowRunsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/admin/cron": {
"get": {
"produces": [
@@ -656,7 +766,7 @@
},
{
"type": "string",
- "description": "user's login name to search for",
+ "description": "identifier of the user, provided by the external authenticator",
"name": "login_name",
"in": "query"
},
@@ -732,7 +842,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user to delete",
+ "description": "username of the user to delete",
"name": "username",
"in": "path",
"required": true
@@ -774,7 +884,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user to edit",
+ "description": "username of the user whose data is to be edited",
"name": "username",
"in": "path",
"required": true
@@ -816,7 +926,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose badges are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -846,7 +956,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user to whom a badge is to be added",
"name": "username",
"in": "path",
"required": true
@@ -880,7 +990,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose badge is to be deleted",
"name": "username",
"in": "path",
"required": true
@@ -922,7 +1032,7 @@
"parameters": [
{
"type": "string",
- "description": "username of the user",
+ "description": "username of the user who is to receive a public key",
"name": "username",
"in": "path",
"required": true
@@ -961,7 +1071,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose public key is to be deleted",
"name": "username",
"in": "path",
"required": true
@@ -1004,7 +1114,7 @@
"parameters": [
{
"type": "string",
- "description": "username of the user that will own the created organization",
+ "description": "username of the user who will own the created organization",
"name": "username",
"in": "path",
"required": true
@@ -1044,7 +1154,7 @@
"parameters": [
{
"type": "string",
- "description": "existing username of user",
+ "description": "current username of the user",
"name": "username",
"in": "path",
"required": true
@@ -1087,7 +1197,7 @@
"parameters": [
{
"type": "string",
- "description": "username of the user. This user will own the created repository",
+ "description": "username of the user who will own the created repository",
"name": "username",
"in": "path",
"required": true
@@ -1799,6 +1909,56 @@
}
}
},
+ "/orgs/{org}/actions/jobs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "organization"
+ ],
+ "summary": "Get org-level workflow jobs",
+ "operationId": "getOrgWorkflowJobs",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the organization",
+ "name": "org",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowJobsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/orgs/{org}/actions/runners": {
"get": {
"produces": [
@@ -1957,6 +2117,80 @@
}
}
},
+ "/orgs/{org}/actions/runs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "organization"
+ ],
+ "summary": "Get org-level workflow runs",
+ "operationId": "getOrgWorkflowRuns",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the organization",
+ "name": "org",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "workflow event name",
+ "name": "event",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow branch",
+ "name": "branch",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggered by user",
+ "name": "actor",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggering sha of the workflow run",
+ "name": "head_sha",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowRunsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/orgs/{org}/actions/secrets": {
"get": {
"produces": [
@@ -2259,16 +2493,16 @@
],
"responses": {
"201": {
- "description": "response when creating an org-level variable"
- },
- "204": {
- "description": "response when creating an org-level variable"
+ "description": "successfully created the org-level variable"
},
"400": {
"$ref": "#/responses/error"
},
- "404": {
- "$ref": "#/responses/notFound"
+ "409": {
+ "description": "variable name already exists."
+ },
+ "500": {
+ "$ref": "#/responses/error"
}
}
},
@@ -2482,7 +2716,7 @@
},
{
"type": "string",
- "description": "user to check",
+ "description": "username of the user to check",
"name": "username",
"in": "path",
"required": true
@@ -2513,7 +2747,7 @@
},
{
"type": "string",
- "description": "user to block",
+ "description": "username of the user to block",
"name": "username",
"in": "path",
"required": true
@@ -2553,7 +2787,7 @@
},
{
"type": "string",
- "description": "user to unblock",
+ "description": "username of the user to unblock",
"name": "username",
"in": "path",
"required": true
@@ -3024,7 +3258,7 @@
},
{
"type": "string",
- "description": "username of the user",
+ "description": "username of the user to check for an organization membership",
"name": "username",
"in": "path",
"required": true
@@ -3061,7 +3295,7 @@
},
{
"type": "string",
- "description": "username of the user",
+ "description": "username of the user to remove from the organization",
"name": "username",
"in": "path",
"required": true
@@ -3135,7 +3369,7 @@
},
{
"type": "string",
- "description": "username of the user",
+ "description": "username of the user to check for a public organization membership",
"name": "username",
"in": "path",
"required": true
@@ -3169,7 +3403,7 @@
},
{
"type": "string",
- "description": "username of the user",
+ "description": "username of the user whose membership is to be publicized",
"name": "username",
"in": "path",
"required": true
@@ -3206,7 +3440,7 @@
},
{
"type": "string",
- "description": "username of the user",
+ "description": "username of the user whose membership is to be concealed",
"name": "username",
"in": "path",
"required": true
@@ -4519,6 +4753,109 @@
}
}
},
+ "/repos/{owner}/{repo}/actions/jobs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Lists all jobs for a repository",
+ "operationId": "listWorkflowJobs",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the owner",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repository",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowJobsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
+ "/repos/{owner}/{repo}/actions/jobs/{job_id}": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Gets a specific workflow job for a workflow run",
+ "operationId": "getWorkflowJob",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the owner",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repository",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "id of the job",
+ "name": "job_id",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowJob"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/actions/jobs/{job_id}/logs": {
"get": {
"produces": [
@@ -4758,6 +5095,177 @@
}
}
},
+ "/repos/{owner}/{repo}/actions/runs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Lists all runs for a repository run",
+ "operationId": "getWorkflowRuns",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the owner",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repository",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "workflow event name",
+ "name": "event",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow branch",
+ "name": "branch",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggered by user",
+ "name": "actor",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggering sha of the workflow run",
+ "name": "head_sha",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/ArtifactsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
+ "/repos/{owner}/{repo}/actions/runs/{run}": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Gets a specific workflow run",
+ "operationId": "GetWorkflowRun",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the owner",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repository",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "id of the run",
+ "name": "run",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowRun"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ },
+ "delete": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Delete a workflow run",
+ "operationId": "deleteActionRun",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the owner",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repository",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "runid of the workflow run",
+ "name": "run",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "No Content"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/actions/runs/{run}/artifacts": {
"get": {
"produces": [
@@ -4810,6 +5318,70 @@
}
}
},
+ "/repos/{owner}/{repo}/actions/runs/{run}/jobs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Lists all jobs for a workflow run",
+ "operationId": "listWorkflowRunJobs",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "name of the owner",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repository",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "integer",
+ "description": "runid of the workflow run",
+ "name": "run",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowJobsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/actions/secrets": {
"get": {
"produces": [
@@ -5217,14 +5789,14 @@
"201": {
"description": "response when creating a repo-level variable"
},
- "204": {
- "description": "response when creating a repo-level variable"
- },
"400": {
"$ref": "#/responses/error"
},
- "404": {
- "$ref": "#/responses/notFound"
+ "409": {
+ "description": "variable name already exists."
+ },
+ "500": {
+ "$ref": "#/responses/error"
}
}
},
@@ -6358,7 +6930,7 @@
},
{
"type": "string",
- "description": "username of the collaborator",
+ "description": "username of the user to check for being a collaborator",
"name": "collaborator",
"in": "path",
"required": true
@@ -6402,7 +6974,7 @@
},
{
"type": "string",
- "description": "username of the collaborator to add",
+ "description": "username of the user to add or update as a collaborator",
"name": "collaborator",
"in": "path",
"required": true
@@ -6502,7 +7074,7 @@
},
{
"type": "string",
- "description": "username of the collaborator",
+ "description": "username of the collaborator whose permissions are to be obtained",
"name": "collaborator",
"in": "path",
"required": true
@@ -6559,6 +7131,20 @@
"in": "query"
},
{
+ "type": "string",
+ "format": "date-time",
+ "description": "Only commits after this date will be returned (ISO 8601 format)",
+ "name": "since",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "format": "date-time",
+ "description": "Only commits before this date will be returned (ISO 8601 format)",
+ "name": "until",
+ "in": "query"
+ },
+ {
"type": "boolean",
"description": "include diff stats for every commit (disable for speedup, default 'true')",
"name": "stat",
@@ -6838,13 +7424,14 @@
},
"/repos/{owner}/{repo}/contents": {
"get": {
+ "description": "This API follows GitHub's design, and it is not easy to use. Recommend users to use our \"contents-ext\" API instead.",
"produces": [
"application/json"
],
"tags": [
"repository"
],
- "summary": "Gets the metadata of all the entries of the root dir",
+ "summary": "Gets the metadata of all the entries of the root dir.",
"operationId": "repoGetContentsList",
"parameters": [
{
@@ -6932,15 +7519,72 @@
}
}
},
+ "/repos/{owner}/{repo}/contents-ext/{filepath}": {
+ "get": {
+ "description": "It guarantees that only one of the response fields is set if the request succeeds. Users can pass \"includes=file_content\" or \"includes=lfs_metadata\" to retrieve more fields. \"includes=file_content\" only works for single file, if you need to retrieve file contents in batch, use \"file-contents\" API after listing the directory.",
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "The extended \"contents\" API, to get file metadata and/or content, or list a directory.",
+ "operationId": "repoGetContentsExt",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "owner of the repo",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repo",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "path of the dir, file, symlink or submodule in the repo. Swagger requires path parameter to be \"required\", you can leave it empty or pass a single dot (\".\") to get the root directory.",
+ "name": "filepath",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "the name of the commit/branch/tag, default to the repository’s default branch.",
+ "name": "ref",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "By default this API's response only contains file's metadata. Use comma-separated \"includes\" options to retrieve more fields. Option \"file_content\" will try to retrieve the file content, \"lfs_metadata\" will try to retrieve LFS metadata, \"commit_metadata\" will try to retrieve commit metadata, and \"commit_message\" will try to retrieve commit message.",
+ "name": "includes",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/ContentsExtResponse"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/contents/{filepath}": {
"get": {
+ "description": "This API follows GitHub's design, and it is not easy to use. Recommend users to use the \"contents-ext\" API instead.",
"produces": [
"application/json"
],
"tags": [
"repository"
],
- "summary": "Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir",
+ "summary": "Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir.",
"operationId": "repoGetContents",
"parameters": [
{
@@ -7158,6 +7802,9 @@
"404": {
"$ref": "#/responses/error"
},
+ "422": {
+ "$ref": "#/responses/error"
+ },
"423": {
"$ref": "#/responses/repoArchivedError"
}
@@ -7265,7 +7912,7 @@
},
"/repos/{owner}/{repo}/file-contents": {
"get": {
- "description": "See the POST method. This GET method supports to use JSON encoded request body in query parameter.",
+ "description": "See the POST method. This GET method supports using JSON encoded request body in query parameter.",
"produces": [
"application/json"
],
@@ -11049,7 +11696,7 @@
"$ref": "#/responses/notFound"
},
"409": {
- "description": "Cannot cancel a non existent stopwatch"
+ "description": "Cannot cancel a non-existent stopwatch"
}
}
}
@@ -11155,7 +11802,7 @@
"$ref": "#/responses/notFound"
},
"409": {
- "description": "Cannot stop a non existent stopwatch"
+ "description": "Cannot stop a non-existent stopwatch"
}
}
}
@@ -11304,7 +11951,7 @@
},
{
"type": "string",
- "description": "user to subscribe",
+ "description": "username of the user to subscribe the issue to",
"name": "user",
"in": "path",
"required": true
@@ -11362,7 +12009,7 @@
},
{
"type": "string",
- "description": "user witch unsubscribe",
+ "description": "username of the user to unsubscribe from an issue",
"name": "user",
"in": "path",
"required": true
@@ -12232,7 +12879,7 @@
},
{
"type": "string",
- "description": "The name of the commit/branch/tag. Default the repository’s default branch",
+ "description": "The name of the commit/branch/tag. Default to the repository’s default branch",
"name": "ref",
"in": "query"
}
@@ -12811,6 +13458,7 @@
"enum": [
"oldest",
"recentupdate",
+ "recentclose",
"leastupdate",
"mostcomment",
"leastcomment",
@@ -14375,7 +15023,7 @@
},
{
"type": "string",
- "description": "The name of the commit/branch/tag. Default the repository’s default branch",
+ "description": "The name of the commit/branch/tag. Default to the repository’s default branch",
"name": "ref",
"in": "query"
}
@@ -15103,6 +15751,42 @@
}
}
},
+ "/repos/{owner}/{repo}/signing-key.pub": {
+ "get": {
+ "produces": [
+ "text/plain"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Get signing-key.pub for given repository",
+ "operationId": "repoSigningKeySSH",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "owner of the repo",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repo",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "ssh public key",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/stargazers": {
"get": {
"produces": [
@@ -16137,7 +16821,7 @@
},
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose tracked times are to be listed",
"name": "user",
"in": "path",
"required": true
@@ -16936,6 +17620,26 @@
}
}
},
+ "/signing-key.pub": {
+ "get": {
+ "produces": [
+ "text/plain"
+ ],
+ "tags": [
+ "miscellaneous"
+ ],
+ "summary": "Get default signing-key.pub",
+ "operationId": "getSigningKeySSH",
+ "responses": {
+ "200": {
+ "description": "ssh public key",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
"/teams/{id}": {
"get": {
"produces": [
@@ -17140,7 +17844,7 @@
},
{
"type": "string",
- "description": "username of the member to list",
+ "description": "username of the user whose data is to be listed",
"name": "username",
"in": "path",
"required": true
@@ -17175,7 +17879,7 @@
},
{
"type": "string",
- "description": "username of the user to add",
+ "description": "username of the user to add to a team",
"name": "username",
"in": "path",
"required": true
@@ -17213,7 +17917,7 @@
},
{
"type": "string",
- "description": "username of the user to remove",
+ "description": "username of the user to remove from a team",
"name": "username",
"in": "path",
"required": true
@@ -17467,6 +18171,49 @@
}
}
},
+ "/user/actions/jobs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "user"
+ ],
+ "summary": "Get workflow jobs",
+ "operationId": "getUserWorkflowJobs",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowJobsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/user/actions/runners": {
"get": {
"produces": [
@@ -17584,6 +18331,73 @@
}
}
},
+ "/user/actions/runs": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "user"
+ ],
+ "summary": "Get workflow runs",
+ "operationId": "getUserWorkflowRuns",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "workflow event name",
+ "name": "event",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow branch",
+ "name": "branch",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
+ "name": "status",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggered by user",
+ "name": "actor",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "triggering sha of the workflow run",
+ "name": "head_sha",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of results to return (1-based)",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page size of results",
+ "name": "limit",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/WorkflowRunsList"
+ },
+ "400": {
+ "$ref": "#/responses/error"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/user/actions/secrets/{secretname}": {
"put": {
"consumes": [
@@ -17803,16 +18617,13 @@
],
"responses": {
"201": {
- "description": "response when creating a variable"
- },
- "204": {
- "description": "response when creating a variable"
+ "description": "successfully created the user-level variable"
},
"400": {
"$ref": "#/responses/error"
},
- "404": {
- "$ref": "#/responses/notFound"
+ "409": {
+ "description": "variable name already exists."
}
}
},
@@ -18085,7 +18896,7 @@
"parameters": [
{
"type": "string",
- "description": "user to check",
+ "description": "username of the user to check",
"name": "username",
"in": "path",
"required": true
@@ -18109,7 +18920,7 @@
"parameters": [
{
"type": "string",
- "description": "user to block",
+ "description": "username of the user to block",
"name": "username",
"in": "path",
"required": true
@@ -18142,7 +18953,7 @@
"parameters": [
{
"type": "string",
- "description": "user to unblock",
+ "description": "username of the user to unblock",
"name": "username",
"in": "path",
"required": true
@@ -18304,7 +19115,7 @@
"parameters": [
{
"type": "string",
- "description": "username of followed user",
+ "description": "username of the user to check for authenticated followers",
"name": "username",
"in": "path",
"required": true
@@ -18328,7 +19139,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user to follow",
+ "description": "username of the user to follow",
"name": "username",
"in": "path",
"required": true
@@ -18355,7 +19166,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user to unfollow",
+ "description": "username of the user to unfollow",
"name": "username",
"in": "path",
"required": true
@@ -19309,7 +20120,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user to get",
+ "description": "username of the user whose data is to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19338,7 +20149,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose activity feeds are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19392,7 +20203,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose followers are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19433,7 +20244,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose followed users are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19471,14 +20282,14 @@
"parameters": [
{
"type": "string",
- "description": "username of following user",
+ "description": "username of the following user",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
- "description": "username of followed user",
+ "description": "username of the followed user",
"name": "target",
"in": "path",
"required": true
@@ -19507,7 +20318,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose GPG key list is to be obtained",
"name": "username",
"in": "path",
"required": true
@@ -19548,7 +20359,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user to get",
+ "description": "username of the user whose heatmap is to be obtained",
"name": "username",
"in": "path",
"required": true
@@ -19577,7 +20388,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose public keys are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19624,7 +20435,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose organizations are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19665,7 +20476,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose permissions are to be obtained",
"name": "username",
"in": "path",
"required": true
@@ -19704,7 +20515,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose owned repos are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19745,7 +20556,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose starred repos are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19789,7 +20600,7 @@
"parameters": [
{
"type": "string",
- "description": "username of the user",
+ "description": "username of the user whose watched repos are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19830,7 +20641,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of to user whose access tokens are to be listed",
"name": "username",
"in": "path",
"required": true
@@ -19872,7 +20683,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose token is to be created",
"name": "username",
"in": "path",
"required": true
@@ -19911,7 +20722,7 @@
"parameters": [
{
"type": "string",
- "description": "username of user",
+ "description": "username of the user whose token is to be deleted",
"name": "username",
"in": "path",
"required": true
@@ -20326,23 +21137,251 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "ActionWorkflowJob": {
+ "description": "ActionWorkflowJob represents a WorkflowJob",
+ "type": "object",
+ "properties": {
+ "completed_at": {
+ "type": "string",
+ "format": "date-time",
+ "x-go-name": "CompletedAt"
+ },
+ "conclusion": {
+ "type": "string",
+ "x-go-name": "Conclusion"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "x-go-name": "CreatedAt"
+ },
+ "head_branch": {
+ "type": "string",
+ "x-go-name": "HeadBranch"
+ },
+ "head_sha": {
+ "type": "string",
+ "x-go-name": "HeadSha"
+ },
+ "html_url": {
+ "type": "string",
+ "x-go-name": "HTMLURL"
+ },
+ "id": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "ID"
+ },
+ "labels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "x-go-name": "Labels"
+ },
+ "name": {
+ "type": "string",
+ "x-go-name": "Name"
+ },
+ "run_attempt": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "RunAttempt"
+ },
+ "run_id": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "RunID"
+ },
+ "run_url": {
+ "type": "string",
+ "x-go-name": "RunURL"
+ },
+ "runner_id": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "RunnerID"
+ },
+ "runner_name": {
+ "type": "string",
+ "x-go-name": "RunnerName"
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time",
+ "x-go-name": "StartedAt"
+ },
+ "status": {
+ "type": "string",
+ "x-go-name": "Status"
+ },
+ "steps": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ActionWorkflowStep"
+ },
+ "x-go-name": "Steps"
+ },
+ "url": {
+ "type": "string",
+ "x-go-name": "URL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
+ "ActionWorkflowJobsResponse": {
+ "description": "ActionWorkflowJobsResponse returns ActionWorkflowJobs",
+ "type": "object",
+ "properties": {
+ "jobs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ActionWorkflowJob"
+ },
+ "x-go-name": "Entries"
+ },
+ "total_count": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "TotalCount"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"ActionWorkflowRun": {
"description": "ActionWorkflowRun represents a WorkflowRun",
"type": "object",
"properties": {
+ "actor": {
+ "$ref": "#/definitions/User"
+ },
+ "completed_at": {
+ "type": "string",
+ "format": "date-time",
+ "x-go-name": "CompletedAt"
+ },
+ "conclusion": {
+ "type": "string",
+ "x-go-name": "Conclusion"
+ },
+ "display_title": {
+ "type": "string",
+ "x-go-name": "DisplayTitle"
+ },
+ "event": {
+ "type": "string",
+ "x-go-name": "Event"
+ },
+ "head_branch": {
+ "type": "string",
+ "x-go-name": "HeadBranch"
+ },
+ "head_repository": {
+ "$ref": "#/definitions/Repository"
+ },
"head_sha": {
"type": "string",
"x-go-name": "HeadSha"
},
+ "html_url": {
+ "type": "string",
+ "x-go-name": "HTMLURL"
+ },
"id": {
"type": "integer",
"format": "int64",
"x-go-name": "ID"
},
+ "path": {
+ "type": "string",
+ "x-go-name": "Path"
+ },
+ "repository": {
+ "$ref": "#/definitions/Repository"
+ },
"repository_id": {
"type": "integer",
"format": "int64",
"x-go-name": "RepositoryID"
+ },
+ "run_attempt": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "RunAttempt"
+ },
+ "run_number": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "RunNumber"
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time",
+ "x-go-name": "StartedAt"
+ },
+ "status": {
+ "type": "string",
+ "x-go-name": "Status"
+ },
+ "trigger_actor": {
+ "$ref": "#/definitions/User"
+ },
+ "url": {
+ "type": "string",
+ "x-go-name": "URL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
+ "ActionWorkflowRunsResponse": {
+ "description": "ActionWorkflowRunsResponse returns ActionWorkflowRuns",
+ "type": "object",
+ "properties": {
+ "total_count": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "TotalCount"
+ },
+ "workflow_runs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ActionWorkflowRun"
+ },
+ "x-go-name": "Entries"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
+ "ActionWorkflowStep": {
+ "description": "ActionWorkflowStep represents a step of a WorkflowJob",
+ "type": "object",
+ "properties": {
+ "completed_at": {
+ "type": "string",
+ "format": "date-time",
+ "x-go-name": "CompletedAt"
+ },
+ "conclusion": {
+ "type": "string",
+ "x-go-name": "Conclusion"
+ },
+ "name": {
+ "type": "string",
+ "x-go-name": "Name"
+ },
+ "number": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "Number"
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time",
+ "x-go-name": "StartedAt"
+ },
+ "status": {
+ "type": "string",
+ "x-go-name": "Status"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
@@ -20484,7 +21523,7 @@
"x-go-name": "Time"
},
"user_name": {
- "description": "User who spent the time (optional)",
+ "description": "username of the user who spent the time working on the issue (optional)",
"type": "string",
"x-go-name": "User"
}
@@ -20831,7 +21870,7 @@
],
"properties": {
"content": {
- "description": "new or updated file content, must be base64 encoded",
+ "description": "new or updated file content, it must be base64 encoded",
"type": "string",
"x-go-name": "ContentBase64"
},
@@ -20841,11 +21880,13 @@
"x-go-name": "FromPath"
},
"operation": {
- "description": "indicates what to do with the file",
+ "description": "indicates what to do with the file: \"create\" for creating a new file, \"update\" for updating an existing file,\n\"upload\" for creating or updating a file, \"rename\" for renaming a file, and \"delete\" for deleting an existing file.",
"type": "string",
"enum": [
"create",
"update",
+ "upload",
+ "rename",
"delete"
],
"x-go-name": "Operation"
@@ -20856,7 +21897,7 @@
"x-go-name": "Path"
},
"sha": {
- "description": "sha is the SHA for the file that already exists, required for update or delete",
+ "description": "the blob ID (SHA) for the file that already exists, required for changing existing files",
"type": "string",
"x-go-name": "SHA"
}
@@ -20972,7 +22013,17 @@
"x-go-name": "SHA"
},
"state": {
- "$ref": "#/definitions/CommitStatusState"
+ "type": "string",
+ "enum": [
+ "pending",
+ "success",
+ "error",
+ "failure",
+ "warning",
+ "skipped"
+ ],
+ "x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
+ "x-go-name": "State"
},
"statuses": {
"type": "array",
@@ -21200,7 +22251,17 @@
"x-go-name": "ID"
},
"status": {
- "$ref": "#/definitions/CommitStatusState"
+ "type": "string",
+ "enum": [
+ "pending",
+ "success",
+ "error",
+ "failure",
+ "warning",
+ "skipped"
+ ],
+ "x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
+ "x-go-name": "State"
},
"target_url": {
"type": "string",
@@ -21218,11 +22279,6 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
- "CommitStatusState": {
- "description": "CommitStatusState holds the state of a CommitStatus\nIt can be \"pending\", \"success\", \"error\" and \"failure\"",
- "type": "string",
- "x-go-package": "code.gitea.io/gitea/modules/structs"
- },
"CommitUser": {
"type": "object",
"title": "CommitUser contains information of a user in the context of a commit.",
@@ -21262,6 +22318,22 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "ContentsExtResponse": {
+ "type": "object",
+ "properties": {
+ "dir_contents": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ContentsResponse"
+ },
+ "x-go-name": "DirContents"
+ },
+ "file_contents": {
+ "$ref": "#/definitions/ContentsResponse"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"ContentsResponse": {
"description": "ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content",
"type": "object",
@@ -21296,6 +22368,10 @@
"format": "date-time",
"x-go-name": "LastAuthorDate"
},
+ "last_commit_message": {
+ "type": "string",
+ "x-go-name": "LastCommitMessage"
+ },
"last_commit_sha": {
"type": "string",
"x-go-name": "LastCommitSHA"
@@ -21305,6 +22381,15 @@
"format": "date-time",
"x-go-name": "LastCommitterDate"
},
+ "lfs_oid": {
+ "type": "string",
+ "x-go-name": "LfsOid"
+ },
+ "lfs_size": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "LfsSize"
+ },
"name": {
"type": "string",
"x-go-name": "Name"
@@ -21977,6 +23062,7 @@
"x-go-name": "RepoAdminChangeTeamAccess"
},
"username": {
+ "description": "username of the organization",
"type": "string",
"x-go-name": "UserName"
},
@@ -22166,6 +23252,10 @@
"type": "boolean",
"x-go-name": "IsPrerelease"
},
+ "tag_message": {
+ "type": "string",
+ "x-go-name": "TagMessage"
+ },
"tag_name": {
"type": "string",
"x-go-name": "TagName"
@@ -22271,7 +23361,17 @@
"x-go-name": "Description"
},
"state": {
- "$ref": "#/definitions/CommitStatusState"
+ "type": "string",
+ "enum": [
+ "pending",
+ "success",
+ "error",
+ "failure",
+ "warning",
+ "skipped"
+ ],
+ "x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped",
+ "x-go-name": "State"
},
"target_url": {
"type": "string",
@@ -22413,7 +23513,9 @@
"x-go-name": "FullName"
},
"login_name": {
+ "description": "identifier of the user, provided by the external authenticator (if configured)",
"type": "string",
+ "default": "empty",
"x-go-name": "LoginName"
},
"must_change_password": {
@@ -22438,6 +23540,7 @@
"x-go-name": "SourceID"
},
"username": {
+ "description": "username of the user",
"type": "string",
"x-go-name": "Username"
},
@@ -22567,7 +23670,7 @@
"x-go-name": "NewBranchName"
},
"sha": {
- "description": "sha is the SHA for the file that already exists",
+ "description": "the blob ID (SHA) for the file that already exists, it is required for changing existing files",
"type": "string",
"x-go-name": "SHA"
},
@@ -23423,7 +24526,9 @@
"x-go-name": "Location"
},
"login_name": {
+ "description": "identifier of the user, provided by the external authenticator (if configured)",
"type": "string",
+ "default": "empty",
"x-go-name": "LoginName"
},
"max_repo_creation": {
@@ -23482,6 +24587,7 @@
"x-go-name": "UserID"
},
"username": {
+ "description": "username of the user",
"type": "string",
"x-go-name": "UserName"
},
@@ -23940,6 +25046,15 @@
"type": "string",
"x-go-name": "Encoding"
},
+ "lfs_oid": {
+ "type": "string",
+ "x-go-name": "LfsOid"
+ },
+ "lfs_size": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "LfsSize"
+ },
"sha": {
"type": "string",
"x-go-name": "SHA"
@@ -24696,6 +25811,10 @@
"branch": {
"type": "string",
"x-go-name": "Branch"
+ },
+ "ff_only": {
+ "type": "boolean",
+ "x-go-name": "FfOnly"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
@@ -24805,7 +25924,8 @@
"gogs",
"onedev",
"gitbucket",
- "codebase"
+ "codebase",
+ "codecommit"
],
"x-go-name": "Service"
},
@@ -25192,7 +26312,7 @@
"x-go-name": "RepoAdminChangeTeamAccess"
},
"username": {
- "description": "deprecated",
+ "description": "username of the organization\ndeprecated",
"type": "string",
"x-go-name": "UserName"
},
@@ -25436,6 +26556,7 @@
"x-go-name": "Name"
},
"username": {
+ "description": "username of the user",
"type": "string",
"x-go-name": "UserName"
}
@@ -26138,6 +27259,10 @@
"type": "boolean",
"x-go-name": "AllowFastForwardOnly"
},
+ "allow_manual_merge": {
+ "type": "boolean",
+ "x-go-name": "AllowManualMerge"
+ },
"allow_merge_commits": {
"type": "boolean",
"x-go-name": "AllowMerge"
@@ -26167,6 +27292,10 @@
"format": "date-time",
"x-go-name": "ArchivedAt"
},
+ "autodetect_manual_merge": {
+ "type": "boolean",
+ "x-go-name": "AutodetectManualMerge"
+ },
"avatar_url": {
"type": "string",
"x-go-name": "AvatarURL"
@@ -26891,6 +28020,7 @@
"x-go-name": "UserID"
},
"user_name": {
+ "description": "username of the user",
"type": "string",
"x-go-name": "UserName"
}
@@ -26994,7 +28124,7 @@
"x-go-name": "NewBranchName"
},
"sha": {
- "description": "sha is the SHA for the file that already exists",
+ "description": "the blob ID (SHA) for the file that already exists, it is required for changing existing files",
"type": "string",
"x-go-name": "SHA"
},
@@ -27132,12 +28262,12 @@
"x-go-name": "Location"
},
"login": {
- "description": "the user's username",
+ "description": "login of the user, same as `username`",
"type": "string",
"x-go-name": "UserName"
},
"login_name": {
- "description": "the user's authentication sign-in name.",
+ "description": "identifier of the user, provided by the external authenticator (if configured)",
"type": "string",
"default": "empty",
"x-go-name": "LoginName"
@@ -27673,6 +28803,12 @@
"$ref": "#/definitions/Compare"
}
},
+ "ContentsExtResponse": {
+ "description": "",
+ "schema": {
+ "$ref": "#/definitions/ContentsExtResponse"
+ }
+ },
"ContentsListResponse": {
"description": "ContentsListResponse",
"schema": {
@@ -28459,6 +29595,30 @@
}
}
},
+ "WorkflowJob": {
+ "description": "WorkflowJob",
+ "schema": {
+ "$ref": "#/definitions/ActionWorkflowJob"
+ }
+ },
+ "WorkflowJobsList": {
+ "description": "WorkflowJobsList",
+ "schema": {
+ "$ref": "#/definitions/ActionWorkflowJobsResponse"
+ }
+ },
+ "WorkflowRun": {
+ "description": "WorkflowRun",
+ "schema": {
+ "$ref": "#/definitions/ActionWorkflowRun"
+ }
+ },
+ "WorkflowRunsList": {
+ "description": "WorkflowRunsList",
+ "schema": {
+ "$ref": "#/definitions/ActionWorkflowRunsResponse"
+ }
+ },
"conflict": {
"description": "APIConflict is a conflict empty response"
},
diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl
index d66568199d..a3f6e1471f 100644
--- a/templates/user/auth/signup_inner.tmpl
+++ b/templates/user/auth/signup_inner.tmpl
@@ -7,6 +7,9 @@
{{end}}
</h4>
<div class="ui attached segment">
+ {{if .IsFirstTimeRegistration}}
+ <p>{{ctx.Locale.Tr "auth.sign_up_tip"}}</p>
+ {{end}}
<form class="ui form" action="{{.SignUpLink}}" method="post">
{{.CsrfTokenHtml}}
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}}
diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl
index 97291fc42d..37dede7af6 100644
--- a/templates/user/dashboard/feeds.tmpl
+++ b/templates/user/dashboard/feeds.tmpl
@@ -91,10 +91,10 @@
{{range $push.Commits}}
{{$commitLink := printf "%s/commit/%s" $repoLink .Sha1}}
<div class="flex-text-block">
- <img alt class="ui avatar" src="{{$push.AvatarLink ctx .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16">
+ <img loading="lazy" alt class="ui avatar" src="{{$push.AvatarLink ctx .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16">
<a class="ui sha label" href="{{$commitLink}}">{{ShortSha .Sha1}}</a>
<span class="text truncate">
- {{ctx.RenderUtils.RenderCommitMessage .Message ($repo.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderCommitMessage .Message $repo}}
</span>
</div>
{{end}}
diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl
index 9af2cd53b3..b8655a84a4 100644
--- a/templates/user/notification/notification_div.tmpl
+++ b/templates/user/notification/notification_div.tmpl
@@ -1,6 +1,6 @@
<div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-sequence-number="{{.SequenceNumber}}">
<div class="ui container">
- {{$notificationUnreadCount := call .NotificationUnreadCount ctx}}
+ {{$notificationUnreadCount := call .PageGlobalData.GetNotificationUnreadCount}}
<div class="tw-flex tw-items-center tw-justify-between tw-mb-[--page-spacing]">
<div class="small-menu-items ui compact tiny menu">
<a class="{{if eq .Status 1}}active {{end}}item" href="{{AppSubUrl}}/notifications?q=unread">
diff --git a/templates/user/notification/notification_subscriptions.tmpl b/templates/user/notification/notification_subscriptions.tmpl
index 6ef53ab654..e4dd27e63b 100644
--- a/templates/user/notification/notification_subscriptions.tmpl
+++ b/templates/user/notification/notification_subscriptions.tmpl
@@ -69,8 +69,8 @@
{{template "shared/issuelist" dict "." . "listType" "dashboard"}}
{{end}}
{{else}}
- {{template "shared/repo_search" .}}
- {{template "explore/repo_list" .}}
+ {{template "shared/repo/search" .}}
+ {{template "shared/repo/list" .}}
{{template "base/paginate" .}}
{{end}}
</div>
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl
index e5c3412ddd..74a53b937d 100644
--- a/templates/user/profile.tmpl
+++ b/templates/user/profile.tmpl
@@ -17,8 +17,8 @@
{{template "user/dashboard/feeds" .}}
{{else if eq .TabName "stars"}}
<div class="stars">
- {{template "shared/repo_search" .}}
- {{template "explore/repo_list" .}}
+ {{template "shared/repo/search" .}}
+ {{template "shared/repo/list" .}}
{{template "base/paginate" .}}
</div>
{{else if eq .TabName "following"}}
@@ -30,8 +30,8 @@
{{else if eq .TabName "organizations"}}
{{template "repo/user_cards" .}}
{{else}}
- {{template "shared/repo_search" .}}
- {{template "explore/repo_list" .}}
+ {{template "shared/repo/search" .}}
+ {{template "shared/repo/list" .}}
{{template "base/paginate" .}}
{{end}}
</div>
diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl
index 501f238c7a..8c24da7fc9 100644
--- a/templates/user/settings/applications.tmpl
+++ b/templates/user/settings/applications.tmpl
@@ -68,7 +68,7 @@
</label>
</div>
<div>
- <div class="tw-my-2">{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}</div>
+ <div class="tw-my-2">{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (HTMLFormat `href="%s" target="_blank"` "https://docs.gitea.com/development/oauth2-provider#scopes")}}</div>
<table class="ui table unstackable tw-my-2">
{{range $category := .TokenCategories}}
<tr>
diff --git a/templates/user/settings/organization.tmpl b/templates/user/settings/organization.tmpl
index 16c27b52cd..a48ca9ec9b 100644
--- a/templates/user/settings/organization.tmpl
+++ b/templates/user/settings/organization.tmpl
@@ -47,7 +47,7 @@
{{ctx.Locale.Tr "org.members.leave"}}
</div>
<div class="content">
- <p>{{ctx.Locale.Tr "org.members.leave.detail" (`<span class="dataOrganizationName"></span>`|SafeHTML)}}</p>
+ <p>{{ctx.Locale.Tr "org.members.leave.detail" (HTMLFormat `<span class="%s"></span>` "dataOrganizationName")}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 03c3c18f28..d8e5e27b89 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -12,7 +12,7 @@
<span class="text red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span>
<span class="text red tw-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span>
</label>
- <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}disabled{{end}} maxlength="40">
+ <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" required {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}disabled{{end}} maxlength="40">
{{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}
<p class="help text blue">{{ctx.Locale.Tr "settings.password_username_disabled"}}</p>
{{end}}
diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go
index af631ca8fd..4a408dbd7b 100644
--- a/tests/e2e/e2e_test.go
+++ b/tests/e2e/e2e_test.go
@@ -4,7 +4,7 @@
// This is primarily coped from /tests/integration/integration_test.go
// TODO: Move common functions to shared file
-//nolint:forbidigo
+//nolint:forbidigo // use of print functions is allowed in tests
package e2e
import (
diff --git a/tests/integration/actions_delete_run_test.go b/tests/integration/actions_delete_run_test.go
new file mode 100644
index 0000000000..22f9a1f740
--- /dev/null
+++ b/tests/integration/actions_delete_run_test.go
@@ -0,0 +1,181 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "testing"
+ "time"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/routers/web/repo/actions"
+
+ runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
+ "github.com/stretchr/testify/assert"
+ "google.golang.org/protobuf/types/known/timestamppb"
+)
+
+func TestActionsDeleteRun(t *testing.T) {
+ now := time.Now()
+ testCase := struct {
+ treePath string
+ fileContent string
+ outcomes map[string]*mockTaskOutcome
+ expectedStatuses map[string]string
+ }{
+ treePath: ".gitea/workflows/test1.yml",
+ fileContent: `name: test1
+on:
+ push:
+ paths:
+ - .gitea/workflows/test1.yml
+jobs:
+ job1:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo job1
+ job2:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo job2
+ job3:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo job3
+`,
+ outcomes: map[string]*mockTaskOutcome{
+ "job1": {
+ result: runnerv1.Result_RESULT_SUCCESS,
+ logRows: []*runnerv1.LogRow{
+ {
+ Time: timestamppb.New(now.Add(4 * time.Second)),
+ Content: " \U0001F433 docker create image",
+ },
+ {
+ Time: timestamppb.New(now.Add(5 * time.Second)),
+ Content: "job1",
+ },
+ {
+ Time: timestamppb.New(now.Add(6 * time.Second)),
+ Content: "\U0001F3C1 Job succeeded",
+ },
+ },
+ },
+ "job2": {
+ result: runnerv1.Result_RESULT_SUCCESS,
+ logRows: []*runnerv1.LogRow{
+ {
+ Time: timestamppb.New(now.Add(4 * time.Second)),
+ Content: " \U0001F433 docker create image",
+ },
+ {
+ Time: timestamppb.New(now.Add(5 * time.Second)),
+ Content: "job2",
+ },
+ {
+ Time: timestamppb.New(now.Add(6 * time.Second)),
+ Content: "\U0001F3C1 Job succeeded",
+ },
+ },
+ },
+ "job3": {
+ result: runnerv1.Result_RESULT_SUCCESS,
+ logRows: []*runnerv1.LogRow{
+ {
+ Time: timestamppb.New(now.Add(4 * time.Second)),
+ Content: " \U0001F433 docker create image",
+ },
+ {
+ Time: timestamppb.New(now.Add(5 * time.Second)),
+ Content: "job3",
+ },
+ {
+ Time: timestamppb.New(now.Add(6 * time.Second)),
+ Content: "\U0001F3C1 Job succeeded",
+ },
+ },
+ },
+ },
+ expectedStatuses: map[string]string{
+ "job1": actions_model.StatusSuccess.String(),
+ "job2": actions_model.StatusSuccess.String(),
+ "job3": actions_model.StatusSuccess.String(),
+ },
+ }
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ session := loginUser(t, user2.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
+
+ apiRepo := createActionsTestRepo(t, token, "actions-delete-run-test", false)
+ runner := newMockRunner()
+ runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
+
+ opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+testCase.treePath, testCase.fileContent)
+ createWorkflowFile(t, token, user2.Name, apiRepo.Name, testCase.treePath, opts)
+
+ runIndex := ""
+ for i := 0; i < len(testCase.outcomes); i++ {
+ task := runner.fetchTask(t)
+ jobName := getTaskJobNameByTaskID(t, token, user2.Name, apiRepo.Name, task.Id)
+ outcome := testCase.outcomes[jobName]
+ assert.NotNil(t, outcome)
+ runner.execTask(t, task, outcome)
+ runIndex = task.Context.GetFields()["run_number"].GetStringValue()
+ assert.Equal(t, "1", runIndex)
+ }
+
+ for i := 0; i < len(testCase.outcomes); i++ {
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ })
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ var listResp actions.ViewResponse
+ err := json.Unmarshal(resp.Body.Bytes(), &listResp)
+ assert.NoError(t, err)
+ assert.Len(t, listResp.State.Run.Jobs, 3)
+
+ req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)).
+ AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusOK)
+ }
+
+ req := NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ })
+ session.MakeRequest(t, req, http.StatusNotFound)
+
+ req = NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ })
+ session.MakeRequest(t, req, http.StatusNotFound)
+
+ for i := 0; i < len(testCase.outcomes); i++ {
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ })
+ session.MakeRequest(t, req, http.StatusNotFound)
+
+ req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)).
+ AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusNotFound)
+ }
+ })
+}
diff --git a/tests/integration/actions_runner_test.go b/tests/integration/actions_runner_test.go
index 75402929a1..6cc5a10e0f 100644
--- a/tests/integration/actions_runner_test.go
+++ b/tests/integration/actions_runner_test.go
@@ -115,10 +115,9 @@ func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1
}
type mockTaskOutcome struct {
- result runnerv1.Result
- outputs map[string]string
- logRows []*runnerv1.LogRow
- execTime time.Duration
+ result runnerv1.Result
+ outputs map[string]string
+ logRows []*runnerv1.LogRow
}
func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, outcome *mockTaskOutcome) {
@@ -145,7 +144,6 @@ func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, outcome *mockTa
sentOutputKeys = append(sentOutputKeys, outputKey)
assert.ElementsMatch(t, sentOutputKeys, resp.Msg.SentOutputs)
}
- time.Sleep(outcome.execTime)
resp, err := r.client.runnerServiceClient.UpdateTask(t.Context(), connect.NewRequest(&runnerv1.UpdateTaskRequest{
State: &runnerv1.TaskState{
Id: task.Id,
diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go
index f576dc38ab..088491d570 100644
--- a/tests/integration/actions_trigger_test.go
+++ b/tests/integration/actions_trigger_test.go
@@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
actions_module "code.gitea.io/gitea/modules/actions"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
@@ -633,12 +634,12 @@ jobs:
assert.NotEmpty(t, addFileResp)
sha = addFileResp.Commit.SHA
assert.Eventually(t, func() bool {
- latestCommitStatuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll)
+ latestCommitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll)
assert.NoError(t, err)
if len(latestCommitStatuses) == 0 {
return false
}
- if latestCommitStatuses[0].State == api.CommitStatusPending {
+ if latestCommitStatuses[0].State == commitstatus.CommitStatusPending {
insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context)
return true
}
@@ -676,17 +677,17 @@ jobs:
}
func checkCommitStatusAndInsertFakeStatus(t *testing.T, repo *repo_model.Repository, sha string) {
- latestCommitStatuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll)
+ latestCommitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll)
assert.NoError(t, err)
assert.Len(t, latestCommitStatuses, 1)
- assert.Equal(t, api.CommitStatusPending, latestCommitStatuses[0].State)
+ assert.Equal(t, commitstatus.CommitStatusPending, latestCommitStatuses[0].State)
insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context)
}
func insertFakeStatus(t *testing.T, repo *repo_model.Repository, sha, targetURL, context string) {
err := commitstatus_service.CreateCommitStatus(db.DefaultContext, repo, user_model.NewActionsUser(), sha, &git_model.CommitStatus{
- State: api.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: targetURL,
Context: context,
})
@@ -719,7 +720,7 @@ func TestWorkflowDispatchPublicApi(t *testing.T) {
{
Operation: "create",
TreePath: ".gitea/workflows/dispatch.yml",
- ContentReader: strings.NewReader(`name: test
+ ContentReader: strings.NewReader(`
on:
workflow_dispatch
jobs:
@@ -799,7 +800,7 @@ func TestWorkflowDispatchPublicApiWithInputs(t *testing.T) {
{
Operation: "create",
TreePath: ".gitea/workflows/dispatch.yml",
- ContentReader: strings.NewReader(`name: test
+ ContentReader: strings.NewReader(`
on:
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
jobs:
@@ -890,7 +891,7 @@ func TestWorkflowDispatchPublicApiJSON(t *testing.T) {
{
Operation: "create",
TreePath: ".gitea/workflows/dispatch.yml",
- ContentReader: strings.NewReader(`name: test
+ ContentReader: strings.NewReader(`
on:
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
jobs:
@@ -976,7 +977,7 @@ func TestWorkflowDispatchPublicApiWithInputsJSON(t *testing.T) {
{
Operation: "create",
TreePath: ".gitea/workflows/dispatch.yml",
- ContentReader: strings.NewReader(`name: test
+ ContentReader: strings.NewReader(`
on:
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
jobs:
@@ -1070,7 +1071,7 @@ func TestWorkflowDispatchPublicApiWithInputsNonDefaultBranchJSON(t *testing.T) {
{
Operation: "create",
TreePath: ".gitea/workflows/dispatch.yml",
- ContentReader: strings.NewReader(`name: test
+ ContentReader: strings.NewReader(`
on:
workflow_dispatch
jobs:
@@ -1106,7 +1107,7 @@ jobs:
{
Operation: "update",
TreePath: ".gitea/workflows/dispatch.yml",
- ContentReader: strings.NewReader(`name: test
+ ContentReader: strings.NewReader(`
on:
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
jobs:
@@ -1156,6 +1157,7 @@ jobs:
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
Title: "add workflow",
RepoID: repo.ID,
+ Repo: repo,
Event: "workflow_dispatch",
Ref: "refs/heads/dispatch",
WorkflowID: "dispatch.yml",
@@ -1207,7 +1209,7 @@ func TestWorkflowApi(t *testing.T) {
{
Operation: "create",
TreePath: ".gitea/workflows/dispatch.yml",
- ContentReader: strings.NewReader(`name: test
+ ContentReader: strings.NewReader(`
on:
workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } }
jobs:
@@ -1448,3 +1450,157 @@ jobs:
assert.Equal(t, pullRequest.MergedCommitID, actionRun.CommitSHA)
})
}
+
+func TestActionRunNameWithContextVariables(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+
+ // create the repo
+ repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
+ Name: "action-run-name-with-variables",
+ Description: "test action run name",
+ AutoInit: true,
+ Gitignores: "Go",
+ License: "MIT",
+ Readme: "Default",
+ DefaultBranch: "main",
+ IsPrivate: false,
+ })
+ assert.NoError(t, err)
+ assert.NotEmpty(t, repo)
+
+ // add workflow file to the repo
+ addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "create",
+ TreePath: ".gitea/workflows/runname.yml",
+ ContentReader: strings.NewReader(`name: test
+on:
+ [create,delete]
+run-name: ${{ gitea.actor }} is running this workflow
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo helloworld
+`),
+ },
+ },
+ Message: "add workflow with run-name",
+ OldBranch: "main",
+ NewBranch: "main",
+ Author: &files_service.IdentityOptions{
+ GitUserName: user2.Name,
+ GitUserEmail: user2.Email,
+ },
+ Committer: &files_service.IdentityOptions{
+ GitUserName: user2.Name,
+ GitUserEmail: user2.Email,
+ },
+ Dates: &files_service.CommitDateOptions{
+ Author: time.Now(),
+ Committer: time.Now(),
+ },
+ })
+ assert.NoError(t, err)
+ assert.NotEmpty(t, addWorkflowToBaseResp)
+
+ // Get the commit ID of the default branch
+ gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo)
+ assert.NoError(t, err)
+ defer gitRepo.Close()
+ branch, err := git_model.GetBranch(db.DefaultContext, repo.ID, repo.DefaultBranch)
+ assert.NoError(t, err)
+
+ // create a branch
+ err = repo_service.CreateNewBranchFromCommit(db.DefaultContext, user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables")
+ assert.NoError(t, err)
+ run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
+ Title: user2.LoginName + " is running this workflow",
+ RepoID: repo.ID,
+ Event: "create",
+ Ref: "refs/heads/test-action-run-name-with-variables",
+ WorkflowID: "runname.yml",
+ CommitSHA: branch.CommitID,
+ })
+ assert.NotNil(t, run)
+ })
+}
+
+func TestActionRunName(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+
+ // create the repo
+ repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
+ Name: "action-run-name",
+ Description: "test action run-name",
+ AutoInit: true,
+ Gitignores: "Go",
+ License: "MIT",
+ Readme: "Default",
+ DefaultBranch: "main",
+ IsPrivate: false,
+ })
+ assert.NoError(t, err)
+ assert.NotEmpty(t, repo)
+
+ // add workflow file to the repo
+ addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "create",
+ TreePath: ".gitea/workflows/runname.yml",
+ ContentReader: strings.NewReader(`name: test
+on:
+ [create,delete]
+run-name: run name without variables
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo helloworld
+`),
+ },
+ },
+ Message: "add workflow with run name",
+ OldBranch: "main",
+ NewBranch: "main",
+ Author: &files_service.IdentityOptions{
+ GitUserName: user2.Name,
+ GitUserEmail: user2.Email,
+ },
+ Committer: &files_service.IdentityOptions{
+ GitUserName: user2.Name,
+ GitUserEmail: user2.Email,
+ },
+ Dates: &files_service.CommitDateOptions{
+ Author: time.Now(),
+ Committer: time.Now(),
+ },
+ })
+ assert.NoError(t, err)
+ assert.NotEmpty(t, addWorkflowToBaseResp)
+
+ // Get the commit ID of the default branch
+ gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo)
+ assert.NoError(t, err)
+ defer gitRepo.Close()
+ branch, err := git_model.GetBranch(db.DefaultContext, repo.ID, repo.DefaultBranch)
+ assert.NoError(t, err)
+
+ // create a branch
+ err = repo_service.CreateNewBranchFromCommit(db.DefaultContext, user2, repo, gitRepo, branch.CommitID, "test-action-run-name")
+ assert.NoError(t, err)
+ run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
+ Title: "run name without variables",
+ RepoID: repo.ID,
+ Event: "create",
+ Ref: "refs/heads/test-action-run-name",
+ WorkflowID: "runname.yml",
+ CommitSHA: branch.CommitID,
+ })
+ assert.NotNil(t, run)
+ })
+}
diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go
index d5d7e70bc7..95e03ab750 100644
--- a/tests/integration/admin_user_test.go
+++ b/tests/integration/admin_user_test.go
@@ -4,6 +4,7 @@
package integration
import (
+ "fmt"
"net/http"
"strconv"
"testing"
@@ -72,12 +73,37 @@ func TestAdminDeleteUser(t *testing.T) {
session := loginUser(t, "user1")
- csrf := GetUserCSRFToken(t, session)
- req := NewRequestWithValues(t, "POST", "/-/admin/users/8/delete", map[string]string{
- "_csrf": csrf,
- })
- session.MakeRequest(t, req, http.StatusSeeOther)
-
- assertUserDeleted(t, 8)
- unittest.CheckConsistencyFor(t, &user_model.User{})
+ usersToDelete := []struct {
+ userID int64
+ purge bool
+ }{
+ {
+ userID: 2,
+ purge: true,
+ },
+ {
+ userID: 8,
+ },
+ }
+
+ for _, entry := range usersToDelete {
+ t.Run(fmt.Sprintf("DeleteUser%d", entry.userID), func(t *testing.T) {
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: entry.userID})
+ assert.NotNil(t, user)
+
+ var query string
+ if entry.purge {
+ query = "?purge=true"
+ }
+
+ csrf := GetUserCSRFToken(t, session)
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/-/admin/users/%d/delete%s", entry.userID, query), map[string]string{
+ "_csrf": csrf,
+ })
+ session.MakeRequest(t, req, http.StatusSeeOther)
+
+ assertUserDeleted(t, entry.userID)
+ unittest.CheckConsistencyFor(t, &user_model.User{})
+ })
+ }
}
diff --git a/tests/integration/api_actions_delete_run_test.go b/tests/integration/api_actions_delete_run_test.go
new file mode 100644
index 0000000000..5b41702c57
--- /dev/null
+++ b/tests/integration/api_actions_delete_run_test.go
@@ -0,0 +1,98 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/json"
+ api "code.gitea.io/gitea/modules/structs"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAPIActionsDeleteRunCheckPermission(t *testing.T) {
+ defer prepareTestEnvActionsArtifacts(t)()
+
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+ testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound)
+}
+
+func TestAPIActionsDeleteRun(t *testing.T) {
+ defer prepareTestEnvActionsArtifacts(t)()
+
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+
+ testAPIActionsDeleteRunListArtifacts(t, repo, token, 2)
+ testAPIActionsDeleteRunListTasks(t, repo, token, true)
+ testAPIActionsDeleteRun(t, repo, token, http.StatusNoContent)
+
+ testAPIActionsDeleteRunListArtifacts(t, repo, token, 0)
+ testAPIActionsDeleteRunListTasks(t, repo, token, false)
+ testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound)
+}
+
+func TestAPIActionsDeleteRunRunning(t *testing.T) {
+ defer prepareTestEnvActionsArtifacts(t)()
+
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+
+ req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793", repo.FullName())).
+ AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusBadRequest)
+}
+
+func testAPIActionsDeleteRun(t *testing.T, repo *repo_model.Repository, token string, expected int) {
+ req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795", repo.FullName())).
+ AddTokenAuth(token)
+ MakeRequest(t, req, expected)
+}
+
+func testAPIActionsDeleteRunListArtifacts(t *testing.T, repo *repo_model.Repository, token string, artifacts int) {
+ req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/artifacts", repo.FullName())).
+ AddTokenAuth(token)
+ resp := MakeRequest(t, req, http.StatusOK)
+ var listResp api.ActionArtifactsResponse
+ err := json.Unmarshal(resp.Body.Bytes(), &listResp)
+ assert.NoError(t, err)
+ assert.Len(t, listResp.Entries, artifacts)
+}
+
+func testAPIActionsDeleteRunListTasks(t *testing.T, repo *repo_model.Repository, token string, expected bool) {
+ req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/tasks", repo.FullName())).
+ AddTokenAuth(token)
+ resp := MakeRequest(t, req, http.StatusOK)
+ var listResp api.ActionTaskResponse
+ err := json.Unmarshal(resp.Body.Bytes(), &listResp)
+ assert.NoError(t, err)
+ findTask1 := false
+ findTask2 := false
+ for _, entry := range listResp.Entries {
+ if entry.ID == 53 {
+ findTask1 = true
+ continue
+ }
+ if entry.ID == 54 {
+ findTask2 = true
+ continue
+ }
+ }
+ assert.Equal(t, expected, findTask1)
+ assert.Equal(t, expected, findTask2)
+}
diff --git a/tests/integration/api_actions_runner_test.go b/tests/integration/api_actions_runner_test.go
index ace7aa381a..fb9ba5b0c2 100644
--- a/tests/integration/api_actions_runner_test.go
+++ b/tests/integration/api_actions_runner_test.go
@@ -41,8 +41,6 @@ func testActionsRunnerAdmin(t *testing.T) {
runnerList := api.ActionRunnersResponse{}
DecodeJSON(t, runnerListResp, &runnerList)
- assert.Len(t, runnerList.Entries, 4)
-
idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
require.NotEqual(t, -1, idx)
expectedRunner := runnerList.Entries[idx]
@@ -160,16 +158,20 @@ func testActionsRunnerOwner(t *testing.T) {
runnerList := api.ActionRunnersResponse{}
DecodeJSON(t, runnerListResp, &runnerList)
- assert.Len(t, runnerList.Entries, 1)
- assert.Equal(t, "runner_to_be_deleted-org", runnerList.Entries[0].Name)
- assert.Equal(t, int64(34347), runnerList.Entries[0].ID)
- assert.False(t, runnerList.Entries[0].Ephemeral)
- assert.Len(t, runnerList.Entries[0].Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
- assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
+ idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34347 })
+ require.NotEqual(t, -1, idx)
+ expectedRunner := runnerList.Entries[idx]
+
+ require.NotNil(t, expectedRunner)
+ assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name)
+ assert.Equal(t, int64(34347), expectedRunner.ID)
+ assert.False(t, expectedRunner.Ephemeral)
+ assert.Len(t, expectedRunner.Labels, 2)
+ assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
+ assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
// Verify get the runner by id
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
+ req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
runnerResp := MakeRequest(t, req, http.StatusOK)
runner := api.ActionRunner{}
@@ -183,11 +185,11 @@ func testActionsRunnerOwner(t *testing.T) {
assert.Equal(t, "linux", runner.Labels[1].Name)
// Verify delete the runner by id
- req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
+ req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent)
// Verify runner deletion
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
+ req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNotFound)
})
@@ -329,4 +331,12 @@ func testActionsRunnerRepo(t *testing.T) {
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNotFound)
})
+
+ t.Run("DeleteAdminRunnerNotFoundUnknownID", func(t *testing.T) {
+ userUsername := "user2"
+ token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
+ // Verify delete a runner by unknown id is not found
+ req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 4384797347934)).AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusNotFound)
+ })
}
diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go
index 083535a9a5..b30cdfd0fc 100644
--- a/tests/integration/api_helper_for_declarative_test.go
+++ b/tests/integration/api_helper_for_declarative_test.go
@@ -263,7 +263,7 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64)
var req *RequestWrapper
var resp *httptest.ResponseRecorder
- for i := 0; i < 6; i++ {
+ for range 6 {
req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
MergeMessageField: "doAPIMergePullRequest Merge",
Do: string(repo_model.MergeStyleMerge),
diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go
index e035f7200b..370c90a100 100644
--- a/tests/integration/api_issue_test.go
+++ b/tests/integration/api_issue_test.go
@@ -166,7 +166,7 @@ func TestAPICreateIssueParallel(t *testing.T) {
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name)
var wg sync.WaitGroup
- for i := 0; i < 10; i++ {
+ for i := range 10 {
wg.Add(1)
go func(parentT *testing.T, i int) {
parentT.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) {
@@ -267,10 +267,7 @@ func TestAPISearchIssues(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// as this API was used in the frontend, it uses UI page size
- expectedIssueCount := 20 // from the fixtures
- if expectedIssueCount > setting.UI.IssuePagingNum {
- expectedIssueCount = setting.UI.IssuePagingNum
- }
+ expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
link, _ := url.Parse("/api/v1/repos/issues/search")
token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)
@@ -371,10 +368,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// as this API was used in the frontend, it uses UI page size
- expectedIssueCount := 20 // from the fixtures
- if expectedIssueCount > setting.UI.IssuePagingNum {
- expectedIssueCount = setting.UI.IssuePagingNum
- }
+ expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
link, _ := url.Parse("/api/v1/repos/issues/search")
token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)
diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go
index 86b3be9d0c..8f2c2592e7 100644
--- a/tests/integration/api_packages_chef_test.go
+++ b/tests/integration/api_packages_chef_test.go
@@ -181,7 +181,7 @@ nwIDAQAB
var data []byte
if version == "1.3" {
- data = []byte(fmt.Sprintf(
+ data = fmt.Appendf(nil,
"Method:%s\nPath:%s\nX-Ops-Content-Hash:%s\nX-Ops-Sign:version=%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s\nX-Ops-Server-API-Version:%s",
req.Method,
path.Clean(req.URL.Path),
@@ -190,17 +190,17 @@ nwIDAQAB
req.Header.Get("X-Ops-Timestamp"),
username,
req.Header.Get("X-Ops-Server-Api-Version"),
- ))
+ )
} else {
sum := sha1.Sum([]byte(path.Clean(req.URL.Path)))
- data = []byte(fmt.Sprintf(
+ data = fmt.Appendf(nil,
"Method:%s\nHashed Path:%s\nX-Ops-Content-Hash:%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s",
req.Method,
base64.StdEncoding.EncodeToString(sum[:]),
req.Header.Get("X-Ops-Content-Hash"),
req.Header.Get("X-Ops-Timestamp"),
username,
- ))
+ )
}
for k := range req.Header {
diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go
index b2db77685d..204f099bbe 100644
--- a/tests/integration/api_packages_container_test.go
+++ b/tests/integration/api_packages_container_test.go
@@ -7,6 +7,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/base64"
+ "encoding/hex"
"fmt"
"net/http"
"strconv"
@@ -17,7 +18,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- container_model "code.gitea.io/gitea/models/packages/container"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
container_module "code.gitea.io/gitea/modules/packages/container"
@@ -57,7 +57,7 @@ func TestPackageContainer(t *testing.T) {
return values
}
- images := []string{"test", "te/st"}
+ images := []string{"test", "sub/name"}
tags := []string{"latest", "main"}
multiTag := "multi"
@@ -70,7 +70,8 @@ func TestPackageContainer(t *testing.T) {
configContent := `{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/true"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"container":"b89fe92a887d55c0961f02bdfbfd8ac3ddf66167db374770d2d9e9fab3311510","container_config":{"Hostname":"b89fe92a887d","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/true\"]"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"created":"2022-01-01T00:00:00.000000000Z","docker_version":"20.10.12","history":[{"created":"2022-01-01T00:00:00.000000000Z","created_by":"/bin/sh -c #(nop) COPY file:0e7589b0c800daaf6fa460d2677101e4676dd9491980210cb345480e513f3602 in /true "},{"created":"2022-01-01T00:00:00.000000001Z","created_by":"/bin/sh -c #(nop) CMD [\"/true\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:0ff3b91bdf21ecdf2f2f3d4372c2098a14dbe06cd678e8f0a85fd4902d00e2e2"]}}`
manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6"
- manifestContent := `{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}`
+ manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}`
+ manifestContentType := container_module.ContentTypeDockerDistributionManifestV2
untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d"
untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}`
@@ -250,7 +251,7 @@ func TestPackageContainer(t *testing.T) {
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
- pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_model.UploadVersion)
+ pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_module.UploadVersion)
assert.NoError(t, err)
pfs, err := packages_model.GetFilesByVersionID(db.DefaultContext, pv.ID)
@@ -296,11 +297,22 @@ func TestPackageContainer(t *testing.T) {
SetHeader("Content-Range", "1-10")
MakeRequest(t, req, http.StatusRequestedRangeNotSatisfiable)
- contentRange := fmt.Sprintf("0-%d", len(blobContent)-1)
- req.SetHeader("Content-Range", contentRange)
+ // first patch without Content-Range
+ req = NewRequestWithBody(t, "PATCH", setting.AppURL+uploadURL[1:], bytes.NewReader(blobContent[:1])).
+ AddTokenAuth(userToken)
+ resp = MakeRequest(t, req, http.StatusAccepted)
+ assert.NotEmpty(t, resp.Header().Get("Location"))
+ assert.Equal(t, "0-0", resp.Header().Get("Range"))
+
+ // then send remaining content with Content-Range
+ req = NewRequestWithBody(t, "PATCH", setting.AppURL+uploadURL[1:], bytes.NewReader(blobContent[1:])).
+ SetHeader("Content-Range", fmt.Sprintf("1-%d", len(blobContent)-1)).
+ AddTokenAuth(userToken)
resp = MakeRequest(t, req, http.StatusAccepted)
+ contentRange := fmt.Sprintf("0-%d", len(blobContent)-1)
assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid"))
+ assert.NotEmpty(t, resp.Header().Get("Location"))
assert.Equal(t, contentRange, resp.Header().Get("Range"))
uploadURL = resp.Header().Get("Location")
@@ -310,7 +322,8 @@ func TestPackageContainer(t *testing.T) {
resp = MakeRequest(t, req, http.StatusNoContent)
assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid"))
- assert.Equal(t, fmt.Sprintf("0-%d", len(blobContent)), resp.Header().Get("Range"))
+ assert.Equal(t, uploadURL, resp.Header().Get("Location"))
+ assert.Equal(t, contentRange, resp.Header().Get("Range"))
pbu, err = packages_model.GetBlobUploadByID(db.DefaultContext, uuid)
assert.NoError(t, err)
@@ -341,7 +354,8 @@ func TestPackageContainer(t *testing.T) {
resp = MakeRequest(t, req, http.StatusNoContent)
assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid"))
- assert.Equal(t, "0-0", resp.Header().Get("Range"))
+ // FIXME: undefined behavior when the uploaded content is empty: https://github.com/opencontainers/distribution-spec/issues/578
+ assert.Nil(t, resp.Header().Values("Range"))
req = NewRequest(t, "DELETE", setting.AppURL+uploadURL[1:]).
AddTokenAuth(userToken)
@@ -429,7 +443,7 @@ func TestPackageContainer(t *testing.T) {
assert.Len(t, pd.Files, 3)
for _, pfd := range pd.Files {
switch pfd.File.Name {
- case container_model.ManifestFilename:
+ case container_module.ManifestFilename:
assert.True(t, pfd.File.IsLead)
assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", pfd.Properties.GetByName(container_module.PropertyMediaType))
assert.Equal(t, manifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest))
@@ -492,7 +506,7 @@ func TestPackageContainer(t *testing.T) {
resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length"))
- assert.Equal(t, oci.MediaTypeImageManifest, resp.Header().Get("Content-Type"))
+ assert.Equal(t, manifestContentType, resp.Header().Get("Content-Type"))
assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest"))
assert.Equal(t, manifestContent, resp.Body.String())
})
@@ -531,7 +545,7 @@ func TestPackageContainer(t *testing.T) {
assert.Len(t, pd.Files, 3)
for _, pfd := range pd.Files {
- if pfd.File.Name == container_model.ManifestFilename {
+ if pfd.File.Name == container_module.ManifestFilename {
assert.True(t, pfd.File.IsLead)
assert.Equal(t, oci.MediaTypeImageManifest, pfd.Properties.GetByName(container_module.PropertyMediaType))
assert.Equal(t, untaggedManifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest))
@@ -623,6 +637,22 @@ func TestPackageContainer(t *testing.T) {
assert.Equal(t, blobContent, resp.Body.Bytes())
})
+ t.Run("GetBlob/Empty", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ emptyDigestBuf := sha256.Sum256(nil)
+ emptyDigest := "sha256:" + hex.EncodeToString(emptyDigestBuf[:])
+ req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, emptyDigest), strings.NewReader("")).AddTokenAuth(userToken)
+ MakeRequest(t, req, http.StatusCreated)
+
+ req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, emptyDigest)).AddTokenAuth(userToken)
+ resp := MakeRequest(t, req, http.StatusOK)
+ assert.Equal(t, "0", resp.Header().Get("Content-Length"))
+
+ req = NewRequest(t, "GET", fmt.Sprintf("%s/blobs/%s", url, emptyDigest)).AddTokenAuth(userToken)
+ resp = MakeRequest(t, req, http.StatusOK)
+ assert.Equal(t, "0", resp.Header().Get("Content-Length"))
+ })
+
t.Run("GetTagList", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -732,7 +762,7 @@ func TestPackageContainer(t *testing.T) {
url := fmt.Sprintf("%sv2/%s/parallel", setting.AppURL, user.Name)
var wg sync.WaitGroup
- for i := 0; i < 10; i++ {
+ for i := range 10 {
wg.Add(1)
content := []byte{byte(i)}
diff --git a/tests/integration/api_packages_debian_test.go b/tests/integration/api_packages_debian_test.go
index 98027d774c..3ae60d2aa2 100644
--- a/tests/integration/api_packages_debian_test.go
+++ b/tests/integration/api_packages_debian_test.go
@@ -284,7 +284,7 @@ func TestPackageDebian(t *testing.T) {
// because "Iterate" keeps a dangling SQL session but the callback function still uses the same session to execute statements.
// The "Iterate" problem has been checked by TestContextSafety now, so here we only need to check the cleanup logic with a small number
packagesCount := 2
- for i := 0; i < packagesCount; i++ {
+ for i := range packagesCount {
uploadURL := fmt.Sprintf("%s/pool/%s/%s/upload", rootURL, "test", "main")
req := NewRequestWithBody(t, "PUT", uploadURL, createArchive(packageName, "1.0."+strconv.Itoa(i), "all")).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
diff --git a/tests/integration/api_packages_goproxy_test.go b/tests/integration/api_packages_goproxy_test.go
index dab9fefc5e..fa0ee5b901 100644
--- a/tests/integration/api_packages_goproxy_test.go
+++ b/tests/integration/api_packages_goproxy_test.go
@@ -87,7 +87,7 @@ func TestPackageGo(t *testing.T) {
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusConflict)
- time.Sleep(time.Second)
+ time.Sleep(time.Second) // Ensure the timestamp is different, then the "list" below can have stable order
content = createArchive(map[string][]byte{
packageName + "@" + packageVersion2 + "/go.mod": []byte(goModContent),
diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go
index 408c8805c2..30ef1884cd 100644
--- a/tests/integration/api_packages_maven_test.go
+++ b/tests/integration/api_packages_maven_test.go
@@ -321,7 +321,7 @@ func TestPackageMavenConcurrent(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var wg sync.WaitGroup
- for i := 0; i < 10; i++ {
+ for i := range 10 {
wg.Add(1)
go func(i int) {
putFile(t, fmt.Sprintf("/%s/%s.jar", packageVersion, strconv.Itoa(i)), "test", http.StatusCreated)
diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go
index c0e69a82cd..65b1b9845a 100644
--- a/tests/integration/api_packages_nuget_test.go
+++ b/tests/integration/api_packages_nuget_test.go
@@ -46,21 +46,30 @@ func TestPackageNuGet(t *testing.T) {
defer tests.PrepareTestEnv(t)()
type FeedEntryProperties struct {
- Version string `xml:"Version"`
- NormalizedVersion string `xml:"NormalizedVersion"`
Authors string `xml:"Authors"`
+ Copyright string `xml:"Copyright,omitempty"`
+ Created nuget.TypedValue[time.Time] `xml:"Created"`
Dependencies string `xml:"Dependencies"`
Description string `xml:"Description"`
- VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"`
+ DevelopmentDependency nuget.TypedValue[bool] `xml:"DevelopmentDependency"`
DownloadCount nuget.TypedValue[int64] `xml:"DownloadCount"`
- PackageSize nuget.TypedValue[int64] `xml:"PackageSize"`
- Created nuget.TypedValue[time.Time] `xml:"Created"`
+ ID string `xml:"Id"`
+ IconURL string `xml:"IconUrl,omitempty"`
+ Language string `xml:"Language,omitempty"`
LastUpdated nuget.TypedValue[time.Time] `xml:"LastUpdated"`
- Published nuget.TypedValue[time.Time] `xml:"Published"`
+ LicenseURL string `xml:"LicenseUrl,omitempty"`
+ MinClientVersion string `xml:"MinClientVersion,omitempty"`
+ NormalizedVersion string `xml:"NormalizedVersion"`
+ Owners string `xml:"Owners,omitempty"`
+ PackageSize nuget.TypedValue[int64] `xml:"PackageSize"`
ProjectURL string `xml:"ProjectUrl,omitempty"`
+ Published nuget.TypedValue[time.Time] `xml:"Published"`
ReleaseNotes string `xml:"ReleaseNotes,omitempty"`
RequireLicenseAcceptance nuget.TypedValue[bool] `xml:"RequireLicenseAcceptance"`
+ Tags string `xml:"Tags,omitempty"`
Title string `xml:"Title"`
+ Version string `xml:"Version"`
+ VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"`
}
type FeedEntry struct {
@@ -86,28 +95,54 @@ func TestPackageNuGet(t *testing.T) {
readToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadPackage)
badToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadNotification)
- packageName := "test.package"
+ packageName := "test.package" // id
+ packageID := packageName
packageVersion := "1.0.3"
packageAuthors := "KN4CK3R"
packageDescription := "Gitea Test Package"
+
symbolFilename := "test.pdb"
symbolID := "d910bb6948bd4c6cb40155bcf52c3c94"
+ packageCopyright := "Package Copyright"
+ packageIconURL := "https://gitea.io/favicon.png"
+ packageLanguage := "Package Language"
+ packageLicenseURL := "https://gitea.io/license"
+ packageMinClientVersion := "1.0.0.0"
+ packageOwners := "Package Owners"
+ packageProjectURL := "https://gitea.io"
+ packageReleaseNotes := "Package Release Notes"
+ packageTags := "tag_1 tag_2 tag_3"
+ packageTitle := "Package Title"
+ packageDevelopmentDependency := true
+ packageRequireLicenseAcceptance := true
+
createNuspec := func(id, version string) string {
return `<?xml version="1.0" encoding="utf-8"?>
-<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
- <metadata>
- <id>` + id + `</id>
- <version>` + version + `</version>
- <authors>` + packageAuthors + `</authors>
- <description>` + packageDescription + `</description>
- <dependencies>
- <group targetFramework=".NETStandard2.0">
- <dependency id="Microsoft.CSharp" version="4.5.0" />
- </group>
- </dependencies>
- </metadata>
-</package>`
+ <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
+ <metadata minClientVersion="` + packageMinClientVersion + `">
+ <authors>` + packageAuthors + `</authors>
+ <copyright>` + packageCopyright + `</copyright>
+ <description>` + packageDescription + `</description>
+ <developmentDependency>true</developmentDependency>
+ <iconUrl>` + packageIconURL + `</iconUrl>
+ <id>` + id + `</id>
+ <language>` + packageLanguage + `</language>
+ <licenseUrl>` + packageLicenseURL + `</licenseUrl>
+ <owners>` + packageOwners + `</owners>
+ <projectUrl>` + packageProjectURL + `</projectUrl>
+ <releaseNotes>` + packageReleaseNotes + `</releaseNotes>
+ <requireLicenseAcceptance>true</requireLicenseAcceptance>
+ <tags>` + packageTags + `</tags>
+ <title>` + packageTitle + `</title>
+ <version>` + version + `</version>
+ <dependencies>
+ <group targetFramework=".NETStandard2.0">
+ <dependency id="Microsoft.CSharp" version="4.5.0" />
+ </group>
+ </dependencies>
+ </metadata>
+ </package>`
}
createPackage := func(id, version string) *bytes.Buffer {
@@ -393,7 +428,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
assert.NoError(t, err)
- assert.Equal(t, int64(412), pb.Size)
+ assert.Equal(t, int64(610), pb.Size)
case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion):
assert.False(t, pf.IsLead)
@@ -405,7 +440,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
assert.NoError(t, err)
- assert.Equal(t, int64(427), pb.Size)
+ assert.Equal(t, int64(996), pb.Size)
case symbolFilename:
assert.False(t, pf.IsLead)
@@ -736,10 +771,24 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
var result FeedEntry
decodeXML(t, resp, &result)
- assert.Equal(t, packageName, result.Properties.Title)
- assert.Equal(t, packageVersion, result.Properties.Version)
assert.Equal(t, packageAuthors, result.Properties.Authors)
assert.Equal(t, packageDescription, result.Properties.Description)
+ assert.Equal(t, packageID, result.Properties.ID)
+ assert.Equal(t, packageVersion, result.Properties.Version)
+
+ assert.Equal(t, packageCopyright, result.Properties.Copyright)
+ assert.Equal(t, packageDevelopmentDependency, result.Properties.DevelopmentDependency.Value)
+ assert.Equal(t, packageIconURL, result.Properties.IconURL)
+ assert.Equal(t, packageLanguage, result.Properties.Language)
+ assert.Equal(t, packageLicenseURL, result.Properties.LicenseURL)
+ assert.Equal(t, packageMinClientVersion, result.Properties.MinClientVersion)
+ assert.Equal(t, packageOwners, result.Properties.Owners)
+ assert.Equal(t, packageProjectURL, result.Properties.ProjectURL)
+ assert.Equal(t, packageReleaseNotes, result.Properties.ReleaseNotes)
+ assert.Equal(t, packageRequireLicenseAcceptance, result.Properties.RequireLicenseAcceptance.Value)
+ assert.Equal(t, packageTags, result.Properties.Tags)
+ assert.Equal(t, packageTitle, result.Properties.Title)
+
assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies)
})
diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go
index 469bd1fc6c..bd1959f64e 100644
--- a/tests/integration/api_packages_rpm_test.go
+++ b/tests/integration/api_packages_rpm_test.go
@@ -157,9 +157,14 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
t.Run("Download", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
+ // download the package without the file name
req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
resp := MakeRequest(t, req, http.StatusOK)
+ assert.Equal(t, content, resp.Body.Bytes())
+ // download the package with a file name (it can be anything)
+ req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s/any-file-name", groupURL, packageName, packageVersion, packageArchitecture))
+ resp = MakeRequest(t, req, http.StatusOK)
assert.Equal(t, content, resp.Body.Bytes())
})
@@ -447,7 +452,8 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
pub, err := openpgp.ReadArmoredKeyRing(gpgResp.Body)
require.NoError(t, err)
- req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
+ rpmFileName := fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)
+ req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture, rpmFileName))
resp := MakeRequest(t, req, http.StatusOK)
_, sigs, err := rpmutils.Verify(resp.Body, pub)
diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go
index 786addbd76..f10b098885 100644
--- a/tests/integration/api_packages_test.go
+++ b/tests/integration/api_packages_test.go
@@ -15,9 +15,9 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- container_model "code.gitea.io/gitea/models/packages/container"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
@@ -538,7 +538,7 @@ func TestPackageCleanup(t *testing.T) {
assert.NoError(t, err)
assert.NotEmpty(t, pbs)
- _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion)
+ _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion)
assert.NoError(t, err)
err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration)
@@ -548,7 +548,7 @@ func TestPackageCleanup(t *testing.T) {
assert.NoError(t, err)
assert.Empty(t, pbs)
- _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion)
+ _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion)
assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
})
@@ -636,12 +636,16 @@ func TestPackageCleanup(t *testing.T) {
},
{
Name: "Mixed",
- Versions: []version{
- {Version: "keep", ShouldExist: true, Created: time.Now().Add(time.Duration(10000)).Unix()},
- {Version: "dummy", ShouldExist: true, Created: 1},
- {Version: "test-3", ShouldExist: true},
- {Version: "test-4", ShouldExist: false, Created: 1},
- },
+ Versions: func(limit, removeDays int) []version {
+ aa := []version{
+ {Version: "keep", ShouldExist: true, Created: time.Now().Add(time.Duration(10000)).Unix()},
+ {Version: "dummy", ShouldExist: true, Created: 1},
+ }
+ for i := range limit {
+ aa = append(aa, version{Version: fmt.Sprintf("test-%v", i+3), ShouldExist: util.Iif(i < removeDays, true, false), Created: time.Now().AddDate(0, 0, -i).Unix()})
+ }
+ return aa
+ }(220, 7),
Rule: &packages_model.PackageCleanupRule{
Enabled: true,
KeepCount: 1,
@@ -686,7 +690,7 @@ func TestPackageCleanup(t *testing.T) {
err = packages_service.DeletePackageVersionAndReferences(db.DefaultContext, pv)
assert.NoError(t, err)
} else {
- assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
+ assert.ErrorIs(t, err, packages_model.ErrPackageNotExist, v.Version)
}
}
diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go
index e698148d84..97c2c0d54b 100644
--- a/tests/integration/api_repo_archive_test.go
+++ b/tests/integration/api_repo_archive_test.go
@@ -12,7 +12,9 @@ import (
"testing"
auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/tests"
@@ -58,9 +60,12 @@ func TestAPIDownloadArchive(t *testing.T) {
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
+
+ t.Run("GitHubStyle", testAPIDownloadArchiveGitHubStyle)
+ t.Run("PrivateRepo", testAPIDownloadArchivePrivateRepo)
}
-func TestAPIDownloadArchive2(t *testing.T) {
+func testAPIDownloadArchiveGitHubStyle(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
@@ -95,7 +100,13 @@ func TestAPIDownloadArchive2(t *testing.T) {
bs, err = io.ReadAll(resp.Body)
assert.NoError(t, err)
assert.Len(t, bs, 382)
+}
- link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
- MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
+func testAPIDownloadArchivePrivateRepo(t *testing.T) {
+ _ = repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), &repo_model.Repository{ID: 1, IsPrivate: true}, "is_private")
+ MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/archive/master.zip"), http.StatusNotFound)
+ MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/zipball/master"), http.StatusNotFound)
+ _ = repo_model.UpdateRepoUnitPublicAccess(t.Context(), &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeCode, AnonymousAccessMode: perm.AccessModeRead})
+ MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/archive/master.zip"), http.StatusOK)
+ MakeRequest(t, NewRequest(t, "HEAD", "/api/v1/repos/user2/repo1/zipball/master"), http.StatusOK)
}
diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go
index 0a7f37facb..af3bc54680 100644
--- a/tests/integration/api_repo_file_create_test.go
+++ b/tests/integration/api_repo_file_create_test.go
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
"github.com/stretchr/testify/assert"
@@ -52,8 +53,8 @@ func getCreateFileOptions() api.CreateFileOptions {
func normalizeFileContentResponseCommitTime(c *api.ContentsResponse) {
// decoded JSON response may contain different timezone from the one parsed by git commit
// so we need to normalize the time to UTC to make "assert.Equal" pass
- c.LastCommitterDate = c.LastCommitterDate.UTC()
- c.LastAuthorDate = c.LastAuthorDate.UTC()
+ c.LastCommitterDate = util.ToPointer(c.LastCommitterDate.UTC())
+ c.LastAuthorDate = util.ToPointer(c.LastAuthorDate.UTC())
}
type apiFileResponseInfo struct {
@@ -74,9 +75,9 @@ func getExpectedFileResponseForCreate(info apiFileResponseInfo) *api.FileRespons
Name: path.Base(info.treePath),
Path: info.treePath,
SHA: sha,
- LastCommitSHA: info.lastCommitSHA,
- LastCommitterDate: info.lastCommitterWhen,
- LastAuthorDate: info.lastAuthorWhen,
+ LastCommitSHA: util.ToPointer(info.lastCommitSHA),
+ LastCommitterDate: util.ToPointer(info.lastCommitterWhen),
+ LastAuthorDate: util.ToPointer(info.lastAuthorWhen),
Size: 16,
Type: "file",
Encoding: &encoding,
@@ -130,7 +131,7 @@ func BenchmarkAPICreateFileSmall(b *testing.B) {
repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for n := 0; b.Loop(); n++ {
treePath := fmt.Sprintf("update/file%d.txt", n)
_, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
}
@@ -145,7 +146,7 @@ func BenchmarkAPICreateFileMedium(b *testing.B) {
repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo
b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for n := 0; b.Loop(); n++ {
treePath := fmt.Sprintf("update/file%d.txt", n)
copy(data, treePath)
_, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go
index 47730cd933..9dd47f93e6 100644
--- a/tests/integration/api_repo_file_delete_test.go
+++ b/tests/integration/api_repo_file_delete_test.go
@@ -20,20 +20,22 @@ import (
func getDeleteFileOptions() *api.DeleteFileOptions {
return &api.DeleteFileOptions{
- FileOptions: api.FileOptions{
- BranchName: "master",
- NewBranchName: "master",
- Message: "Removing the file new/file.txt",
- Author: api.Identity{
- Name: "John Doe",
- Email: "johndoe@example.com",
- },
- Committer: api.Identity{
- Name: "Jane Doe",
- Email: "janedoe@example.com",
+ FileOptionsWithSHA: api.FileOptionsWithSHA{
+ FileOptions: api.FileOptions{
+ BranchName: "master",
+ NewBranchName: "master",
+ Message: "Removing the file new/file.txt",
+ Author: api.Identity{
+ Name: "John Doe",
+ Email: "johndoe@example.com",
+ },
+ Committer: api.Identity{
+ Name: "Jane Doe",
+ Email: "janedoe@example.com",
+ },
},
+ SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
},
- SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
}
}
@@ -110,7 +112,7 @@ func TestAPIDeleteFile(t *testing.T) {
deleteFileOptions.SHA = "badsha"
req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions).
AddTokenAuth(token2)
- MakeRequest(t, req, http.StatusBadRequest)
+ MakeRequest(t, req, http.StatusUnprocessableEntity)
// Test creating a file in repo16 by user4 who does not have write access
fileID++
diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go
index 1605cfbd0b..9a56711da6 100644
--- a/tests/integration/api_repo_file_update_test.go
+++ b/tests/integration/api_repo_file_update_test.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
"github.com/stretchr/testify/assert"
@@ -27,7 +28,7 @@ func getUpdateFileOptions() *api.UpdateFileOptions {
content := "This is updated text"
contentEncoded := base64.StdEncoding.EncodeToString([]byte(content))
return &api.UpdateFileOptions{
- DeleteFileOptions: api.DeleteFileOptions{
+ FileOptionsWithSHA: api.FileOptionsWithSHA{
FileOptions: api.FileOptions{
BranchName: "master",
NewBranchName: "master",
@@ -60,9 +61,9 @@ func getExpectedFileResponseForUpdate(info apiFileResponseInfo) *api.FileRespons
Name: path.Base(info.treePath),
Path: info.treePath,
SHA: sha,
- LastCommitSHA: info.lastCommitSHA,
- LastCommitterDate: info.lastCommitterWhen,
- LastAuthorDate: info.lastAuthorWhen,
+ LastCommitSHA: util.ToPointer(info.lastCommitSHA),
+ LastCommitterDate: util.ToPointer(info.lastCommitterWhen),
+ LastAuthorDate: util.ToPointer(info.lastAuthorWhen),
Type: "file",
Size: 20,
Encoding: &encoding,
diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go
index 9b192c6304..563d6fcc10 100644
--- a/tests/integration/api_repo_get_contents_list_test.go
+++ b/tests/integration/api_repo_get_contents_list_test.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/stretchr/testify/assert"
@@ -35,9 +36,9 @@ func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA stri
Name: path.Base(treePath),
Path: treePath,
SHA: sha,
- LastCommitSHA: lastCommitSHA,
- LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
- LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
+ LastCommitSHA: util.ToPointer(lastCommitSHA),
+ LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
+ LastAuthorDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
Type: "file",
Size: 30,
URL: &selfURL,
@@ -65,7 +66,6 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
- treePath := "" // root dir
// Get user2's token
session := loginUser(t, user2.Name)
@@ -94,7 +94,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
// ref is default ref
ref := repo1.DefaultBranch
refType := "branch"
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref)
resp := MakeRequest(t, req, http.StatusOK)
var contentsListResponse []*api.ContentsResponse
DecodeJSON(t, resp, &contentsListResponse)
@@ -106,7 +106,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
// No ref
refType = "branch"
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath)
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo1.Name)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsListResponse)
assert.NotNil(t, contentsListResponse)
@@ -117,7 +117,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
// ref is the branch we created above in setup
ref = newBranch
refType = "branch"
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsListResponse)
assert.NotNil(t, contentsListResponse)
@@ -131,7 +131,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
// ref is the new tag we created above in setup
ref = newTag
refType = "tag"
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsListResponse)
assert.NotNil(t, contentsListResponse)
@@ -145,7 +145,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
// ref is a commit
ref = commitID
refType = "commit"
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsListResponse)
assert.NotNil(t, contentsListResponse)
@@ -154,21 +154,21 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
// Test file contents a file with a bad ref
ref = "badref"
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref)
MakeRequest(t, req, http.StatusNotFound)
// Test accessing private ref with user token that does not have access - should fail
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath).
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name).
AddTokenAuth(token4)
MakeRequest(t, req, http.StatusNotFound)
// Test access private ref of owner of token
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md", user2.Name, repo16.Name).
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name).
AddTokenAuth(token2)
MakeRequest(t, req, http.StatusOK)
// Test access of org org3 private repo file by owner user2
- req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath).
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", org3.Name, repo3.Name).
AddTokenAuth(token2)
MakeRequest(t, req, http.StatusOK)
}
diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go
index a7de97c052..33df74f6ee 100644
--- a/tests/integration/api_repo_get_contents_test.go
+++ b/tests/integration/api_repo_get_contents_test.go
@@ -7,6 +7,7 @@ import (
"io"
"net/http"
"net/url"
+ "slices"
"testing"
"time"
@@ -20,9 +21,9 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
- "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string) *api.ContentsResponse {
@@ -34,9 +35,9 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string)
Name: treePath,
Path: treePath,
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
- LastCommitSHA: lastCommitSHA,
- LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
- LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
+ LastCommitSHA: util.ToPointer(lastCommitSHA),
+ LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
+ LastAuthorDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
Type: "file",
Size: 30,
Encoding: util.ToPointer("base64"),
@@ -54,7 +55,11 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string)
}
func TestAPIGetContents(t *testing.T) {
- onGiteaRun(t, testAPIGetContents)
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ testAPIGetContentsRefFormats(t)
+ testAPIGetContents(t, u)
+ testAPIGetContentsExt(t)
+ })
}
func testAPIGetContents(t *testing.T, u *url.URL) {
@@ -76,30 +81,34 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
// Get the commit ID of the default branch
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo1)
- assert.NoError(t, err)
+ require.NoError(t, err)
defer gitRepo.Close()
// Make a new branch in repo1
newBranch := "test_branch"
err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
- assert.NoError(t, err)
+ require.NoError(t, err)
commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
- assert.NoError(t, err)
+ require.NoError(t, err)
// Make a new tag in repo1
newTag := "test_tag"
err = gitRepo.CreateTag(newTag, commitID)
- assert.NoError(t, err)
+ require.NoError(t, err)
/*** END SETUP ***/
+ // not found
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/no-such/file.md", user2.Name, repo1.Name)
+ resp := MakeRequest(t, req, http.StatusNotFound)
+ assert.Contains(t, resp.Body.String(), "object does not exist [id: , rel_path: no-such]")
+
// ref is default ref
ref := repo1.DefaultBranch
refType := "branch"
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
- resp := MakeRequest(t, req, http.StatusOK)
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
+ resp = MakeRequest(t, req, http.StatusOK)
var contentsResponse api.ContentsResponse
DecodeJSON(t, resp, &contentsResponse)
- assert.NotNil(t, contentsResponse)
lastCommit, _ := gitRepo.GetCommitByPath("README.md")
expectedContentsResponse := getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String())
assert.Equal(t, *expectedContentsResponse, contentsResponse)
@@ -109,17 +118,15 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsResponse)
- assert.NotNil(t, contentsResponse)
expectedContentsResponse = getExpectedContentsResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String())
assert.Equal(t, *expectedContentsResponse, contentsResponse)
- // ref is the branch we created above in setup
+ // ref is the branch we created above in setup
ref = newBranch
refType = "branch"
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsResponse)
- assert.NotNil(t, contentsResponse)
branchCommit, _ := gitRepo.GetBranchCommit(ref)
lastCommit, _ = branchCommit.GetCommitByPath("README.md")
expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String())
@@ -131,7 +138,6 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsResponse)
- assert.NotNil(t, contentsResponse)
tagCommit, _ := gitRepo.GetTagCommit(ref)
lastCommit, _ = tagCommit.GetCommitByPath("README.md")
expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String())
@@ -143,7 +149,6 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &contentsResponse)
- assert.NotNil(t, contentsResponse)
expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, commitID)
assert.Equal(t, *expectedContentsResponse, contentsResponse)
@@ -168,9 +173,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
MakeRequest(t, req, http.StatusOK)
}
-func TestAPIGetContentsRefFormats(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
-
+func testAPIGetContentsRefFormats(t *testing.T) {
file := "README.md"
sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
content := "# repo1\n\nDescription for repo1"
@@ -203,3 +206,98 @@ func TestAPIGetContentsRefFormats(t *testing.T) {
// FIXME: this is an incorrect behavior, non-existing branch falls back to default branch
_ = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/README.md?ref=no-such"), http.StatusOK)
}
+
+func testAPIGetContentsExt(t *testing.T) {
+ session := loginUser(t, "user2")
+ token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+ t.Run("DirContents", func(t *testing.T) {
+ req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext?ref=sub-home-md-img-check")
+ resp := MakeRequest(t, req, http.StatusOK)
+ var contentsResponse api.ContentsExtResponse
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.FileContents)
+ assert.NotNil(t, contentsResponse.DirContents)
+
+ req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/.?ref=sub-home-md-img-check")
+ resp = MakeRequest(t, req, http.StatusOK)
+ contentsResponse = api.ContentsExtResponse{}
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.FileContents)
+ assert.NotNil(t, contentsResponse.DirContents)
+
+ req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check")
+ resp = MakeRequest(t, req, http.StatusOK)
+ contentsResponse = api.ContentsExtResponse{}
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.FileContents)
+ assert.Equal(t, "README.md", contentsResponse.DirContents[0].Name)
+ assert.Nil(t, contentsResponse.DirContents[0].Encoding)
+ assert.Nil(t, contentsResponse.DirContents[0].Content)
+ assert.Nil(t, contentsResponse.DirContents[0].LastCommitSHA)
+ assert.Nil(t, contentsResponse.DirContents[0].LastCommitMessage)
+
+ // "includes=file_content" shouldn't affect directory listing
+ req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check&includes=file_content")
+ resp = MakeRequest(t, req, http.StatusOK)
+ contentsResponse = api.ContentsExtResponse{}
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.FileContents)
+ assert.Equal(t, "README.md", contentsResponse.DirContents[0].Name)
+ assert.Nil(t, contentsResponse.DirContents[0].Encoding)
+ assert.Nil(t, contentsResponse.DirContents[0].Content)
+
+ req = NewRequestf(t, "GET", "/api/v1/repos/user2/lfs/contents-ext?includes=file_content,lfs_metadata").AddTokenAuth(token2)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ contentsResponse = api.ContentsExtResponse{}
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.FileContents)
+ respFileIdx := slices.IndexFunc(contentsResponse.DirContents, func(response *api.ContentsResponse) bool { return response.Name == "jpeg.jpg" })
+ require.NotEqual(t, -1, respFileIdx)
+ respFile := contentsResponse.DirContents[respFileIdx]
+ assert.Equal(t, "jpeg.jpg", respFile.Name)
+ assert.Nil(t, respFile.Encoding)
+ assert.Nil(t, respFile.Content)
+ assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize)
+ assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
+ })
+ t.Run("FileContents", func(t *testing.T) {
+ // by default, no file content or commit info is returned
+ req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check")
+ resp := MakeRequest(t, req, http.StatusOK)
+ var contentsResponse api.ContentsExtResponse
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.DirContents)
+ assert.Equal(t, "README.md", contentsResponse.FileContents.Name)
+ assert.Nil(t, contentsResponse.FileContents.Encoding)
+ assert.Nil(t, contentsResponse.FileContents.Content)
+ assert.Nil(t, contentsResponse.FileContents.LastCommitSHA)
+ assert.Nil(t, contentsResponse.FileContents.LastCommitMessage)
+
+ // file content is only returned when `includes=file_content`
+ req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check&includes=file_content,commit_metadata,commit_message")
+ resp = MakeRequest(t, req, http.StatusOK)
+ contentsResponse = api.ContentsExtResponse{}
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.DirContents)
+ assert.Equal(t, "README.md", contentsResponse.FileContents.Name)
+ assert.NotNil(t, contentsResponse.FileContents.Encoding)
+ assert.NotNil(t, contentsResponse.FileContents.Content)
+ assert.Equal(t, "4649299398e4d39a5c09eb4f534df6f1e1eb87cc", *contentsResponse.FileContents.LastCommitSHA)
+ assert.Equal(t, "Test how READMEs render images when found in a subfolder\n", *contentsResponse.FileContents.LastCommitMessage)
+
+ req = NewRequestf(t, "GET", "/api/v1/repos/user2/lfs/contents-ext/jpeg.jpg?includes=file_content").AddTokenAuth(token2)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ contentsResponse = api.ContentsExtResponse{}
+ DecodeJSON(t, resp, &contentsResponse)
+ assert.Nil(t, contentsResponse.DirContents)
+ assert.NotNil(t, contentsResponse.FileContents)
+ respFile := contentsResponse.FileContents
+ assert.Equal(t, "jpeg.jpg", respFile.Name)
+ assert.NotNil(t, respFile.Encoding)
+ assert.NotNil(t, respFile.Content)
+ assert.Nil(t, contentsResponse.FileContents.LastCommitSHA)
+ assert.Nil(t, contentsResponse.FileContents.LastCommitMessage)
+ assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize)
+ assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
+ })
+}
diff --git a/tests/integration/api_repo_languages_test.go b/tests/integration/api_repo_languages_test.go
index 1045aef57d..6347a43b4e 100644
--- a/tests/integration/api_repo_languages_test.go
+++ b/tests/integration/api_repo_languages_test.go
@@ -9,6 +9,8 @@ import (
"testing"
"time"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/stretchr/testify/assert"
)
@@ -32,7 +34,8 @@ func TestRepoLanguages(t *testing.T) {
"content": "package main",
"commit_choice": "direct",
})
- session.MakeRequest(t, req, http.StatusSeeOther)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.NotEmpty(t, test.RedirectURL(resp))
// let gitea calculate language stats
time.Sleep(time.Second)
diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go
index 6b42b83bc5..ec6a3a3b57 100644
--- a/tests/integration/api_repo_lfs_test.go
+++ b/tests/integration/api_repo_lfs_test.go
@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@@ -226,9 +227,7 @@ func TestAPILFSBatch(t *testing.T) {
t.Run("FileTooBig", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
-
- oldMaxFileSize := setting.LFS.MaxFileSize
- setting.LFS.MaxFileSize = 2
+ defer test.MockVariableValue(&setting.LFS.MaxFileSize, 2)()
req := newRequest(t, &lfs.BatchRequest{
Operation: "upload",
@@ -243,8 +242,6 @@ func TestAPILFSBatch(t *testing.T) {
assert.NotNil(t, br.Objects[0].Error)
assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code)
assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message)
-
- setting.LFS.MaxFileSize = oldMaxFileSize
})
t.Run("AddMeta", func(t *testing.T) {
diff --git a/tests/integration/api_repo_license_test.go b/tests/integration/api_repo_license_test.go
index 52d3085694..fb4450a2bd 100644
--- a/tests/integration/api_repo_license_test.go
+++ b/tests/integration/api_repo_license_test.go
@@ -12,12 +12,13 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
var testLicenseContent = `
-Copyright (c) 2024 Gitea
+Copyright (c) 2024 Gitea
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -48,7 +49,8 @@ func TestAPIRepoLicense(t *testing.T) {
"content": testLicenseContent,
"commit_choice": "direct",
})
- session.MakeRequest(t, req, http.StatusSeeOther)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.NotEmpty(t, test.RedirectURL(resp))
// let gitea update repo license
time.Sleep(time.Second)
diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go
index 672c2a2c8b..a2c3a467c6 100644
--- a/tests/integration/api_repo_test.go
+++ b/tests/integration/api_repo_test.go
@@ -586,7 +586,7 @@ func TestAPIRepoTransfer(t *testing.T) {
// cleanup
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
- _ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID)
+ _ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, repo.ID)
}
func transfer(t *testing.T) *repo_model.Repository {
diff --git a/tests/integration/api_repo_variables_test.go b/tests/integration/api_repo_variables_test.go
index 7847962b07..b5c88af279 100644
--- a/tests/integration/api_repo_variables_test.go
+++ b/tests/integration/api_repo_variables_test.go
@@ -35,11 +35,11 @@ func TestAPIRepoVariables(t *testing.T) {
},
{
Name: "_",
- ExpectedStatus: http.StatusNoContent,
+ ExpectedStatus: http.StatusCreated,
},
{
Name: "TEST_VAR",
- ExpectedStatus: http.StatusNoContent,
+ ExpectedStatus: http.StatusCreated,
},
{
Name: "test_var",
@@ -81,7 +81,7 @@ func TestAPIRepoVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val",
}).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
+ MakeRequest(t, req, http.StatusCreated)
cases := []struct {
Name string
@@ -138,7 +138,7 @@ func TestAPIRepoVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val",
}).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
+ MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent)
diff --git a/tests/integration/api_user_variables_test.go b/tests/integration/api_user_variables_test.go
index 367b83e7d4..d430c9e21d 100644
--- a/tests/integration/api_user_variables_test.go
+++ b/tests/integration/api_user_variables_test.go
@@ -29,11 +29,11 @@ func TestAPIUserVariables(t *testing.T) {
},
{
Name: "_",
- ExpectedStatus: http.StatusNoContent,
+ ExpectedStatus: http.StatusCreated,
},
{
Name: "TEST_VAR",
- ExpectedStatus: http.StatusNoContent,
+ ExpectedStatus: http.StatusCreated,
},
{
Name: "test_var",
@@ -75,7 +75,7 @@ func TestAPIUserVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val",
}).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
+ MakeRequest(t, req, http.StatusCreated)
cases := []struct {
Name string
@@ -132,7 +132,7 @@ func TestAPIUserVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val",
}).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
+ MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent)
diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go
index c00e88b88b..24f0c03bed 100644
--- a/tests/integration/auth_ldap_test.go
+++ b/tests/integration/auth_ldap_test.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
@@ -437,8 +438,8 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) {
Name: gitLDAPUser.UserName,
})
usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
- UserID: user.ID,
- IncludePrivate: true,
+ UserID: user.ID,
+ IncludeVisibility: structs.VisibleTypePrivate,
})
assert.NoError(t, err)
allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID)
diff --git a/tests/integration/change_default_branch_test.go b/tests/integration/change_default_branch_test.go
index 729eb1e4ce..9b61cff9fd 100644
--- a/tests/integration/change_default_branch_test.go
+++ b/tests/integration/change_default_branch_test.go
@@ -6,12 +6,16 @@ package integration
import (
"fmt"
"net/http"
+ "strconv"
"testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
)
func TestChangeDefaultBranch(t *testing.T) {
@@ -38,3 +42,96 @@ func TestChangeDefaultBranch(t *testing.T) {
})
session.MakeRequest(t, req, http.StatusNotFound)
}
+
+func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]git.DivergeObject) {
+ req := NewRequest(t, "GET", branchesURL)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ htmlDoc := NewHTMLParser(t, resp.Body)
+
+ branchNodes := htmlDoc.doc.Find(".branch-name").Nodes
+ branchNames := []string{}
+ for _, node := range branchNodes {
+ branchNames = append(branchNames, node.FirstChild.Data)
+ }
+
+ expectBranchCount := len(expectedBranchToDivergence)
+
+ assert.Len(t, branchNames, expectBranchCount+1)
+ assert.Equal(t, expectedDefaultBranch, branchNames[0])
+
+ allCountBehindNodes := htmlDoc.doc.Find(".count-behind").Nodes
+ allCountAheadNodes := htmlDoc.doc.Find(".count-ahead").Nodes
+
+ assert.Len(t, allCountAheadNodes, expectBranchCount)
+ assert.Len(t, allCountBehindNodes, expectBranchCount)
+
+ for i := range expectBranchCount {
+ branchName := branchNames[i+1]
+ assert.Contains(t, expectedBranchToDivergence, branchName)
+
+ expectedCountAhead := expectedBranchToDivergence[branchName].Ahead
+ expectedCountBehind := expectedBranchToDivergence[branchName].Behind
+ countAhead, err := strconv.Atoi(allCountAheadNodes[i].FirstChild.Data)
+ assert.NoError(t, err)
+ countBehind, err := strconv.Atoi(allCountBehindNodes[i].FirstChild.Data)
+ assert.NoError(t, err)
+
+ assert.Equal(t, expectedCountAhead, countAhead)
+ assert.Equal(t, expectedCountBehind, countBehind)
+ }
+}
+
+func TestChangeDefaultBranchDivergence(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+
+ session := loginUser(t, owner.Name)
+ branchesURL := fmt.Sprintf("/%s/%s/branches", owner.Name, repo.Name)
+ settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name)
+
+ // check branch divergence before switching default branch
+ expectedBranchToDivergenceBefore := map[string]git.DivergeObject{
+ "not-signed": {
+ Ahead: 0,
+ Behind: 0,
+ },
+ "good-sign-not-yet-validated": {
+ Ahead: 0,
+ Behind: 1,
+ },
+ "good-sign": {
+ Ahead: 1,
+ Behind: 3,
+ },
+ }
+ checkDivergence(t, session, branchesURL, "master", expectedBranchToDivergenceBefore)
+
+ // switch default branch
+ newDefaultBranch := "good-sign-not-yet-validated"
+ csrf := GetUserCSRFToken(t, session)
+ req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{
+ "_csrf": csrf,
+ "action": "default_branch",
+ "branch": newDefaultBranch,
+ })
+ session.MakeRequest(t, req, http.StatusSeeOther)
+
+ // check branch divergence after switching default branch
+ expectedBranchToDivergenceAfter := map[string]git.DivergeObject{
+ "master": {
+ Ahead: 1,
+ Behind: 0,
+ },
+ "not-signed": {
+ Ahead: 1,
+ Behind: 0,
+ },
+ "good-sign": {
+ Ahead: 1,
+ Behind: 2,
+ },
+ }
+ checkDivergence(t, session, branchesURL, newDefaultBranch, expectedBranchToDivergenceAfter)
+}
diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go
index 61f11c58b0..3878302ef0 100644
--- a/tests/integration/cmd_keys_test.go
+++ b/tests/integration/cmd_keys_test.go
@@ -13,7 +13,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
- "github.com/urfave/cli/v2"
+ "github.com/urfave/cli/v3"
)
func Test_CmdKeys(t *testing.T) {
@@ -36,18 +36,21 @@ func Test_CmdKeys(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- out := new(bytes.Buffer)
- app := cli.NewApp()
- app.Writer = out
- app.Commands = []*cli.Command{cmd.CmdKeys}
+ var stdout, stderr bytes.Buffer
+ app := &cli.Command{
+ Writer: &stdout,
+ ErrWriter: &stderr,
+ Commands: []*cli.Command{cmd.CmdKeys},
+ }
cmd.CmdKeys.HideHelp = true
- err := app.Run(append([]string{"prog"}, tt.args...))
+ err := app.Run(t.Context(), append([]string{"prog"}, tt.args...))
if tt.wantErr {
assert.Error(t, err)
+ assert.Equal(t, tt.expectedOutput, stderr.String())
} else {
assert.NoError(t, err)
+ assert.Equal(t, tt.expectedOutput, stdout.String())
}
- assert.Equal(t, tt.expectedOutput, out.String())
})
}
})
diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go
index a5936d86de..ac47ed0094 100644
--- a/tests/integration/editor_test.go
+++ b/tests/integration/editor_test.go
@@ -7,6 +7,7 @@ import (
"bytes"
"fmt"
"io"
+ "maps"
"mime/multipart"
"net/http"
"net/http/httptest"
@@ -19,289 +20,278 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-func TestCreateFile(t *testing.T) {
+func TestEditor(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
- session := loginUser(t, "user2")
- testCreateFile(t, session, "user2", "repo1", "master", "test.txt", "Content")
+ sessionUser2 := loginUser(t, "user2")
+ t.Run("EditFileNotAllowed", testEditFileNotAllowed)
+ t.Run("DiffPreview", testEditorDiffPreview)
+ t.Run("CreateFile", testEditorCreateFile)
+ t.Run("EditFile", func(t *testing.T) {
+ testEditFile(t, sessionUser2, "user2", "repo1", "master", "README.md", "Hello, World (direct)\n")
+ testEditFileToNewBranch(t, sessionUser2, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (commit-to-new-branch)\n")
+ })
+ t.Run("PatchFile", testEditorPatchFile)
+ t.Run("DeleteFile", func(t *testing.T) {
+ viewLink := "/user2/repo1/src/branch/branch2/README.md"
+ sessionUser2.MakeRequest(t, NewRequest(t, "GET", viewLink), http.StatusOK)
+ testEditorActionPostRequest(t, sessionUser2, "/user2/repo1/_delete/branch2/README.md", map[string]string{"commit_choice": "direct"})
+ sessionUser2.MakeRequest(t, NewRequest(t, "GET", viewLink), http.StatusNotFound)
+ })
+ t.Run("ForkToEditFile", func(t *testing.T) {
+ testForkToEditFile(t, loginUser(t, "user4"), "user4", "user2", "repo1", "master", "README.md")
+ })
+ t.Run("WebGitCommitEmail", testEditorWebGitCommitEmail)
+ t.Run("ProtectedBranch", testEditorProtectedBranch)
})
}
-func testCreateFile(t *testing.T, session *TestSession, user, repo, branch, filePath, content string) *httptest.ResponseRecorder {
- // Request editor page
- newURL := fmt.Sprintf("/%s/%s/_new/%s/", user, repo, branch)
- req := NewRequest(t, "GET", newURL)
- resp := session.MakeRequest(t, req, http.StatusOK)
-
- doc := NewHTMLParser(t, resp.Body)
- lastCommit := doc.GetInputValueByName("last_commit")
- assert.NotEmpty(t, lastCommit)
+func testEditorCreateFile(t *testing.T) {
+ session := loginUser(t, "user2")
+ testCreateFile(t, session, "user2", "repo1", "master", "test.txt", "Content")
+ testEditorActionPostRequestError(t, session, "/user2/repo1/_new/master/", map[string]string{
+ "tree_path": "test.txt",
+ "commit_choice": "direct",
+ "new_branch_name": "master",
+ }, `A file named "test.txt" already exists in this repository.`)
+ testEditorActionPostRequestError(t, session, "/user2/repo1/_new/master/", map[string]string{
+ "tree_path": "test.txt",
+ "commit_choice": "commit-to-new-branch",
+ "new_branch_name": "master",
+ }, `Branch "master" already exists in this repository.`)
+}
- // Save new file to master branch
- req = NewRequestWithValues(t, "POST", newURL, map[string]string{
- "_csrf": doc.GetCSRF(),
- "last_commit": lastCommit,
+func testCreateFile(t *testing.T, session *TestSession, user, repo, branch, filePath, content string) {
+ testEditorActionEdit(t, session, user, repo, "_new", branch, "", map[string]string{
"tree_path": filePath,
"content": content,
"commit_choice": "direct",
})
- return session.MakeRequest(t, req, http.StatusSeeOther)
}
-func TestCreateFileOnProtectedBranch(t *testing.T) {
- onGiteaRun(t, func(t *testing.T, u *url.URL) {
- session := loginUser(t, "user2")
-
- csrf := GetUserCSRFToken(t, session)
- // Change master branch to protected
- req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
- "_csrf": csrf,
- "rule_name": "master",
- "enable_push": "true",
- })
- session.MakeRequest(t, req, http.StatusSeeOther)
- // Check if master branch has been locked successfully
- flashMsg := session.GetCookieFlashMessage()
- assert.Equal(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg)
-
- // Request editor page
- req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
- resp := session.MakeRequest(t, req, http.StatusOK)
-
- doc := NewHTMLParser(t, resp.Body)
- lastCommit := doc.GetInputValueByName("last_commit")
- assert.NotEmpty(t, lastCommit)
-
- // Save new file to master branch
- req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
- "_csrf": doc.GetCSRF(),
- "last_commit": lastCommit,
- "tree_path": "test.txt",
- "content": "Content",
- "commit_choice": "direct",
- })
-
- resp = session.MakeRequest(t, req, http.StatusOK)
- // Check body for error message
- assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch &#34;master&#34;.")
-
- // remove the protected branch
- csrf = GetUserCSRFToken(t, session)
-
- // Change master branch to protected
- req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/1/delete", map[string]string{
- "_csrf": csrf,
- })
-
- resp = session.MakeRequest(t, req, http.StatusOK)
-
- res := make(map[string]string)
- assert.NoError(t, json.NewDecoder(resp.Body).Decode(&res))
- assert.Equal(t, "/user2/repo1/settings/branches", res["redirect"])
-
- // Check if master branch has been locked successfully
- flashMsg = session.GetCookieFlashMessage()
- assert.Equal(t, `Removing branch protection rule "1" failed.`, flashMsg.ErrorMsg)
+func testEditorProtectedBranch(t *testing.T) {
+ session := loginUser(t, "user2")
+ // Change the "master" branch to "protected"
+ req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "rule_name": "master",
+ "enable_push": "true",
})
+ session.MakeRequest(t, req, http.StatusSeeOther)
+ flashMsg := session.GetCookieFlashMessage()
+ assert.Equal(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg)
+
+ // Try to commit a file to the "master" branch and it should fail
+ resp := testEditorActionPostRequest(t, session, "/user2/repo1/_new/master/", map[string]string{"tree_path": "test-protected-branch.txt", "commit_choice": "direct"})
+ assert.Equal(t, http.StatusBadRequest, resp.Code)
+ assert.Equal(t, `Cannot commit to protected branch "master".`, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
}
-func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) *httptest.ResponseRecorder {
- // Get to the 'edit this file' page
- req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath))
+func testEditorActionPostRequest(t *testing.T, session *TestSession, requestPath string, params map[string]string) *httptest.ResponseRecorder {
+ req := NewRequest(t, "GET", requestPath)
resp := session.MakeRequest(t, req, http.StatusOK)
-
htmlDoc := NewHTMLParser(t, resp.Body)
- lastCommit := htmlDoc.GetInputValueByName("last_commit")
- assert.NotEmpty(t, lastCommit)
-
- // Submit the edits
- req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath),
- map[string]string{
- "_csrf": htmlDoc.GetCSRF(),
- "last_commit": lastCommit,
- "tree_path": filePath,
- "content": newContent,
- "commit_choice": "direct",
- },
- )
- session.MakeRequest(t, req, http.StatusSeeOther)
+ form := map[string]string{
+ "_csrf": htmlDoc.GetCSRF(),
+ "last_commit": htmlDoc.GetInputValueByName("last_commit"),
+ }
+ maps.Copy(form, params)
+ req = NewRequestWithValues(t, "POST", requestPath, form)
+ return session.MakeRequest(t, req, NoExpectedStatus)
+}
- // Verify the change
- req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", branch, filePath))
- resp = session.MakeRequest(t, req, http.StatusOK)
- assert.Equal(t, newContent, resp.Body.String())
+func testEditorActionPostRequestError(t *testing.T, session *TestSession, requestPath string, params map[string]string, errorMessage string) {
+ resp := testEditorActionPostRequest(t, session, requestPath, params)
+ assert.Equal(t, http.StatusBadRequest, resp.Code)
+ assert.Equal(t, errorMessage, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
+}
+func testEditorActionEdit(t *testing.T, session *TestSession, user, repo, editorAction, branch, filePath string, params map[string]string) *httptest.ResponseRecorder {
+ params["tree_path"] = util.IfZero(params["tree_path"], filePath)
+ newBranchName := util.Iif(params["commit_choice"] == "direct", branch, params["new_branch_name"])
+ resp := testEditorActionPostRequest(t, session, fmt.Sprintf("/%s/%s/%s/%s/%s", user, repo, editorAction, branch, filePath), params)
+ assert.Equal(t, http.StatusOK, resp.Code)
+ assert.NotEmpty(t, test.RedirectURL(resp))
+ req := NewRequest(t, "GET", path.Join(user, repo, "raw/branch", newBranchName, params["tree_path"]))
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.Equal(t, params["content"], resp.Body.String())
return resp
}
-func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, branch, targetBranch, filePath, newContent string) *httptest.ResponseRecorder {
- // Get to the 'edit this file' page
- req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath))
- resp := session.MakeRequest(t, req, http.StatusOK)
-
- htmlDoc := NewHTMLParser(t, resp.Body)
- lastCommit := htmlDoc.GetInputValueByName("last_commit")
- assert.NotEmpty(t, lastCommit)
-
- // Submit the edits
- req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath),
- map[string]string{
- "_csrf": htmlDoc.GetCSRF(),
- "last_commit": lastCommit,
- "tree_path": filePath,
- "content": newContent,
- "commit_choice": "commit-to-new-branch",
- "new_branch_name": targetBranch,
- },
- )
- session.MakeRequest(t, req, http.StatusSeeOther)
-
- // Verify the change
- req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", targetBranch, filePath))
- resp = session.MakeRequest(t, req, http.StatusOK)
- assert.Equal(t, newContent, resp.Body.String())
+func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) {
+ testEditorActionEdit(t, session, user, repo, "_edit", branch, filePath, map[string]string{
+ "content": newContent,
+ "commit_choice": "direct",
+ })
+}
- return resp
+func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, branch, targetBranch, filePath, newContent string) {
+ testEditorActionEdit(t, session, user, repo, "_edit", branch, filePath, map[string]string{
+ "content": newContent,
+ "commit_choice": "commit-to-new-branch",
+ "new_branch_name": targetBranch,
+ })
}
-func TestEditFile(t *testing.T) {
- onGiteaRun(t, func(t *testing.T, u *url.URL) {
- session := loginUser(t, "user2")
- testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n")
+func testEditorDiffPreview(t *testing.T) {
+ session := loginUser(t, "user2")
+ req := NewRequestWithValues(t, "POST", "/user2/repo1/_preview/master/README.md", map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "content": "Hello, World (Edited)\n",
})
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.Contains(t, resp.Body.String(), `<span class="added-code">Hello, World (Edited)</span>`)
}
-func TestEditFileToNewBranch(t *testing.T) {
- onGiteaRun(t, func(t *testing.T, u *url.URL) {
- session := loginUser(t, "user2")
- testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n")
+func testEditorPatchFile(t *testing.T) {
+ session := loginUser(t, "user2")
+ pathContentCommon := `diff --git a/patch-file-1.txt b/patch-file-1.txt
+new file mode 100644
+index 0000000000..aaaaaaaaaa
+--- /dev/null
++++ b/patch-file-1.txt
+@@ -0,0 +1 @@
++`
+ testEditorActionPostRequest(t, session, "/user2/repo1/_diffpatch/master/", map[string]string{
+ "content": pathContentCommon + "patched content\n",
+ "commit_choice": "commit-to-new-branch",
+ "new_branch_name": "patched-branch",
+ })
+ resp := MakeRequest(t, NewRequest(t, "GET", "/user2/repo1/raw/branch/patched-branch/patch-file-1.txt"), http.StatusOK)
+ assert.Equal(t, "patched content\n", resp.Body.String())
+
+ // patch again, it should fail
+ resp = testEditorActionPostRequest(t, session, "/user2/repo1/_diffpatch/patched-branch/", map[string]string{
+ "content": pathContentCommon + "another patched content\n",
+ "commit_choice": "commit-to-new-branch",
+ "new_branch_name": "patched-branch-1",
})
+ assert.Equal(t, "Unable to apply patch", test.ParseJSONError(resp.Body.Bytes()).ErrorMessage)
}
-func TestWebGitCommitEmail(t *testing.T) {
- onGiteaRun(t, func(t *testing.T, _ *url.URL) {
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- require.True(t, user.KeepEmailPrivate)
-
- repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
- gitRepo, _ := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
- defer gitRepo.Close()
- getLastCommit := func(t *testing.T) *git.Commit {
- c, err := gitRepo.GetBranchCommit("master")
- require.NoError(t, err)
- return c
+func testEditorWebGitCommitEmail(t *testing.T) {
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ require.True(t, user.KeepEmailPrivate)
+
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ gitRepo, _ := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
+ defer gitRepo.Close()
+ getLastCommit := func(t *testing.T) *git.Commit {
+ c, err := gitRepo.GetBranchCommit("master")
+ require.NoError(t, err)
+ return c
+ }
+
+ session := loginUser(t, user.Name)
+
+ makeReq := func(t *testing.T, link string, params map[string]string, expectedUserName, expectedEmail string) *httptest.ResponseRecorder {
+ lastCommit := getLastCommit(t)
+ params["_csrf"] = GetUserCSRFToken(t, session)
+ params["last_commit"] = lastCommit.ID.String()
+ params["commit_choice"] = "direct"
+ req := NewRequestWithValues(t, "POST", link, params)
+ resp := session.MakeRequest(t, req, NoExpectedStatus)
+ newCommit := getLastCommit(t)
+ if expectedUserName == "" {
+ require.Equal(t, lastCommit.ID.String(), newCommit.ID.String())
+ respErr := test.ParseJSONError(resp.Body.Bytes())
+ assert.Equal(t, translation.NewLocale("en-US").TrString("repo.editor.invalid_commit_email"), respErr.ErrorMessage)
+ } else {
+ require.NotEqual(t, lastCommit.ID.String(), newCommit.ID.String())
+ assert.Equal(t, expectedUserName, newCommit.Author.Name)
+ assert.Equal(t, expectedEmail, newCommit.Author.Email)
+ assert.Equal(t, expectedUserName, newCommit.Committer.Name)
+ assert.Equal(t, expectedEmail, newCommit.Committer.Email)
}
+ return resp
+ }
+
+ uploadFile := func(t *testing.T, name, content string) string {
+ body := &bytes.Buffer{}
+ uploadForm := multipart.NewWriter(body)
+ file, _ := uploadForm.CreateFormFile("file", name)
+ _, _ = io.Copy(file, strings.NewReader(content))
+ _ = uploadForm.WriteField("_csrf", GetUserCSRFToken(t, session))
+ _ = uploadForm.Close()
+
+ req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body)
+ req.Header.Add("Content-Type", uploadForm.FormDataContentType())
+ resp := session.MakeRequest(t, req, http.StatusOK)
- session := loginUser(t, user.Name)
-
- makeReq := func(t *testing.T, link string, params map[string]string, expectedUserName, expectedEmail string) *httptest.ResponseRecorder {
- lastCommit := getLastCommit(t)
- params["_csrf"] = GetUserCSRFToken(t, session)
- params["last_commit"] = lastCommit.ID.String()
- params["commit_choice"] = "direct"
- req := NewRequestWithValues(t, "POST", link, params)
- resp := session.MakeRequest(t, req, NoExpectedStatus)
- newCommit := getLastCommit(t)
- if expectedUserName == "" {
- require.Equal(t, lastCommit.ID.String(), newCommit.ID.String())
- htmlDoc := NewHTMLParser(t, resp.Body)
- errMsg := htmlDoc.doc.Find(".ui.negative.message").Text()
- assert.Contains(t, errMsg, translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_email"))
- } else {
- require.NotEqual(t, lastCommit.ID.String(), newCommit.ID.String())
- assert.Equal(t, expectedUserName, newCommit.Author.Name)
- assert.Equal(t, expectedEmail, newCommit.Author.Email)
- assert.Equal(t, expectedUserName, newCommit.Committer.Name)
- assert.Equal(t, expectedEmail, newCommit.Committer.Email)
- }
- return resp
- }
+ respMap := map[string]string{}
+ DecodeJSON(t, resp, &respMap)
+ return respMap["uuid"]
+ }
+
+ t.Run("EmailInactive", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 35, UID: user.ID})
+ require.False(t, email.IsActivated)
+ makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{
+ "tree_path": "README.md",
+ "content": "test content",
+ "commit_email": email.Email,
+ }, "", "")
+ })
- uploadFile := func(t *testing.T, name, content string) string {
- body := &bytes.Buffer{}
- uploadForm := multipart.NewWriter(body)
- file, _ := uploadForm.CreateFormFile("file", name)
- _, _ = io.Copy(file, strings.NewReader(content))
- _ = uploadForm.WriteField("_csrf", GetUserCSRFToken(t, session))
- _ = uploadForm.Close()
-
- req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body)
- req.Header.Add("Content-Type", uploadForm.FormDataContentType())
- resp := session.MakeRequest(t, req, http.StatusOK)
-
- respMap := map[string]string{}
- DecodeJSON(t, resp, &respMap)
- return respMap["uuid"]
- }
+ t.Run("EmailInvalid", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true})
+ require.NotEqual(t, email.UID, user.ID)
+ makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{
+ "tree_path": "README.md",
+ "content": "test content",
+ "commit_email": email.Email,
+ }, "", "")
+ })
- t.Run("EmailInactive", func(t *testing.T) {
+ testWebGit := func(t *testing.T, linkForKeepPrivate string, paramsForKeepPrivate map[string]string, linkForChosenEmail string, paramsForChosenEmail map[string]string) (resp1, resp2 *httptest.ResponseRecorder) {
+ t.Run("DefaultEmailKeepPrivate", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
- email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 35, UID: user.ID})
- require.False(t, email.IsActivated)
- makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{
- "tree_path": "README.md",
- "content": "test content",
- "commit_email": email.Email,
- }, "", "")
+ paramsForKeepPrivate["commit_email"] = ""
+ resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "user2@noreply.example.org")
})
-
- t.Run("EmailInvalid", func(t *testing.T) {
+ t.Run("ChooseEmail", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
- email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true})
- require.NotEqual(t, email.UID, user.ID)
- makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{
- "tree_path": "README.md",
- "content": "test content",
- "commit_email": email.Email,
- }, "", "")
- })
-
- testWebGit := func(t *testing.T, linkForKeepPrivate string, paramsForKeepPrivate map[string]string, linkForChosenEmail string, paramsForChosenEmail map[string]string) (resp1, resp2 *httptest.ResponseRecorder) {
- t.Run("DefaultEmailKeepPrivate", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
- paramsForKeepPrivate["commit_email"] = ""
- resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "user2@noreply.example.org")
- })
- t.Run("ChooseEmail", func(t *testing.T) {
- defer tests.PrintCurrentTest(t)()
- paramsForChosenEmail["commit_email"] = "user2@example.com"
- resp2 = makeReq(t, linkForChosenEmail, paramsForChosenEmail, "User Two", "user2@example.com")
- })
- return resp1, resp2
- }
-
- t.Run("Edit", func(t *testing.T) {
- testWebGit(t,
- "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for keep private"},
- "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for chosen email"},
- )
+ paramsForChosenEmail["commit_email"] = "user2@example.com"
+ resp2 = makeReq(t, linkForChosenEmail, paramsForChosenEmail, "User Two", "user2@example.com")
})
+ return resp1, resp2
+ }
+
+ t.Run("Edit", func(t *testing.T) {
+ testWebGit(t,
+ "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for keep private"},
+ "/user2/repo1/_edit/master/README.md", map[string]string{"tree_path": "README.md", "content": "for chosen email"},
+ )
+ })
- t.Run("UploadDelete", func(t *testing.T) {
- file1UUID := uploadFile(t, "file1", "File 1")
- file2UUID := uploadFile(t, "file2", "File 2")
- testWebGit(t,
- "/user2/repo1/_upload/master", map[string]string{"files": file1UUID},
- "/user2/repo1/_upload/master", map[string]string{"files": file2UUID},
- )
- testWebGit(t,
- "/user2/repo1/_delete/master/file1", map[string]string{},
- "/user2/repo1/_delete/master/file2", map[string]string{},
- )
- })
+ t.Run("UploadDelete", func(t *testing.T) {
+ file1UUID := uploadFile(t, "file1", "File 1")
+ file2UUID := uploadFile(t, "file2", "File 2")
+ testWebGit(t,
+ "/user2/repo1/_upload/master", map[string]string{"files": file1UUID},
+ "/user2/repo1/_upload/master", map[string]string{"files": file2UUID},
+ )
+ testWebGit(t,
+ "/user2/repo1/_delete/master/file1", map[string]string{},
+ "/user2/repo1/_delete/master/file2", map[string]string{},
+ )
+ })
- t.Run("ApplyPatchCherryPick", func(t *testing.T) {
- testWebGit(t,
- "/user2/repo1/_diffpatch/master", map[string]string{
- "tree_path": "__dummy__",
- "content": `diff --git a/patch-file-1.txt b/patch-file-1.txt
+ t.Run("ApplyPatchCherryPick", func(t *testing.T) {
+ testWebGit(t,
+ "/user2/repo1/_diffpatch/master", map[string]string{
+ "tree_path": "__dummy__",
+ "content": `diff --git a/patch-file-1.txt b/patch-file-1.txt
new file mode 100644
index 0000000000..aaaaaaaaaa
--- /dev/null
@@ -309,10 +299,10 @@ index 0000000000..aaaaaaaaaa
@@ -0,0 +1 @@
+File 1
`,
- },
- "/user2/repo1/_diffpatch/master", map[string]string{
- "tree_path": "__dummy__",
- "content": `diff --git a/patch-file-2.txt b/patch-file-2.txt
+ },
+ "/user2/repo1/_diffpatch/master", map[string]string{
+ "tree_path": "__dummy__",
+ "content": `diff --git a/patch-file-2.txt b/patch-file-2.txt
new file mode 100644
index 0000000000..bbbbbbbbbb
--- /dev/null
@@ -320,20 +310,146 @@ index 0000000000..bbbbbbbbbb
@@ -0,0 +1 @@
+File 2
`,
- },
- )
-
- commit1, err := gitRepo.GetCommitByPath("patch-file-1.txt")
- require.NoError(t, err)
- commit2, err := gitRepo.GetCommitByPath("patch-file-2.txt")
- require.NoError(t, err)
- resp1, _ := testWebGit(t,
- "/user2/repo1/_cherrypick/"+commit1.ID.String()+"/master", map[string]string{"revert": "true"},
- "/user2/repo1/_cherrypick/"+commit2.ID.String()+"/master", map[string]string{"revert": "true"},
- )
-
- // By the way, test the "cherrypick" page: a successful revert redirects to the main branch
- assert.Equal(t, "/user2/repo1/src/branch/master", resp1.Header().Get("Location"))
- })
+ },
+ )
+
+ commit1, err := gitRepo.GetCommitByPath("patch-file-1.txt")
+ require.NoError(t, err)
+ commit2, err := gitRepo.GetCommitByPath("patch-file-2.txt")
+ require.NoError(t, err)
+ resp1, _ := testWebGit(t,
+ "/user2/repo1/_cherrypick/"+commit1.ID.String()+"/master", map[string]string{"revert": "true"},
+ "/user2/repo1/_cherrypick/"+commit2.ID.String()+"/master", map[string]string{"revert": "true"},
+ )
+
+ // By the way, test the "cherrypick" page: a successful revert redirects to the main branch
+ assert.Equal(t, "/user2/repo1/src/branch/master", test.RedirectURL(resp1))
})
}
+
+func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, branch, filePath string) {
+ forkToEdit := func(t *testing.T, session *TestSession, owner, repo, operation, branch, filePath string) {
+ // visit the base repo, see the "Add File" button
+ req := NewRequest(t, "GET", path.Join(owner, repo))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+ AssertHTMLElement(t, htmlDoc, ".repo-add-file", 1)
+
+ // attempt to edit a file, see the guideline page
+ req = NewRequest(t, "GET", path.Join(owner, repo, operation, branch, filePath))
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.Contains(t, resp.Body.String(), "Fork Repository to Propose Changes")
+
+ // fork the repository
+ req = NewRequestWithValues(t, "POST", path.Join(owner, repo, "_fork", branch), map[string]string{"_csrf": GetUserCSRFToken(t, session)})
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.JSONEq(t, `{"redirect":""}`, resp.Body.String())
+ }
+
+ t.Run("ForkButArchived", func(t *testing.T) {
+ // Fork repository because we can't edit it
+ forkToEdit(t, session, owner, repo, "_edit", branch, filePath)
+
+ // Archive the repository
+ req := NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"),
+ map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "repo_name": repo,
+ "action": "archive",
+ },
+ )
+ session.MakeRequest(t, req, http.StatusSeeOther)
+
+ // Check editing archived repository is disabled
+ req = NewRequest(t, "GET", path.Join(owner, repo, "_edit", branch, filePath)).SetHeader("Accept", "text/html")
+ resp := session.MakeRequest(t, req, http.StatusNotFound)
+ assert.Contains(t, resp.Body.String(), "You have forked this repository but your fork is not editable.")
+
+ // Unfork the repository
+ req = NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"),
+ map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "repo_name": repo,
+ "action": "convert_fork",
+ },
+ )
+ session.MakeRequest(t, req, http.StatusSeeOther)
+ })
+
+ // Fork repository again, and check the existence of the forked repo with unique name
+ forkToEdit(t, session, owner, repo, "_edit", branch, filePath)
+ session.MakeRequest(t, NewRequestf(t, "GET", "/%s/%s-1", user, repo), http.StatusOK)
+
+ t.Run("CheckBaseRepoForm", func(t *testing.T) {
+ // the base repo's edit form should have the correct action and upload links (pointing to the forked repo)
+ req := NewRequest(t, "GET", path.Join(owner, repo, "_upload", branch, filePath)+"?foo=bar")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+
+ uploadForm := htmlDoc.doc.Find(".form-fetch-action")
+ formAction := uploadForm.AttrOr("action", "")
+ assert.Equal(t, fmt.Sprintf("/%s/%s-1/_upload/%s/%s?from_base_branch=%s&foo=bar", user, repo, branch, filePath, branch), formAction)
+ uploadLink := uploadForm.Find(".dropzone").AttrOr("data-link-url", "")
+ assert.Equal(t, fmt.Sprintf("/%s/%s-1/upload-file", user, repo), uploadLink)
+ newBranchName := uploadForm.Find("input[name=new_branch_name]").AttrOr("value", "")
+ assert.Equal(t, user+"-patch-1", newBranchName)
+ commitChoice := uploadForm.Find("input[name=commit_choice][checked]").AttrOr("value", "")
+ assert.Equal(t, "commit-to-new-branch", commitChoice)
+ lastCommit := uploadForm.Find("input[name=last_commit]").AttrOr("value", "")
+ assert.NotEmpty(t, lastCommit)
+ })
+
+ t.Run("ViewBaseEditFormAndCommitToFork", func(t *testing.T) {
+ req := NewRequest(t, "GET", path.Join(owner, repo, "_edit", branch, filePath))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+ editRequestForm := map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "last_commit": htmlDoc.GetInputValueByName("last_commit"),
+ "tree_path": filePath,
+ "content": "new content in fork",
+ "commit_choice": "commit-to-new-branch",
+ }
+ // change a file in the forked repo with existing branch name (should fail)
+ editRequestForm["new_branch_name"] = "master"
+ req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s-1/_edit/%s/%s?from_base_branch=%s", user, repo, branch, filePath, branch), editRequestForm)
+ resp = session.MakeRequest(t, req, http.StatusBadRequest)
+ respJSON := test.ParseJSONError(resp.Body.Bytes())
+ assert.Equal(t, `Branch "master" already exists in your fork, please choose a new branch name.`, respJSON.ErrorMessage)
+
+ // change a file in the forked repo (should succeed)
+ newBranchName := htmlDoc.GetInputValueByName("new_branch_name")
+ editRequestForm["new_branch_name"] = newBranchName
+ req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s-1/_edit/%s/%s?from_base_branch=%s", user, repo, branch, filePath, branch), editRequestForm)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.Equal(t, fmt.Sprintf("/%s/%s/compare/%s...%s/%s-1:%s", owner, repo, branch, user, repo, newBranchName), test.RedirectURL(resp))
+
+ // check the file in the fork's branch is changed
+ req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s-1/src/branch/%s/%s", user, repo, newBranchName, filePath))
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.Contains(t, resp.Body.String(), "new content in fork")
+ })
+}
+
+func testEditFileNotAllowed(t *testing.T) {
+ sessionUser1 := loginUser(t, "user1") // admin, all access
+ sessionUser4 := loginUser(t, "user4")
+ // "_cherrypick" has a different route pattern, so skip its test
+ operations := []string{"_new", "_edit", "_delete", "_upload", "_diffpatch"}
+ for _, operation := range operations {
+ t.Run(operation, func(t *testing.T) {
+ // Branch does not exist
+ targetLink := path.Join("user2", "repo1", operation, "missing", "README.md")
+ sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound)
+
+ // Private repository
+ targetLink = path.Join("user2", "repo2", operation, "master", "Home.md")
+ sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusOK)
+ sessionUser4.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound)
+
+ // Empty repository
+ targetLink = path.Join("org41", "repo61", operation, "master", "README.md")
+ sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound)
+ })
+ }
+}
diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go
index f85d883cc7..6a8c70f12f 100644
--- a/tests/integration/empty_repo_test.go
+++ b/tests/integration/empty_repo_test.go
@@ -10,7 +10,6 @@ import (
"io"
"mime/multipart"
"net/http"
- "net/http/httptest"
"strings"
"testing"
@@ -23,13 +22,14 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) *httptest.ResponseRecorder {
+func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, treePath, content string) {
url := fmt.Sprintf("/%s/%s/_new/%s", user, repo, branch)
req := NewRequestWithValues(t, "POST", url, map[string]string{
"_csrf": GetUserCSRFToken(t, session),
@@ -37,7 +37,8 @@ func testAPINewFile(t *testing.T, session *TestSession, user, repo, branch, tree
"tree_path": treePath,
"content": content,
})
- return session.MakeRequest(t, req, http.StatusSeeOther)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.NotEmpty(t, test.RedirectURL(resp))
}
func TestEmptyRepo(t *testing.T) {
@@ -86,7 +87,7 @@ func TestEmptyRepoAddFile(t *testing.T) {
"content": "newly-added-test-file",
})
- resp = session.MakeRequest(t, req, http.StatusSeeOther)
+ resp = session.MakeRequest(t, req, http.StatusOK)
redirect := test.RedirectURL(resp)
assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/test-file.md", redirect)
@@ -100,22 +101,29 @@ func TestEmptyRepoAddFile(t *testing.T) {
assert.Contains(t, resp.Body.String(), "test-file.md")
// if the repo is in incorrect state, it should be able to self-heal (recover to correct state)
- user30EmptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
- user30EmptyRepo.IsEmpty = true
- user30EmptyRepo.DefaultBranch = "no-such"
- _, err := db.GetEngine(db.DefaultContext).ID(user30EmptyRepo.ID).Cols("is_empty", "default_branch").Update(user30EmptyRepo)
- require.NoError(t, err)
- user30EmptyRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
- assert.True(t, user30EmptyRepo.IsEmpty)
-
- req = NewRequest(t, "GET", "/user30/empty")
- resp = session.MakeRequest(t, req, http.StatusSeeOther)
- redirect = test.RedirectURL(resp)
- assert.Equal(t, "/user30/empty", redirect)
-
- req = NewRequest(t, "GET", "/user30/empty")
- resp = session.MakeRequest(t, req, http.StatusOK)
- assert.Contains(t, resp.Body.String(), "test-file.md")
+ testEmptyOrBrokenRecover := func(t *testing.T, isEmpty, isBroken bool) {
+ user30EmptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
+ user30EmptyRepo.IsEmpty = isEmpty
+ user30EmptyRepo.Status = util.Iif(isBroken, repo_model.RepositoryBroken, repo_model.RepositoryReady)
+ user30EmptyRepo.DefaultBranch = "no-such"
+ _, err := db.GetEngine(db.DefaultContext).ID(user30EmptyRepo.ID).Cols("is_empty", "status", "default_branch").Update(user30EmptyRepo)
+ require.NoError(t, err)
+ user30EmptyRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
+ assert.Equal(t, isEmpty, user30EmptyRepo.IsEmpty)
+ assert.Equal(t, isBroken, user30EmptyRepo.Status == repo_model.RepositoryBroken)
+
+ req = NewRequest(t, "GET", "/user30/empty")
+ resp = session.MakeRequest(t, req, http.StatusSeeOther)
+ redirect = test.RedirectURL(resp)
+ assert.Equal(t, "/user30/empty", redirect)
+
+ req = NewRequest(t, "GET", "/user30/empty")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.Contains(t, resp.Body.String(), "test-file.md")
+ }
+ testEmptyOrBrokenRecover(t, true, false)
+ testEmptyOrBrokenRecover(t, false, true)
+ testEmptyOrBrokenRecover(t, true, true)
}
func TestEmptyRepoUploadFile(t *testing.T) {
@@ -146,9 +154,9 @@ func TestEmptyRepoUploadFile(t *testing.T) {
"files": respMap["uuid"],
"tree_path": "",
})
- resp = session.MakeRequest(t, req, http.StatusSeeOther)
+ resp = session.MakeRequest(t, req, http.StatusOK)
redirect := test.RedirectURL(resp)
- assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/", redirect)
+ assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch, redirect)
req = NewRequest(t, "GET", redirect)
resp = session.MakeRequest(t, req, http.StatusOK)
diff --git a/tests/integration/ephemeral_actions_runner_deletion_test.go b/tests/integration/ephemeral_actions_runner_deletion_test.go
new file mode 100644
index 0000000000..40f8c643a8
--- /dev/null
+++ b/tests/integration/ephemeral_actions_runner_deletion_test.go
@@ -0,0 +1,77 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "testing"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/util"
+ repo_service "code.gitea.io/gitea/services/repository"
+ user_service "code.gitea.io/gitea/services/user"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEphemeralActionsRunnerDeletion(t *testing.T) {
+ t.Run("ByTaskCompletion", testEphemeralActionsRunnerDeletionByTaskCompletion)
+ t.Run("ByRepository", testEphemeralActionsRunnerDeletionByRepository)
+ t.Run("ByUser", testEphemeralActionsRunnerDeletionByUser)
+}
+
+// Test that the ephemeral runner is deleted when the task is finished
+func testEphemeralActionsRunnerDeletionByTaskCompletion(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ _, err := actions_model.GetRunnerByID(t.Context(), 34350)
+ assert.NoError(t, err)
+
+ task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52})
+ assert.Equal(t, actions_model.StatusRunning, task.Status)
+
+ task.Status = actions_model.StatusSuccess
+ err = actions_model.UpdateTask(t.Context(), task, "status")
+ assert.NoError(t, err)
+
+ _, err = actions_model.GetRunnerByID(t.Context(), 34350)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+}
+
+func testEphemeralActionsRunnerDeletionByRepository(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ _, err := actions_model.GetRunnerByID(t.Context(), 34350)
+ assert.NoError(t, err)
+
+ task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52})
+ assert.Equal(t, actions_model.StatusRunning, task.Status)
+
+ err = repo_service.DeleteRepositoryDirectly(t.Context(), task.RepoID, true)
+ assert.NoError(t, err)
+
+ _, err = actions_model.GetRunnerByID(t.Context(), 34350)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+}
+
+// Test that the ephemeral runner is deleted when a user is deleted
+func testEphemeralActionsRunnerDeletionByUser(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ _, err := actions_model.GetRunnerByID(t.Context(), 34350)
+ assert.NoError(t, err)
+
+ task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52})
+ assert.Equal(t, actions_model.StatusRunning, task.Status)
+
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+
+ err = user_service.DeleteUser(t.Context(), user, true)
+ assert.NoError(t, err)
+
+ _, err = actions_model.GetRunnerByID(t.Context(), 34350)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+}
diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go
index 34fe212d50..3b0f9589d2 100644
--- a/tests/integration/git_general_test.go
+++ b/tests/integration/git_general_test.go
@@ -11,8 +11,10 @@ import (
"net/http"
"net/url"
"os"
+ "os/exec"
"path"
"path/filepath"
+ "slices"
"strconv"
"testing"
"time"
@@ -24,12 +26,14 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
+ "github.com/kballard/go-shellquote"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -105,7 +109,12 @@ func testGitGeneral(t *testing.T, u *url.URL) {
// Setup key the user ssh key
withKeyFile(t, keyname, func(keyFile string) {
- t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile))
+ var keyID int64
+ t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
+ keyID = key.ID
+ }))
+ assert.NotZero(t, keyID)
+ t.Run("LFSAccessTest", doSSHLFSAccessTest(sshContext, keyID))
// Setup remote link
// TODO: get url from api
@@ -136,6 +145,36 @@ func testGitGeneral(t *testing.T, u *url.URL) {
})
}
+func doSSHLFSAccessTest(_ APITestContext, keyID int64) func(*testing.T) {
+ return func(t *testing.T) {
+ sshCommand := os.Getenv("GIT_SSH_COMMAND") // it is set in withKeyFile
+ sshCmdParts, err := shellquote.Split(sshCommand) // and parse the ssh command to construct some mocked arguments
+ require.NoError(t, err)
+
+ t.Run("User2AccessOwned", func(t *testing.T) {
+ sshCmdUser2Self := append(slices.Clone(sshCmdParts),
+ "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
+ "git-lfs-authenticate", "user2/repo1.git", "upload", // accessible to own repo
+ )
+ cmd := exec.CommandContext(t.Context(), sshCmdUser2Self[0], sshCmdUser2Self[1:]...)
+ _, err := cmd.Output()
+ assert.NoError(t, err) // accessible, no error
+ })
+
+ t.Run("User2AccessOther", func(t *testing.T) {
+ sshCmdUser2Other := append(slices.Clone(sshCmdParts),
+ "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
+ "git-lfs-authenticate", "user5/repo4.git", "upload", // inaccessible to other's (user5/repo4)
+ )
+ cmd := exec.CommandContext(t.Context(), sshCmdUser2Other[0], sshCmdUser2Other[1:]...)
+ _, err := cmd.Output()
+ var errExit *exec.ExitError
+ require.ErrorAs(t, err, &errExit) // inaccessible, error
+ assert.Contains(t, string(errExit.Stderr), fmt.Sprintf("User: 2:user2 with Key: %d:test-key is not authorized to write to user5/repo4.", keyID))
+ })
+ }
+}
+
func ensureAnonymousClone(t *testing.T, u *url.URL) {
dstLocalPath := t.TempDir()
t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
@@ -675,7 +714,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
commitID := path.Base(commitURL)
- addCommitStatus := func(status api.CommitStatusState) func(*testing.T) {
+ addCommitStatus := func(status commitstatus.CommitStatusState) func(*testing.T) {
return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{
State: status,
TargetURL: "http://test.ci/",
@@ -685,7 +724,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
}
// Call API to add Pending status for commit
- t.Run("CreateStatus", addCommitStatus(api.CommitStatusPending))
+ t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusPending))
// Cancel not existing auto merge
ctx.ExpectedCode = http.StatusNotFound
@@ -714,7 +753,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
assert.False(t, pr.HasMerged)
// Call API to add Failure status for commit
- t.Run("CreateStatus", addCommitStatus(api.CommitStatusFailure))
+ t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusFailure))
// Check pr status
pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
@@ -722,7 +761,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
assert.False(t, pr.HasMerged)
// Call API to add Success status for commit
- t.Run("CreateStatus", addCommitStatus(api.CommitStatusSuccess))
+ t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusSuccess))
// wait to let gitea merge stuff
time.Sleep(time.Second)
diff --git a/tests/integration/git_misc_test.go b/tests/integration/git_misc_test.go
index bf9e2c02ab..a5c53fd6e9 100644
--- a/tests/integration/git_misc_test.go
+++ b/tests/integration/git_misc_test.go
@@ -135,3 +135,105 @@ func TestAgitPullPush(t *testing.T) {
assert.NoError(t, err)
})
}
+
+func TestAgitReviewStaleness(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
+
+ u.Path = baseAPITestContext.GitPath()
+ u.User = url.UserPassword("user2", userPassword)
+
+ dstPath := t.TempDir()
+ doGitClone(dstPath, u)(t)
+
+ gitRepo, err := git.OpenRepository(t.Context(), dstPath)
+ assert.NoError(t, err)
+ defer gitRepo.Close()
+
+ doGitCreateBranch(dstPath, "test-agit-review")
+
+ // Create initial commit
+ _, err = generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "initial-")
+ assert.NoError(t, err)
+
+ // create PR via agit
+ err = git.NewCommand("push", "origin",
+ "-o", "title=Test agit Review Staleness", "-o", "description=Testing review staleness",
+ "HEAD:refs/for/master/test-agit-review",
+ ).Run(git.DefaultContext, &git.RunOpts{Dir: dstPath})
+ assert.NoError(t, err)
+
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
+ BaseRepoID: 1,
+ Flow: issues_model.PullRequestFlowAGit,
+ HeadBranch: "user2/test-agit-review",
+ })
+ assert.NoError(t, pr.LoadIssue(db.DefaultContext))
+
+ // Get initial commit ID for the review
+ initialCommitID := pr.HeadCommitID
+ t.Logf("Initial commit ID: %s", initialCommitID)
+
+ // Create a review on the PR (as user1 reviewing user2's PR)
+ reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
+ Type: issues_model.ReviewTypeApprove,
+ Reviewer: reviewer,
+ Issue: pr.Issue,
+ CommitID: initialCommitID,
+ Content: "LGTM! Looks good to merge.",
+ Official: false,
+ })
+ assert.NoError(t, err)
+ assert.False(t, review.Stale, "New review should not be stale")
+
+ // Verify review exists and is not stale
+ reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
+ IssueID: pr.IssueID,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, reviews, 1)
+ assert.Equal(t, initialCommitID, reviews[0].CommitID)
+ assert.False(t, reviews[0].Stale, "Review should not be stale initially")
+
+ // Create a new commit and update the agit PR
+ _, err = generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "updated-")
+ assert.NoError(t, err)
+
+ err = git.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath})
+ assert.NoError(t, err)
+
+ // Reload PR to get updated commit ID
+ pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
+ BaseRepoID: 1,
+ Flow: issues_model.PullRequestFlowAGit,
+ HeadBranch: "user2/test-agit-review",
+ })
+ assert.NoError(t, pr.LoadIssue(db.DefaultContext))
+
+ // For AGit PRs, HeadCommitID must be loaded from git references
+ baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
+ assert.NoError(t, err)
+ defer baseGitRepo.Close()
+
+ updatedCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
+ assert.NoError(t, err)
+ t.Logf("Updated commit ID: %s", updatedCommitID)
+
+ // Verify the PR was updated with new commit
+ assert.NotEqual(t, initialCommitID, updatedCommitID, "PR should have new commit ID after update")
+
+ // Check that the review is now marked as stale
+ reviews, err = issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
+ IssueID: pr.IssueID,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, reviews, 1)
+
+ assert.True(t, reviews[0].Stale, "Review should be marked as stale after AGit PR update")
+
+ // The review commit ID should remain the same (pointing to the original commit)
+ assert.Equal(t, initialCommitID, reviews[0].CommitID, "Review commit ID should remain unchanged and point to original commit")
+ })
+}
diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go
index bac7b4f48b..d716847b54 100644
--- a/tests/integration/git_push_test.go
+++ b/tests/integration/git_push_test.go
@@ -27,7 +27,7 @@ func TestGitPush(t *testing.T) {
func testGitPush(t *testing.T, u *url.URL) {
t.Run("Push branches at once", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
- for i := 0; i < 100; i++ {
+ for i := range 100 {
branchName := fmt.Sprintf("branch-%d", i)
pushed = append(pushed, branchName)
doGitCreateBranch(gitPath, branchName)(t)
@@ -40,7 +40,7 @@ func testGitPush(t *testing.T, u *url.URL) {
t.Run("Push branches exists", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
- for i := 0; i < 10; i++ {
+ for i := range 10 {
branchName := fmt.Sprintf("branch-%d", i)
if i < 5 {
pushed = append(pushed, branchName)
@@ -54,7 +54,7 @@ func testGitPush(t *testing.T, u *url.URL) {
pushed = pushed[:0]
// do some changes for the first 5 branches created above
- for i := 0; i < 5; i++ {
+ for i := range 5 {
branchName := fmt.Sprintf("branch-%d", i)
pushed = append(pushed, branchName)
@@ -75,7 +75,7 @@ func testGitPush(t *testing.T, u *url.URL) {
t.Run("Push branches one by one", func(t *testing.T) {
runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
- for i := 0; i < 100; i++ {
+ for i := range 100 {
branchName := fmt.Sprintf("branch-%d", i)
doGitCreateBranch(gitPath, branchName)(t)
doGitPushTestRepository(gitPath, "origin", branchName)(t)
@@ -101,14 +101,14 @@ func testGitPush(t *testing.T, u *url.URL) {
doGitPushTestRepository(gitPath, "origin", "master")(t) // make sure master is the default branch instead of a branch we are going to delete
pushed = append(pushed, "master")
- for i := 0; i < 100; i++ {
+ for i := range 100 {
branchName := fmt.Sprintf("branch-%d", i)
pushed = append(pushed, branchName)
doGitCreateBranch(gitPath, branchName)(t)
}
doGitPushTestRepository(gitPath, "origin", "--all")(t)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
branchName := fmt.Sprintf("branch-%d", i)
doGitPushTestRepository(gitPath, "origin", "--delete", branchName)(t)
deleted = append(deleted, branchName)
@@ -191,7 +191,7 @@ func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gi
assert.Equal(t, commitID, branch.CommitID)
}
- require.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID))
+ require.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, repo.ID))
}
func TestPushPullRefs(t *testing.T) {
diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_ssh_git_test.go
index 32de200f63..56f9f87783 100644
--- a/tests/integration/gpg_git_test.go
+++ b/tests/integration/gpg_ssh_git_test.go
@@ -4,7 +4,10 @@
package integration
import (
+ "crypto/ed25519"
+ "crypto/rand"
"encoding/base64"
+ "encoding/pem"
"fmt"
"net/url"
"os"
@@ -23,6 +26,7 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "golang.org/x/crypto/ssh"
)
func TestGPGGit(t *testing.T) {
@@ -42,6 +46,37 @@ func TestGPGGit(t *testing.T) {
defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})()
defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})()
+ testGitSigning(t)
+}
+
+func TestSSHGit(t *testing.T) {
+ tmpDir := t.TempDir() // use a temp dir to store the SSH keys
+ err := os.Chmod(tmpDir, 0o700)
+ assert.NoError(t, err)
+
+ pub, priv, err := ed25519.GenerateKey(rand.Reader)
+ require.NoError(t, err, "ed25519.GenerateKey")
+ sshPubKey, err := ssh.NewPublicKey(pub)
+ require.NoError(t, err, "ssh.NewPublicKey")
+
+ err = os.WriteFile(tmpDir+"/id_ed25519.pub", ssh.MarshalAuthorizedKey(sshPubKey), 0o600)
+ require.NoError(t, err, "os.WriteFile id_ed25519.pub")
+ block, err := ssh.MarshalPrivateKey(priv, "")
+ require.NoError(t, err, "ssh.MarshalPrivateKey")
+ err = os.WriteFile(tmpDir+"/id_ed25519", pem.EncodeToMemory(block), 0o600)
+ require.NoError(t, err, "os.WriteFile id_ed25519")
+
+ defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, tmpDir+"/id_ed25519.pub")()
+ defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")()
+ defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")()
+ defer test.MockVariableValue(&setting.Repository.Signing.SigningFormat, "ssh")()
+ defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})()
+ defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})()
+
+ testGitSigning(t)
+}
+
+func testGitSigning(t *testing.T) {
username := "user2"
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
baseAPITestContext := NewAPITestContext(t, username, "repo1")
diff --git a/tests/integration/html_helper.go b/tests/integration/html_helper.go
index 874fc32228..4d589b32e7 100644
--- a/tests/integration/html_helper.go
+++ b/tests/integration/html_helper.go
@@ -42,7 +42,7 @@ func (doc *HTMLDoc) GetCSRF() string {
return doc.GetInputValueByName("_csrf")
}
-// AssertHTMLElement check if element by selector exists or does not exist depending on checkExists
+// AssertHTMLElement check if the element by selector exists or does not exist depending on checkExists
func AssertHTMLElement[T int | bool](t testing.TB, doc *HTMLDoc, selector string, checkExists T) {
sel := doc.doc.Find(selector)
switch v := any(checkExists).(type) {
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index d5b7bb7a3e..5896a97ef1 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -1,7 +1,7 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-//nolint:forbidigo
+//nolint:forbidigo // use of print functions is allowed in tests
package integration
import (
@@ -148,6 +148,9 @@ func (s *TestSession) GetCookieFlashMessage() *middleware.Flash {
func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder {
t.Helper()
+ if s == nil {
+ return MakeRequest(t, rw, expectedStatus)
+ }
req := rw.Request
baseURL, err := url.Parse(setting.AppURL)
assert.NoError(t, err)
diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go
index f0a5e4f519..b5dca58357 100644
--- a/tests/integration/issue_test.go
+++ b/tests/integration/issue_test.go
@@ -76,14 +76,11 @@ func TestViewIssuesSortByType(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body)
issuesSelection := getIssuesSelection(t, htmlDoc)
- expectedNumIssues := unittest.GetCount(t,
+ expectedNumIssues := min(unittest.GetCount(t,
&issues_model.Issue{RepoID: repo.ID, PosterID: user.ID},
unittest.Cond("is_closed=?", false),
unittest.Cond("is_pull=?", false),
- )
- if expectedNumIssues > setting.UI.IssuePagingNum {
- expectedNumIssues = setting.UI.IssuePagingNum
- }
+ ), setting.UI.IssuePagingNum)
assert.Equal(t, expectedNumIssues, issuesSelection.Length())
issuesSelection.Each(func(_ int, selection *goquery.Selection) {
@@ -151,6 +148,22 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content
return issueURL
}
+func testIssueDelete(t *testing.T, session *TestSession, issueURL string) {
+ req := NewRequestWithValues(t, "POST", path.Join(issueURL, "delete"), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ })
+ session.MakeRequest(t, req, http.StatusSeeOther)
+}
+
+func testIssueAssign(t *testing.T, session *TestSession, repoLink string, issueID, assigneeID int64) {
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/assignee?issue_ids=%d", issueID), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "id": strconv.FormatInt(assigneeID, 10),
+ "action": "", // empty action means assign
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+}
+
func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 {
req := NewRequest(t, "GET", issueURL)
resp := session.MakeRequest(t, req, http.StatusOK)
@@ -184,6 +197,15 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
return int64(id)
}
+func testIssueChangeMilestone(t *testing.T, session *TestSession, repoLink string, issueID, milestoneID int64) {
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/milestone?issue_ids=%d", issueID), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "id": strconv.FormatInt(milestoneID, 10),
+ })
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.Equal(t, `{"ok":true}`, strings.TrimSpace(resp.Body.String()))
+}
+
func TestNewIssue(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
@@ -473,10 +495,7 @@ func TestSearchIssues(t *testing.T) {
session := loginUser(t, "user2")
- expectedIssueCount := 20 // from the fixtures
- if expectedIssueCount > setting.UI.IssuePagingNum {
- expectedIssueCount = setting.UI.IssuePagingNum
- }
+ expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
link, _ := url.Parse("/issues/search")
req := NewRequest(t, "GET", link.String())
@@ -567,10 +586,7 @@ func TestSearchIssues(t *testing.T) {
func TestSearchIssuesWithLabels(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- expectedIssueCount := 20 // from the fixtures
- if expectedIssueCount > setting.UI.IssuePagingNum {
- expectedIssueCount = setting.UI.IssuePagingNum
- }
+ expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
session := loginUser(t, "user1")
link, _ := url.Parse("/issues/search")
diff --git a/tests/integration/lfs_view_test.go b/tests/integration/lfs_view_test.go
index 64ffebaa78..c26ece22be 100644
--- a/tests/integration/lfs_view_test.go
+++ b/tests/integration/lfs_view_test.go
@@ -68,14 +68,15 @@ func TestLFSRender(t *testing.T) {
req := NewRequest(t, "GET", "/user2/lfs/src/branch/master/crypt.bin")
resp := session.MakeRequest(t, req, http.StatusOK)
- doc := NewHTMLParser(t, resp.Body).doc
+ doc := NewHTMLParser(t, resp.Body)
fileInfo := doc.Find("div.file-info-entry").First().Text()
assert.Contains(t, fileInfo, "LFS")
- rawLink, exists := doc.Find("div.file-view > div.view-raw > a").Attr("href")
- assert.True(t, exists, "Download link should render instead of content because this is a binary file")
- assert.Equal(t, "/user2/lfs/media/branch/master/crypt.bin", rawLink, "The download link should use the proper /media link because it's in LFS")
+ // find new file view container
+ fileViewContainer := doc.Find("[data-global-init=initRepoFileView]")
+ assert.Equal(t, "/user2/lfs/media/branch/master/crypt.bin", fileViewContainer.AttrOr("data-raw-file-link", ""))
+ AssertHTMLElement(t, doc, ".view-raw > .file-view-render-container > .file-view-raw-prompt", 1)
})
// check that a directory with a README file shows its text
diff --git a/tests/integration/links_test.go b/tests/integration/links_test.go
index ec59e147d2..f80cc6f3f9 100644
--- a/tests/integration/links_test.go
+++ b/tests/integration/links_test.go
@@ -17,38 +17,48 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestLinksNoLogin(t *testing.T) {
+func assertLinkPageComplete(t *testing.T, session *TestSession, link string) {
+ req := NewRequest(t, "GET", link)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "Page did not complete: "+link)
+}
+
+func TestLinks(t *testing.T) {
defer tests.PrepareTestEnv(t)()
+ t.Run("NoLogin", testLinksNoLogin)
+ t.Run("RedirectsNoLogin", testLinksRedirectsNoLogin)
+ t.Run("NoLoginNotExist", testLinksNoLoginNotExist)
+ t.Run("AsUser", testLinksAsUser)
+ t.Run("RepoCommon", testLinksRepoCommon)
+}
+
+func testLinksNoLogin(t *testing.T) {
links := []string{
+ "/",
"/explore/repos",
"/explore/repos?q=test",
"/explore/users",
"/explore/users?q=test",
"/explore/organizations",
"/explore/organizations?q=test",
- "/",
"/user/sign_up",
"/user/login",
"/user/forgot_password",
- "/api/swagger",
"/user2/repo1",
"/user2/repo1/",
"/user2/repo1/projects",
"/user2/repo1/projects/1",
"/user2/repo1/releases/tag/delete-tag", // It's the only one existing record on release.yml which has is_tag: true
- "/.well-known/security.txt",
+ "/api/swagger",
}
-
for _, link := range links {
- req := NewRequest(t, "GET", link)
- MakeRequest(t, req, http.StatusOK)
+ assertLinkPageComplete(t, nil, link)
}
+ MakeRequest(t, NewRequest(t, "GET", "/.well-known/security.txt"), http.StatusOK)
}
-func TestRedirectsNoLogin(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
-
+func testLinksRedirectsNoLogin(t *testing.T) {
redirects := []struct{ from, to string }{
{"/user2/repo1/commits/master", "/user2/repo1/commits/branch/master"},
{"/user2/repo1/src/master", "/user2/repo1/src/branch/master"},
@@ -68,9 +78,7 @@ func TestRedirectsNoLogin(t *testing.T) {
}
}
-func TestNoLoginNotExist(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
-
+func testLinksNoLoginNotExist(t *testing.T) {
links := []string{
"/user5/repo4/projects",
"/user5/repo4/projects/3",
@@ -82,7 +90,8 @@ func TestNoLoginNotExist(t *testing.T) {
}
}
-func testLinksAsUser(userName string, t *testing.T) {
+func testLinksAsUser(t *testing.T) {
+ session := loginUser(t, "user2")
links := []string{
"/explore/repos",
"/explore/repos?q=test",
@@ -130,18 +139,14 @@ func testLinksAsUser(userName string, t *testing.T) {
"/user/settings/repos",
}
- session := loginUser(t, userName)
for _, link := range links {
- req := NewRequest(t, "GET", link)
- session.MakeRequest(t, req, http.StatusOK)
+ assertLinkPageComplete(t, session, link)
}
- reqAPI := NewRequestf(t, "GET", "/api/v1/users/%s/repos", userName)
+ reqAPI := NewRequestf(t, "GET", "/api/v1/users/user2/repos")
respAPI := MakeRequest(t, reqAPI, http.StatusOK)
-
var apiRepos []*api.Repository
DecodeJSON(t, respAPI, &apiRepos)
-
repoLinks := []string{
"",
"/issues",
@@ -164,24 +169,15 @@ func testLinksAsUser(userName string, t *testing.T) {
"/wiki/?action=_new",
"/activity",
}
-
for _, repo := range apiRepos {
for _, link := range repoLinks {
- req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s%s", userName, repo.Name, link))
- session.MakeRequest(t, req, http.StatusOK)
+ link = fmt.Sprintf("/user2/%s%s", repo.Name, link)
+ assertLinkPageComplete(t, session, link)
}
}
}
-func TestLinksLogin(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
-
- testLinksAsUser("user2", t)
-}
-
-func TestRepoLinks(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
-
+func testLinksRepoCommon(t *testing.T) {
// repo1 has enabled almost features, so we can test most links
repoLink := "/user2/repo1"
links := []string{
@@ -192,21 +188,18 @@ func TestRepoLinks(t *testing.T) {
// anonymous user
for _, link := range links {
- req := NewRequest(t, "GET", repoLink+link)
- MakeRequest(t, req, http.StatusOK)
+ assertLinkPageComplete(t, nil, repoLink+link)
}
// admin/owner user
session := loginUser(t, "user1")
for _, link := range links {
- req := NewRequest(t, "GET", repoLink+link)
- session.MakeRequest(t, req, http.StatusOK)
+ assertLinkPageComplete(t, session, repoLink+link)
}
// non-admin non-owner user
session = loginUser(t, "user2")
for _, link := range links {
- req := NewRequest(t, "GET", repoLink+link)
- session.MakeRequest(t, req, http.StatusOK)
+ assertLinkPageComplete(t, session, repoLink+link)
}
}
diff --git a/tests/integration/org_count_test.go b/tests/integration/org_count_test.go
index fb71e690c2..c48008e627 100644
--- a/tests/integration/org_count_test.go
+++ b/tests/integration/org_count_test.go
@@ -120,8 +120,8 @@ func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, ca
})
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
- UserID: user.ID,
- IncludePrivate: true,
+ UserID: user.ID,
+ IncludeVisibility: api.VisibleTypePrivate,
})
assert.NoError(t, err)
diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go
index 9a93455858..3ed7baa5ba 100644
--- a/tests/integration/org_test.go
+++ b/tests/integration/org_test.go
@@ -10,12 +10,17 @@ import (
"testing"
auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/organization"
+ "code.gitea.io/gitea/models/perm"
+ "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestOrgRepos(t *testing.T) {
@@ -40,7 +45,7 @@ func TestOrgRepos(t *testing.T) {
sel := htmlDoc.doc.Find("a.name")
assert.Len(t, repos, len(sel.Nodes))
- for i := 0; i < len(repos); i++ {
+ for i := range repos {
assert.Equal(t, repos[i], strings.TrimSpace(sel.Eq(i).Text()))
}
}
@@ -217,4 +222,32 @@ func TestTeamSearch(t *testing.T) {
session = loginUser(t, user5.Name)
req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team")
session.MakeRequest(t, req, http.StatusNotFound)
+
+ t.Run("SearchWithPermission", func(t *testing.T) {
+ ctx := t.Context()
+ const testOrgID int64 = 500
+ const testRepoID int64 = 2000
+ testTeam := &organization.Team{OrgID: testOrgID, LowerName: "test_team", AccessMode: perm.AccessModeNone}
+ require.NoError(t, db.Insert(ctx, testTeam))
+ require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: testOrgID, TeamID: testTeam.ID, RepoID: testRepoID}))
+ require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: testOrgID, TeamID: testTeam.ID, Type: unit.TypeCode, AccessMode: perm.AccessModeRead}))
+ require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: testOrgID, TeamID: testTeam.ID, Type: unit.TypeIssues, AccessMode: perm.AccessModeWrite}))
+
+ teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeRead, unit.TypeCode, unit.TypeIssues)
+ require.NoError(t, err)
+ assert.Len(t, teams, 1) // can read "code" or "issues"
+
+ teams, err = organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeWrite, unit.TypeCode)
+ require.NoError(t, err)
+ assert.Empty(t, teams) // cannot write "code"
+
+ teams, err = organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeWrite, unit.TypeIssues)
+ require.NoError(t, err)
+ assert.Len(t, teams, 1) // can write "issues"
+
+ _, _ = db.GetEngine(ctx).ID(testTeam.ID).Update(&organization.Team{AccessMode: perm.AccessModeWrite})
+ teams, err = organization.GetTeamsWithAccessToAnyRepoUnit(ctx, testOrgID, testRepoID, perm.AccessModeWrite, unit.TypeCode)
+ require.NoError(t, err)
+ assert.Len(t, teams, 1) // team permission is "write", so can write "code"
+ })
}
diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go
index 13213c254d..43a489d4c4 100644
--- a/tests/integration/project_test.go
+++ b/tests/integration/project_test.go
@@ -47,7 +47,7 @@ func TestMoveRepoProjectColumns(t *testing.T) {
err := project_model.NewProject(db.DefaultContext, &project1)
assert.NoError(t, err)
- for i := 0; i < 3; i++ {
+ for i := range 3 {
err = project_model.NewColumn(db.DefaultContext, &project_model.Column{
Title: fmt.Sprintf("column %d", i+1),
ProjectID: project1.ID,
diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go
index 86bdd1b9e3..f95a2f1690 100644
--- a/tests/integration/pull_compare_test.go
+++ b/tests/integration/pull_compare_test.go
@@ -13,7 +13,6 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
- user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/test"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests"
@@ -76,10 +75,9 @@ func TestPullCompare(t *testing.T) {
assert.Positive(t, editButtonCount, "Expected to find a button to edit a file in the PR diff view but there were none")
repoForked := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
- user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
// delete the head repository and revisit the PR diff view
- err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, repoForked.ID)
+ err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, repoForked.ID)
assert.NoError(t, err)
req = NewRequest(t, "GET", prFilesURL)
@@ -161,7 +159,8 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) {
"commit_summary": "user2 updated the file",
"commit_choice": "direct",
})
- user2Session.MakeRequest(t, req, http.StatusSeeOther)
+ resp = user2Session.MakeRequest(t, req, http.StatusOK)
+ assert.NotEmpty(t, test.RedirectURL(resp))
}
}
})
diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go
index cf50d5e639..73b4c22070 100644
--- a/tests/integration/pull_merge_test.go
+++ b/tests/integration/pull_merge_test.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/queue"
@@ -768,7 +769,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) {
}()
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
- State: api.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: "https://gitea.com",
Context: "gitea/actions",
})
@@ -848,7 +849,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) {
}()
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
- State: api.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: "https://gitea.com",
Context: "gitea/actions",
})
@@ -977,14 +978,12 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
}()
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
- State: api.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: "https://gitea.com",
Context: "gitea/actions",
})
assert.NoError(t, err)
- time.Sleep(2 * time.Second)
-
// reload pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.False(t, pr.HasMerged)
@@ -997,8 +996,6 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
htmlDoc := NewHTMLParser(t, resp.Body)
testSubmitReview(t, approveSession, htmlDoc.GetCSRF(), "user2", "repo1", strconv.Itoa(int(pr.Index)), sha, "approve", http.StatusOK)
- time.Sleep(2 * time.Second)
-
// realod pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.True(t, pr.HasMerged)
diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go
index 4d43847f1b..49326a594a 100644
--- a/tests/integration/pull_status_test.go
+++ b/tests/integration/pull_status_test.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
@@ -55,20 +56,20 @@ func TestPullCreate_CommitStatus(t *testing.T) {
commitID := path.Base(commitURL)
- statusList := []api.CommitStatusState{
- api.CommitStatusPending,
- api.CommitStatusError,
- api.CommitStatusFailure,
- api.CommitStatusSuccess,
- api.CommitStatusWarning,
+ statusList := []commitstatus.CommitStatusState{
+ commitstatus.CommitStatusPending,
+ commitstatus.CommitStatusError,
+ commitstatus.CommitStatusFailure,
+ commitstatus.CommitStatusSuccess,
+ commitstatus.CommitStatusWarning,
}
- statesIcons := map[api.CommitStatusState]string{
- api.CommitStatusPending: "octicon-dot-fill",
- api.CommitStatusSuccess: "octicon-check",
- api.CommitStatusError: "gitea-exclamation",
- api.CommitStatusFailure: "octicon-x",
- api.CommitStatusWarning: "gitea-exclamation",
+ statesIcons := map[commitstatus.CommitStatusState]string{
+ commitstatus.CommitStatusPending: "octicon-dot-fill",
+ commitstatus.CommitStatusSuccess: "octicon-check",
+ commitstatus.CommitStatusError: "gitea-exclamation",
+ commitstatus.CommitStatusFailure: "octicon-x",
+ commitstatus.CommitStatusWarning: "gitea-exclamation",
}
testCtx := NewAPITestContext(t, "user1", "repo1", auth_model.AccessTokenScopeWriteRepository)
@@ -99,7 +100,7 @@ func TestPullCreate_CommitStatus(t *testing.T) {
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
css := unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatusSummary{RepoID: repo1.ID, SHA: commitID})
- assert.Equal(t, api.CommitStatusWarning, css.State)
+ assert.Equal(t, commitstatus.CommitStatusSuccess, css.State)
})
}
@@ -128,7 +129,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1")
- testEditFileToNewBranch(t, session, "user1", "repo1", "status1", "status1", "README.md", "# repo1\n\nDescription for repo1")
+ testEditFile(t, session, "user1", "repo1", "status1", "README.md", "# repo1\n\nDescription for repo1")
url := path.Join("user1", "repo1", "compare", "master...status1")
req := NewRequestWithValues(t, "POST", url,
diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go
index 125f0810a7..88a58787af 100644
--- a/tests/integration/release_test.go
+++ b/tests/integration/release_test.go
@@ -7,7 +7,6 @@ import (
"fmt"
"net/http"
"testing"
- "time"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
@@ -68,9 +67,6 @@ func TestViewReleases(t *testing.T) {
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/releases")
session.MakeRequest(t, req, http.StatusOK)
-
- // if CI is to slow this test fail, so lets wait a bit
- time.Sleep(time.Millisecond * 100)
}
func TestViewReleasesNoLogin(t *testing.T) {
@@ -118,7 +114,7 @@ func TestCreateReleasePaging(t *testing.T) {
session := loginUser(t, "user2")
// Create enough releases to have paging
- for i := 0; i < 12; i++ {
+ for i := range 12 {
version := fmt.Sprintf("v0.0.%d", i)
createNewRelease(t, session, "/user2/repo1", version, version, false, false)
}
diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go
index dee0aa6176..0097a7f62e 100644
--- a/tests/integration/repo_commits_test.go
+++ b/tests/integration/repo_commits_test.go
@@ -12,8 +12,7 @@ import (
"testing"
auth_model "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/models/unittest"
- user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -40,40 +39,24 @@ func TestRepoCommits(t *testing.T) {
func Test_ReposGitCommitListNotMaster(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- // Login as User2.
- session := loginUser(t, user.Name)
-
- // Test getting commits (Page 1)
- req := NewRequestf(t, "GET", "/%s/repo16/commits/branch/master", user.Name)
+ session := loginUser(t, "user2")
+ req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
- commits := []string{}
+ var commits []string
doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) {
- commitURL, exists := s.Attr("href")
- assert.True(t, exists)
- assert.NotEmpty(t, commitURL)
+ commitURL, _ := s.Attr("href")
commits = append(commits, path.Base(commitURL))
})
+ assert.Equal(t, []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, commits)
- assert.Len(t, commits, 3)
- assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", commits[0])
- assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", commits[1])
- assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", commits[2])
-
- userNames := []string{}
+ var userHrefs []string
doc.doc.Find("#commits-table .author-wrapper").Each(func(i int, s *goquery.Selection) {
- userPath, exists := s.Attr("href")
- assert.True(t, exists)
- assert.NotEmpty(t, userPath)
- userNames = append(userNames, path.Base(userPath))
+ userHref, _ := s.Attr("href")
+ userHrefs = append(userHrefs, userHref)
})
-
- assert.Len(t, userNames, 3)
- assert.Equal(t, "User2", userNames[0])
- assert.Equal(t, "user21", userNames[1])
- assert.Equal(t, "User2", userNames[2])
+ assert.Equal(t, []string{"/user2", "/user21", "/user2"}, userHrefs)
}
func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
@@ -94,7 +77,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
// Call API to add status for commit
ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{
- State: api.CommitStatusState(state),
+ State: commitstatus.CommitStatusState(state),
TargetURL: "http://test.ci/",
Description: "",
Context: "testci",
@@ -138,7 +121,7 @@ func testRepoCommitsWithStatus(t *testing.T, resp, respOne *httptest.ResponseRec
assert.NotNil(t, status)
if assert.Len(t, statuses, 1) {
- assert.Equal(t, api.CommitStatusState(state), statuses[0].State)
+ assert.Equal(t, commitstatus.CommitStatusState(state), statuses[0].State)
assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/statuses/65f1bf27bc3bf70f64657658635e66094edbcb4d", statuses[0].URL)
assert.Equal(t, "http://test.ci/", statuses[0].TargetURL)
assert.Empty(t, statuses[0].Description)
@@ -186,13 +169,13 @@ func TestRepoCommitsStatusParallel(t *testing.T) {
assert.NotEmpty(t, commitURL)
var wg sync.WaitGroup
- for i := 0; i < 10; i++ {
+ for i := range 10 {
wg.Add(1)
go func(parentT *testing.T, i int) {
parentT.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) {
ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
runBody := doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{
- State: api.CommitStatusPending,
+ State: commitstatus.CommitStatusPending,
TargetURL: "http://test.ci/",
Description: "",
Context: "testci",
@@ -223,14 +206,14 @@ func TestRepoCommitsStatusMultiple(t *testing.T) {
// Call API to add status for commit
ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository)
t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{
- State: api.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: "http://test.ci/",
Description: "",
Context: "testci",
}))
t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{
- State: api.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: "http://test.ci/",
Description: "",
Context: "other_context",
diff --git a/tests/integration/repo_fork_test.go b/tests/integration/repo_fork_test.go
index a7010af14a..95325eefeb 100644
--- a/tests/integration/repo_fork_test.go
+++ b/tests/integration/repo_fork_test.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
org_service "code.gitea.io/gitea/services/org"
"code.gitea.io/gitea/tests"
@@ -51,7 +52,8 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO
"repo_name": forkRepoName,
"fork_single_branch": forkBranch,
})
- session.MakeRequest(t, req, http.StatusSeeOther)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.Equal(t, fmt.Sprintf("/%s/%s", forkOwnerName, forkRepoName), test.RedirectURL(resp))
// Step4: check the existence of the forked repo
req = NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName)
@@ -82,7 +84,7 @@ func TestRepoForkToOrg(t *testing.T) {
func TestForkListLimitedAndPrivateRepos(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- forkItemSelector := ".repo-fork-item"
+ forkItemSelector := ".fork-list .flex-item"
user1Sess := loginUser(t, "user1")
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
diff --git a/tests/integration/repo_merge_upstream_test.go b/tests/integration/repo_merge_upstream_test.go
index e928b04e9b..d33d31c646 100644
--- a/tests/integration/repo_merge_upstream_test.go
+++ b/tests/integration/repo_merge_upstream_test.go
@@ -147,5 +147,37 @@ func TestRepoMergeUpstream(t *testing.T) {
return queryMergeUpstreamButtonLink(htmlDoc) == ""
}, 5*time.Second, 100*time.Millisecond)
})
+
+ t.Run("FastForwardOnly", func(t *testing.T) {
+ // Create a clean branch for fast-forward testing
+ req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/test-repo-fork/branches/_new/branch/master", forkUser.Name), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "new_branch_name": "ff-test-branch",
+ })
+ session.MakeRequest(t, req, http.StatusSeeOther)
+
+ // Add content to base repository that can be fast-forwarded
+ require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "ff-test.txt", "master", "ff-content-1"))
+
+ // ff_only=true with fast-forward possible (should succeed)
+ req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
+ Branch: "ff-test-branch",
+ FfOnly: true,
+ }).AddTokenAuth(token)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ var mergeResp api.MergeUpstreamResponse
+ DecodeJSON(t, resp, &mergeResp)
+ assert.Equal(t, "fast-forward", mergeResp.MergeStyle)
+
+ // ff_only=true when fast-forward is not possible (should fail)
+ require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "another-file.txt", "master", "more-content"))
+
+ req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
+ Branch: "fork-branch",
+ FfOnly: true,
+ }).AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusBadRequest)
+ })
})
}
diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go
index c04d09af08..adfe07519f 100644
--- a/tests/integration/repo_test.go
+++ b/tests/integration/repo_test.go
@@ -27,6 +27,7 @@ import (
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestRepoView(t *testing.T) {
@@ -41,6 +42,7 @@ func TestRepoView(t *testing.T) {
t.Run("BlameFileInRepo", testBlameFileInRepo)
t.Run("ViewRepoDirectory", testViewRepoDirectory)
t.Run("ViewRepoDirectoryReadme", testViewRepoDirectoryReadme)
+ t.Run("ViewRepoSymlink", testViewRepoSymlink)
t.Run("MarkDownReadmeImage", testMarkDownReadmeImage)
t.Run("MarkDownReadmeImageSubfolder", testMarkDownReadmeImageSubfolder)
t.Run("GeneratedSourceLink", testGeneratedSourceLink)
@@ -412,6 +414,21 @@ func testViewRepoDirectoryReadme(t *testing.T) {
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
}
+func testViewRepoSymlink(t *testing.T) {
+ session := loginUser(t, "user2")
+ req := NewRequest(t, "GET", "/user2/readme-test/src/branch/symlink")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ htmlDoc := NewHTMLParser(t, resp.Body)
+ AssertHTMLElement(t, htmlDoc, ".entry-symbol-link", true)
+ followSymbolLinkHref := htmlDoc.Find(".entry-symbol-link").AttrOr("href", "")
+ require.Equal(t, "/user2/readme-test/src/branch/symlink/README.md?follow_symlink=1", followSymbolLinkHref)
+
+ req = NewRequest(t, "GET", followSymbolLinkHref)
+ resp = session.MakeRequest(t, req, http.StatusSeeOther)
+ assert.Equal(t, "/user2/readme-test/src/branch/symlink/some/other/path/awefulcake.txt?follow_symlink=1", resp.Header().Get("Location"))
+}
+
func testMarkDownReadmeImage(t *testing.T) {
defer tests.PrintCurrentTest(t)()
@@ -523,7 +540,7 @@ func TestGenerateRepository(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name})
- err = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, generatedRepo.ID)
+ err = repo_service.DeleteRepositoryDirectly(db.DefaultContext, generatedRepo.ID)
assert.NoError(t, err)
// a failed creating because some mock data
diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go
index 89df15b8de..1da7bc9d3c 100644
--- a/tests/integration/repo_webhook_test.go
+++ b/tests/integration/repo_webhook_test.go
@@ -9,15 +9,16 @@ import (
"net/http"
"net/http/httptest"
"net/url"
+ "path"
"strings"
"testing"
- "time"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/commitstatus"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
@@ -56,16 +57,21 @@ func TestNewWebHookLink(t *testing.T) {
}
}
-func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string) {
+func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string, branchFilter ...string) {
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
+ var branchFilterString string
+ if len(branchFilter) > 0 {
+ branchFilterString = branchFilter[0]
+ }
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{
Type: "gitea",
Config: api.CreateHookOptionConfig{
"content_type": "json",
"url": url,
},
- Events: []string{event},
- Active: true,
+ Events: []string{event},
+ Active: true,
+ BranchFilter: branchFilterString,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
}
@@ -126,19 +132,19 @@ func (m *mockWebhookProvider) Close() {
}
func Test_WebhookCreate(t *testing.T) {
- var payloads []api.CreatePayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.CreatePayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = string(webhook_module.HookEventCreate)
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.CreatePayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.CreatePayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = string(webhook_module.HookEventCreate)
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -158,19 +164,19 @@ func Test_WebhookCreate(t *testing.T) {
}
func Test_WebhookDelete(t *testing.T) {
- var payloads []api.DeletePayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.DeletePayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "delete"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.DeletePayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.DeletePayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "delete"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -191,19 +197,19 @@ func Test_WebhookDelete(t *testing.T) {
}
func Test_WebhookFork(t *testing.T) {
- var payloads []api.ForkPayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.ForkPayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "fork"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.ForkPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.ForkPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "fork"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1")
@@ -223,54 +229,103 @@ func Test_WebhookFork(t *testing.T) {
}
func Test_WebhookIssueComment(t *testing.T) {
- var payloads []api.IssueCommentPayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.IssueCommentPayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "issue_comment"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.IssueCommentPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.IssueCommentPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "issue_comment"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment")
- // 2. trigger the webhook
- issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2")
- testIssueAddComment(t, session, issueURL, "issue title2 comment1", "")
+ t.Run("create comment", func(t *testing.T) {
+ // 2. trigger the webhook
+ issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2")
+ testIssueAddComment(t, session, issueURL, "issue title2 comment1", "")
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "issue_comment", triggeredEvent)
+ assert.Len(t, payloads, 1)
+ assert.EqualValues(t, "created", payloads[0].Action)
+ assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
+ assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
+ assert.Equal(t, "Title2", payloads[0].Issue.Title)
+ assert.Equal(t, "Description2", payloads[0].Issue.Body)
+ assert.Equal(t, "issue title2 comment1", payloads[0].Comment.Body)
+ })
- // 3. validate the webhook is triggered
- assert.Equal(t, "issue_comment", triggeredEvent)
- assert.Len(t, payloads, 1)
- assert.EqualValues(t, "created", payloads[0].Action)
- assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
- assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
- assert.Equal(t, "Title2", payloads[0].Issue.Title)
- assert.Equal(t, "Description2", payloads[0].Issue.Body)
- assert.Equal(t, "issue title2 comment1", payloads[0].Comment.Body)
+ t.Run("update comment", func(t *testing.T) {
+ payloads = make([]api.IssueCommentPayload, 0, 2)
+ triggeredEvent = ""
+
+ // 2. trigger the webhook
+ issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3")
+ commentID := testIssueAddComment(t, session, issueURL, "issue title3 comment1", "")
+ modifiedContent := "issue title2 comment1 - modified"
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "content": modifiedContent,
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "issue_comment", triggeredEvent)
+ assert.Len(t, payloads, 2)
+ assert.EqualValues(t, "edited", payloads[1].Action)
+ assert.Equal(t, "repo1", payloads[1].Issue.Repo.Name)
+ assert.Equal(t, "user2/repo1", payloads[1].Issue.Repo.FullName)
+ assert.Equal(t, "Title3", payloads[1].Issue.Title)
+ assert.Equal(t, "Description3", payloads[1].Issue.Body)
+ assert.Equal(t, modifiedContent, payloads[1].Comment.Body)
+ })
+
+ t.Run("Update comment with no content change", func(t *testing.T) {
+ payloads = make([]api.IssueCommentPayload, 0, 2)
+ triggeredEvent = ""
+ commentContent := "issue title3 comment1"
+
+ // 2. trigger the webhook
+ issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3")
+ commentID := testIssueAddComment(t, session, issueURL, commentContent, "")
+
+ payloads = make([]api.IssueCommentPayload, 0, 2)
+ triggeredEvent = ""
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
+ "_csrf": GetUserCSRFToken(t, session),
+ "content": commentContent,
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // 3. validate the webhook is not triggered because no content change
+ assert.Empty(t, triggeredEvent)
+ assert.Empty(t, payloads)
+ })
})
}
func Test_WebhookRelease(t *testing.T) {
- var payloads []api.ReleasePayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.ReleasePayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "release"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.ReleasePayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.ReleasePayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "release"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -291,6 +346,38 @@ func Test_WebhookRelease(t *testing.T) {
}
func Test_WebhookPush(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.PushPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.PushPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "push"
+ }, http.StatusOK)
+ defer provider.Close()
+
+ // 1. create a new webhook with special webhook for repo1
+ session := loginUser(t, "user2")
+
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push")
+
+ // 2. trigger the webhook
+ testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push")
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "push", triggeredEvent)
+ assert.Len(t, payloads, 1)
+ assert.Equal(t, "repo1", payloads[0].Repo.Name)
+ assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
+ assert.Len(t, payloads[0].Commits, 1)
+ assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
+ })
+}
+
+func Test_WebhookPushDevBranch(t *testing.T) {
var payloads []api.PushPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
@@ -307,15 +394,22 @@ func Test_WebhookPush(t *testing.T) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
- testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push")
+ // only for dev branch
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "develop")
- // 2. trigger the webhook
+ // 2. this should not trigger the webhook
testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push")
+ assert.Empty(t, triggeredEvent)
+ assert.Empty(t, payloads)
- // 3. validate the webhook is triggered
+ // 3. trigger the webhook
+ testCreateFile(t, session, "user2", "repo1", "develop", "test_webhook_push.md", "# a test file for webhook push")
+
+ // 4. validate the webhook is triggered
assert.Equal(t, "push", triggeredEvent)
assert.Len(t, payloads, 1)
assert.Equal(t, "repo1", payloads[0].Repo.Name)
+ assert.Equal(t, "develop", payloads[0].Branch())
assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
assert.Len(t, payloads[0].Commits, 1)
assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
@@ -323,19 +417,19 @@ func Test_WebhookPush(t *testing.T) {
}
func Test_WebhookIssue(t *testing.T) {
- var payloads []api.IssuePayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.IssuePayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "issues"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.IssuePayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.IssuePayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "issues"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -352,23 +446,167 @@ func Test_WebhookIssue(t *testing.T) {
assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
assert.Equal(t, "Title1", payloads[0].Issue.Title)
assert.Equal(t, "Description1", payloads[0].Issue.Body)
+ assert.Positive(t, payloads[0].Issue.Created.Unix())
+ assert.Positive(t, payloads[0].Issue.Updated.Unix())
})
}
-func Test_WebhookPullRequest(t *testing.T) {
- var payloads []api.PullRequestPayload
+func Test_WebhookIssueDelete(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.IssuePayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.IssuePayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "issue"
+ }, http.StatusOK)
+ defer provider.Close()
+
+ // 1. create a new webhook with special webhook for repo1
+ session := loginUser(t, "user2")
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues")
+ issueURL := testNewIssue(t, session, "user2", "repo1", "Title1", "Description1")
+
+ // 2. trigger the webhook
+ testIssueDelete(t, session, issueURL)
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "issue", triggeredEvent)
+ require.Len(t, payloads, 2)
+ assert.EqualValues(t, "deleted", payloads[1].Action)
+ assert.Equal(t, "repo1", payloads[1].Issue.Repo.Name)
+ assert.Equal(t, "user2/repo1", payloads[1].Issue.Repo.FullName)
+ assert.Equal(t, "Title1", payloads[1].Issue.Title)
+ assert.Equal(t, "Description1", payloads[1].Issue.Body)
+ })
+}
+
+func Test_WebhookIssueAssign(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.PullRequestPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.PullRequestPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "pull_request_assign"
+ }, http.StatusOK)
+ defer provider.Close()
+
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
+
+ // 1. create a new webhook with special webhook for repo1
+ session := loginUser(t, "user2")
+
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_assign")
+
+ // 2. trigger the webhook, issue 2 is a pull request
+ testIssueAssign(t, session, repo1.Link(), 2, user2.ID)
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "pull_request_assign", triggeredEvent)
+ assert.Len(t, payloads, 1)
+ assert.EqualValues(t, "assigned", payloads[0].Action)
+ assert.Equal(t, "repo1", payloads[0].PullRequest.Base.Repository.Name)
+ assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName)
+ assert.Equal(t, "issue2", payloads[0].PullRequest.Title)
+ assert.Equal(t, "content for the second issue", payloads[0].PullRequest.Body)
+ assert.Equal(t, user2.ID, payloads[0].PullRequest.Assignee.ID)
+ })
+}
+
+func Test_WebhookIssueMilestone(t *testing.T) {
+ var payloads []api.IssuePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
- var payload api.PullRequestPayload
+ var payload api.IssuePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
- triggeredEvent = "pull_request"
+ triggeredEvent = "issues"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ // create a new webhook with special webhook for repo1
+ session := loginUser(t, "user2")
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_milestone")
+
+ t.Run("assign a milestone", func(t *testing.T) {
+ // trigger the webhook
+ testIssueChangeMilestone(t, session, repo1.Link(), 1, 1)
+
+ // validate the webhook is triggered
+ assert.Equal(t, "issues", triggeredEvent)
+ assert.Len(t, payloads, 1)
+ assert.Equal(t, "milestoned", string(payloads[0].Action))
+ assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
+ assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
+ assert.Equal(t, "issue1", payloads[0].Issue.Title)
+ assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
+ assert.EqualValues(t, 1, payloads[0].Issue.Milestone.ID)
+ })
+
+ t.Run("change a milestong", func(t *testing.T) {
+ // trigger the webhook again
+ triggeredEvent = ""
+ payloads = make([]api.IssuePayload, 0, 1)
+ // change milestone to 2
+ testIssueChangeMilestone(t, session, repo1.Link(), 1, 2)
+
+ // validate the webhook is triggered
+ assert.Equal(t, "issues", triggeredEvent)
+ assert.Len(t, payloads, 1)
+ assert.Equal(t, "milestoned", string(payloads[0].Action))
+ assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
+ assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
+ assert.Equal(t, "issue1", payloads[0].Issue.Title)
+ assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
+ assert.EqualValues(t, 2, payloads[0].Issue.Milestone.ID)
+ })
+
+ t.Run("remove a milestone", func(t *testing.T) {
+ // trigger the webhook again
+ triggeredEvent = ""
+ payloads = make([]api.IssuePayload, 0, 1)
+ // change milestone to 0
+ testIssueChangeMilestone(t, session, repo1.Link(), 1, 0)
+
+ // validate the webhook is triggered
+ assert.Equal(t, "issues", triggeredEvent)
+ assert.Len(t, payloads, 1)
+ assert.Equal(t, "demilestoned", string(payloads[0].Action))
+ assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
+ assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
+ assert.Equal(t, "issue1", payloads[0].Issue.Title)
+ assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
+ assert.Nil(t, payloads[0].Issue.Milestone)
+ })
+ })
+}
+
+func Test_WebhookPullRequest(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.PullRequestPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.PullRequestPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "pull_request"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -392,20 +630,58 @@ func Test_WebhookPullRequest(t *testing.T) {
})
}
-func Test_WebhookPullRequestComment(t *testing.T) {
- var payloads []api.IssueCommentPayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.IssueCommentPayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "pull_request_comment"
- }, http.StatusOK)
- defer provider.Close()
+func Test_WebhookPullRequestDelete(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.PullRequestPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.PullRequestPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "pull_request"
+ }, http.StatusOK)
+ defer provider.Close()
+
+ // 1. create a new webhook with special webhook for repo1
+ session := loginUser(t, "user2")
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request")
+
+ testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
+
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
+ issueURL := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
+
+ // 2. trigger the webhook
+ testIssueDelete(t, session, path.Join(repo1.Link(), "pulls", issueURL))
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "pull_request", triggeredEvent)
+ require.Len(t, payloads, 2)
+ assert.EqualValues(t, "deleted", payloads[1].Action)
+ assert.Equal(t, "repo1", payloads[1].PullRequest.Base.Repository.Name)
+ assert.Equal(t, "user2/repo1", payloads[1].PullRequest.Base.Repository.FullName)
+ assert.Equal(t, 0, *payloads[1].PullRequest.Additions)
+ assert.Equal(t, 0, *payloads[1].PullRequest.ChangedFiles)
+ assert.Equal(t, 0, *payloads[1].PullRequest.Deletions)
+ })
+}
+
+func Test_WebhookPullRequestComment(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.IssueCommentPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.IssueCommentPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "pull_request_comment"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -431,19 +707,19 @@ func Test_WebhookPullRequestComment(t *testing.T) {
}
func Test_WebhookWiki(t *testing.T) {
- var payloads []api.WikiPayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.WikiPayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "wiki"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.WikiPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.WikiPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "wiki"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -463,19 +739,19 @@ func Test_WebhookWiki(t *testing.T) {
}
func Test_WebhookRepository(t *testing.T) {
- var payloads []api.RepositoryPayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.RepositoryPayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "repository"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.RepositoryPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.RepositoryPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "repository"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1")
@@ -495,19 +771,19 @@ func Test_WebhookRepository(t *testing.T) {
}
func Test_WebhookPackage(t *testing.T) {
- var payloads []api.PackagePayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- content, _ := io.ReadAll(r.Body)
- var payload api.PackagePayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "package"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.PackagePayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ content, _ := io.ReadAll(r.Body)
+ var payload api.PackagePayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "package"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1")
@@ -532,24 +808,24 @@ func Test_WebhookPackage(t *testing.T) {
}
func Test_WebhookStatus(t *testing.T) {
- var payloads []api.CommitStatusPayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status")
- assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository")
- assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status")
- assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository")
- assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status")
- content, _ := io.ReadAll(r.Body)
- var payload api.CommitStatusPayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "status"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.CommitStatusPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status")
+ assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository")
+ assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status")
+ assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository")
+ assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status")
+ content, _ := io.ReadAll(r.Body)
+ var payload api.CommitStatusPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "status"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -567,7 +843,7 @@ func Test_WebhookStatus(t *testing.T) {
// update a status for a commit via API
doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{
- State: api.CommitStatusSuccess,
+ State: commitstatus.CommitStatusSuccess,
TargetURL: "http://test.ci/",
Description: "",
Context: "testci",
@@ -585,16 +861,16 @@ func Test_WebhookStatus(t *testing.T) {
}
func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
- var trigger string
- provider := newMockWebhookProvider(func(r *http.Request) {
- assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status")
- assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status")
- assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status")
- trigger = "push"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var trigger string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status")
+ assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status")
+ assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status")
+ trigger = "push"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
@@ -610,22 +886,22 @@ func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
}
func Test_WebhookWorkflowJob(t *testing.T) {
- var payloads []api.WorkflowJobPayload
- var triggeredEvent string
- provider := newMockWebhookProvider(func(r *http.Request) {
- assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job")
- assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job")
- assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job")
- content, _ := io.ReadAll(r.Body)
- var payload api.WorkflowJobPayload
- err := json.Unmarshal(content, &payload)
- assert.NoError(t, err)
- payloads = append(payloads, payload)
- triggeredEvent = "workflow_job"
- }, http.StatusOK)
- defer provider.Close()
-
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ var payloads []api.WorkflowJobPayload
+ var triggeredEvent string
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job")
+ assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job")
+ assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job")
+ content, _ := io.ReadAll(r.Body)
+ var payload api.WorkflowJobPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ payloads = append(payloads, payload)
+ triggeredEvent = "workflow_job"
+ }, http.StatusOK)
+ defer provider.Close()
+
// 1. create a new webhook with special webhook for repo1
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, "user2")
@@ -685,8 +961,7 @@ jobs:
// 4. Execute a single Job
task := runner.fetchTask(t)
outcome := &mockTaskOutcome{
- result: runnerv1.Result_RESULT_SUCCESS,
- execTime: time.Millisecond,
+ result: runnerv1.Result_RESULT_SUCCESS,
}
runner.execTask(t, task, outcome)
@@ -707,8 +982,7 @@ jobs:
assert.Equal(t, commitID, payloads[3].WorkflowJob.HeadSha)
assert.Equal(t, "repo1", payloads[3].Repo.Name)
assert.Equal(t, "user2/repo1", payloads[3].Repo.FullName)
- assert.Contains(t, payloads[3].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[3].WorkflowJob.RunID, payloads[3].WorkflowJob.ID))
- assert.Contains(t, payloads[3].WorkflowJob.URL, payloads[3].WorkflowJob.RunURL)
+ assert.Contains(t, payloads[3].WorkflowJob.URL, fmt.Sprintf("/actions/jobs/%d", payloads[3].WorkflowJob.ID))
assert.Contains(t, payloads[3].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 0))
assert.Len(t, payloads[3].WorkflowJob.Steps, 1)
@@ -722,8 +996,7 @@ jobs:
// 6. Execute a single Job
task = runner.fetchTask(t)
outcome = &mockTaskOutcome{
- result: runnerv1.Result_RESULT_FAILURE,
- execTime: time.Millisecond,
+ result: runnerv1.Result_RESULT_FAILURE,
}
runner.execTask(t, task, outcome)
@@ -745,9 +1018,207 @@ jobs:
assert.Equal(t, commitID, payloads[6].WorkflowJob.HeadSha)
assert.Equal(t, "repo1", payloads[6].Repo.Name)
assert.Equal(t, "user2/repo1", payloads[6].Repo.FullName)
- assert.Contains(t, payloads[6].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[6].WorkflowJob.RunID, payloads[6].WorkflowJob.ID))
- assert.Contains(t, payloads[6].WorkflowJob.URL, payloads[6].WorkflowJob.RunURL)
+ assert.Contains(t, payloads[6].WorkflowJob.URL, fmt.Sprintf("/actions/jobs/%d", payloads[6].WorkflowJob.ID))
assert.Contains(t, payloads[6].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 1))
assert.Len(t, payloads[6].WorkflowJob.Steps, 2)
})
}
+
+type workflowRunWebhook struct {
+ URL string
+ payloads []api.WorkflowRunPayload
+ triggeredEvent string
+}
+
+func Test_WebhookWorkflowRun(t *testing.T) {
+ webhookData := &workflowRunWebhook{}
+ provider := newMockWebhookProvider(func(r *http.Request) {
+ assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_run", "X-GitHub-Event-Type should contain workflow_run")
+ assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_run", "X-Gitea-Event-Type should contain workflow_run")
+ assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_run", "X-Gogs-Event-Type should contain workflow_run")
+ content, _ := io.ReadAll(r.Body)
+ var payload api.WorkflowRunPayload
+ err := json.Unmarshal(content, &payload)
+ assert.NoError(t, err)
+ webhookData.payloads = append(webhookData.payloads, payload)
+ webhookData.triggeredEvent = "workflow_run"
+ }, http.StatusOK)
+ defer provider.Close()
+ webhookData.URL = provider.URL()
+
+ tests := []struct {
+ name string
+ callback func(t *testing.T, webhookData *workflowRunWebhook)
+ }{
+ {
+ name: "WorkflowRun",
+ callback: testWebhookWorkflowRun,
+ },
+ {
+ name: "WorkflowRunDepthLimit",
+ callback: testWebhookWorkflowRunDepthLimit,
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ webhookData.payloads = nil
+ webhookData.triggeredEvent = ""
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ test.callback(t, webhookData)
+ })
+ })
+ }
+}
+
+func testWebhookWorkflowRun(t *testing.T, webhookData *workflowRunWebhook) {
+ // 1. create a new webhook with special webhook for repo1
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ session := loginUser(t, "user2")
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
+
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run")
+
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
+
+ gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
+ assert.NoError(t, err)
+
+ runner := newMockRunner()
+ runner.registerAsRepoRunner(t, "user2", "repo1", "mock-runner", []string{"ubuntu-latest"}, false)
+
+ // 2.1 add workflow_run workflow file to the repo
+
+ opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+"dispatch.yml", `
+on:
+ workflow_run:
+ workflows: ["Push"]
+ types:
+ - completed
+jobs:
+ dispatch:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo 'test the webhook'
+`)
+ createWorkflowFile(t, token, "user2", "repo1", ".gitea/workflows/dispatch.yml", opts)
+
+ // 2.2 trigger the webhooks
+
+ // add workflow file to the repo
+ // init the workflow
+ wfTreePath := ".gitea/workflows/push.yml"
+ wfFileContent := `name: Push
+on: push
+jobs:
+ wf1-job:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo 'test the webhook'
+ wf2-job:
+ runs-on: ubuntu-latest
+ needs: wf1-job
+ steps:
+ - run: echo 'cmd 1'
+ - run: echo 'cmd 2'
+`
+ opts = getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
+ createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
+
+ commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
+ assert.NoError(t, err)
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
+ assert.Len(t, webhookData.payloads, 1)
+ assert.Equal(t, "requested", webhookData.payloads[0].Action)
+ assert.Equal(t, "queued", webhookData.payloads[0].WorkflowRun.Status)
+ assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[0].WorkflowRun.HeadBranch)
+ assert.Equal(t, commitID, webhookData.payloads[0].WorkflowRun.HeadSha)
+ assert.Equal(t, "repo1", webhookData.payloads[0].Repo.Name)
+ assert.Equal(t, "user2/repo1", webhookData.payloads[0].Repo.FullName)
+
+ // 4. Execute two Jobs
+ task := runner.fetchTask(t)
+ outcome := &mockTaskOutcome{
+ result: runnerv1.Result_RESULT_SUCCESS,
+ }
+ runner.execTask(t, task, outcome)
+
+ task = runner.fetchTask(t)
+ outcome = &mockTaskOutcome{
+ result: runnerv1.Result_RESULT_FAILURE,
+ }
+ runner.execTask(t, task, outcome)
+
+ // 7. validate the webhook is triggered
+ assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
+ assert.Len(t, webhookData.payloads, 3)
+ assert.Equal(t, "completed", webhookData.payloads[1].Action)
+ assert.Equal(t, "push", webhookData.payloads[1].WorkflowRun.Event)
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
+ assert.Len(t, webhookData.payloads, 3)
+ assert.Equal(t, "requested", webhookData.payloads[2].Action)
+ assert.Equal(t, "queued", webhookData.payloads[2].WorkflowRun.Status)
+ assert.Equal(t, "workflow_run", webhookData.payloads[2].WorkflowRun.Event)
+ assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[2].WorkflowRun.HeadBranch)
+ assert.Equal(t, commitID, webhookData.payloads[2].WorkflowRun.HeadSha)
+ assert.Equal(t, "repo1", webhookData.payloads[2].Repo.Name)
+ assert.Equal(t, "user2/repo1", webhookData.payloads[2].Repo.FullName)
+}
+
+func testWebhookWorkflowRunDepthLimit(t *testing.T, webhookData *workflowRunWebhook) {
+ // 1. create a new webhook with special webhook for repo1
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ session := loginUser(t, "user2")
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
+
+ testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run")
+
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
+
+ gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
+ assert.NoError(t, err)
+
+ // 2. trigger the webhooks
+
+ // add workflow file to the repo
+ // init the workflow
+ wfTreePath := ".gitea/workflows/push.yml"
+ wfFileContent := `name: Endless Loop
+on:
+ push:
+ workflow_run:
+ types:
+ - requested
+jobs:
+ dispatch:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo 'test the webhook'
+`
+ opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
+ createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
+
+ commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
+ assert.NoError(t, err)
+
+ // 3. validate the webhook is triggered
+ assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
+ // 1x push + 5x workflow_run requested chain
+ assert.Len(t, webhookData.payloads, 6)
+ for i := range 6 {
+ assert.Equal(t, "requested", webhookData.payloads[i].Action)
+ assert.Equal(t, "queued", webhookData.payloads[i].WorkflowRun.Status)
+ assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[i].WorkflowRun.HeadBranch)
+ assert.Equal(t, commitID, webhookData.payloads[i].WorkflowRun.HeadSha)
+ if i == 0 {
+ assert.Equal(t, "push", webhookData.payloads[i].WorkflowRun.Event)
+ } else {
+ assert.Equal(t, "workflow_run", webhookData.payloads[i].WorkflowRun.Event)
+ }
+ assert.Equal(t, "repo1", webhookData.payloads[i].Repo.Name)
+ assert.Equal(t, "user2/repo1", webhookData.payloads[i].Repo.FullName)
+ }
+}
diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go
index ce55a2f943..dc389f5680 100644
--- a/tests/integration/repofiles_change_test.go
+++ b/tests/integration/repofiles_change_test.go
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/contexttest"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -58,6 +59,40 @@ func getUpdateRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang
}
}
+func getUpdateRepoFilesRenameOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
+ return &files_service.ChangeRepoFilesOptions{
+ Files: []*files_service.ChangeRepoFile{
+ // move normally
+ {
+ Operation: "rename",
+ FromTreePath: "README.md",
+ TreePath: "README.txt",
+ },
+ // move from in lfs
+ {
+ Operation: "rename",
+ FromTreePath: "crypt.bin",
+ TreePath: "crypt1.bin",
+ },
+ // move from lfs to normal
+ {
+ Operation: "rename",
+ FromTreePath: "jpeg.jpg",
+ TreePath: "jpeg.jpeg",
+ },
+ // move from normal to lfs
+ {
+ Operation: "rename",
+ FromTreePath: "CONTRIBUTING.md",
+ TreePath: "CONTRIBUTING.md.bin",
+ },
+ },
+ OldBranch: repo.DefaultBranch,
+ NewBranch: repo.DefaultBranch,
+ Message: "Rename files",
+ }
+}
+
func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions {
return &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
@@ -120,9 +155,9 @@ func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.
Name: path.Base(treePath),
Path: treePath,
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
- LastCommitSHA: lastCommit.ID.String(),
- LastCommitterDate: lastCommit.Committer.When,
- LastAuthorDate: lastCommit.Author.When,
+ LastCommitSHA: util.ToPointer(lastCommit.ID.String()),
+ LastCommitterDate: util.ToPointer(lastCommit.Committer.When),
+ LastAuthorDate: util.ToPointer(lastCommit.Author.When),
Type: "file",
Size: 18,
Encoding: &encoding,
@@ -163,7 +198,7 @@ func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
},
},
- Message: "Updates README.md\n",
+ Message: "Creates new/file.txt\n",
Tree: &api.CommitMeta{
URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc",
@@ -190,9 +225,9 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA
Name: filename,
Path: filename,
SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647",
- LastCommitSHA: lastCommitSHA,
- LastCommitterDate: lastCommitterWhen,
- LastAuthorDate: lastAuthorWhen,
+ LastCommitSHA: util.ToPointer(lastCommitSHA),
+ LastCommitterDate: util.ToPointer(lastCommitterWhen),
+ LastAuthorDate: util.ToPointer(lastAuthorWhen),
Type: "file",
Size: 43,
Encoding: &encoding,
@@ -248,6 +283,114 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA
}
}
+func getExpectedFileResponseForRepoFilesUpdateRename(commitID, lastCommitSHA string) *api.FilesResponse {
+ details := []struct {
+ filename, sha, content string
+ size int64
+ lfsOid *string
+ lfsSize *int64
+ }{
+ {
+ filename: "README.txt",
+ sha: "8276d2a29779af982c0afa976bdb793b52d442a8",
+ size: 22,
+ content: "IyBBbiBMRlMtZW5hYmxlZCByZXBvCg==",
+ },
+ {
+ filename: "crypt1.bin",
+ sha: "d4a41a0d4db4949e129bd22f871171ea988103ef",
+ size: 129,
+ content: "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6MmVjY2RiNDM4MjVkMmE0OWQ5OWQ1NDJkYWEyMDA3NWNmZjFkOTdkOWQyMzQ5YTg5NzdlZmU5YzAzNjYxNzM3YwpzaXplIDIwNDgK",
+ lfsOid: util.ToPointer("2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c"),
+ lfsSize: util.ToPointer(int64(2048)),
+ },
+ {
+ filename: "jpeg.jpeg",
+ sha: "71911bf48766c7181518c1070911019fbb00b1fc",
+ size: 107,
+ content: "/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=",
+ },
+ {
+ filename: "CONTRIBUTING.md.bin",
+ sha: "2b6c6c4eaefa24b22f2092c3d54b263ff26feb58",
+ size: 127,
+ content: "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6N2I2YjJjODhkYmE5Zjc2MGExYTU4NDY5YjY3ZmVlMmI2OThlZjdlOTM5OWM0Y2E0ZjM0YTE0Y2NiZTM5ZjYyMwpzaXplIDI3Cg==",
+ lfsOid: util.ToPointer("7b6b2c88dba9f760a1a58469b67fee2b698ef7e9399c4ca4f34a14ccbe39f623"),
+ lfsSize: util.ToPointer(int64(27)),
+ },
+ }
+
+ var responses []*api.ContentsResponse
+ for _, detail := range details {
+ selfURL := setting.AppURL + "api/v1/repos/user2/lfs/contents/" + detail.filename + "?ref=master"
+ htmlURL := setting.AppURL + "user2/lfs/src/branch/master/" + detail.filename
+ gitURL := setting.AppURL + "api/v1/repos/user2/lfs/git/blobs/" + detail.sha
+ downloadURL := setting.AppURL + "user2/lfs/raw/branch/master/" + detail.filename
+ // don't set time related fields because there might be different time in one operation
+ responses = append(responses, &api.ContentsResponse{
+ Name: detail.filename,
+ Path: detail.filename,
+ SHA: detail.sha,
+ LastCommitSHA: util.ToPointer(lastCommitSHA),
+ Type: "file",
+ Size: detail.size,
+ Encoding: util.ToPointer("base64"),
+ Content: &detail.content,
+ URL: &selfURL,
+ HTMLURL: &htmlURL,
+ GitURL: &gitURL,
+ DownloadURL: &downloadURL,
+ Links: &api.FileLinksResponse{
+ Self: &selfURL,
+ GitURL: &gitURL,
+ HTMLURL: &htmlURL,
+ },
+ LfsOid: detail.lfsOid,
+ LfsSize: detail.lfsSize,
+ })
+ }
+
+ return &api.FilesResponse{
+ Files: responses,
+ Commit: &api.FileCommitResponse{
+ CommitMeta: api.CommitMeta{
+ URL: setting.AppURL + "api/v1/repos/user2/lfs/git/commits/" + commitID,
+ SHA: commitID,
+ },
+ HTMLURL: setting.AppURL + "user2/lfs/commit/" + commitID,
+ Author: &api.CommitUser{
+ Identity: api.Identity{
+ Name: "User Two",
+ Email: "user2@noreply.example.org",
+ },
+ },
+ Committer: &api.CommitUser{
+ Identity: api.Identity{
+ Name: "User Two",
+ Email: "user2@noreply.example.org",
+ },
+ },
+ Parents: []*api.CommitMeta{
+ {
+ URL: setting.AppURL + "api/v1/repos/user2/lfs/git/commits/73cf03db6ece34e12bf91e8853dc58f678f2f82d",
+ SHA: "73cf03db6ece34e12bf91e8853dc58f678f2f82d",
+ },
+ },
+ Message: "Rename files\n",
+ Tree: &api.CommitMeta{
+ URL: setting.AppURL + "api/v1/repos/user2/lfs/git/trees/5307376dc3a5557dc1c403c29a8984668ca9ecb5",
+ SHA: "5307376dc3a5557dc1c403c29a8984668ca9ecb5",
+ },
+ },
+ Verification: &api.PayloadCommitVerification{
+ Verified: false,
+ Reason: "gpg.error.not_signed_commit",
+ Signature: "",
+ Payload: "",
+ },
+ }
+}
+
func TestChangeRepoFilesForCreate(t *testing.T) {
// setup
onGiteaRun(t, func(t *testing.T, u *url.URL) {
@@ -369,6 +512,38 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) {
})
}
+func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ ctx, _ := contexttest.MockContext(t, "user2/lfs")
+ ctx.SetPathParam("id", "54")
+ contexttest.LoadRepo(t, ctx, 54)
+ contexttest.LoadRepoCommit(t, ctx)
+ contexttest.LoadUser(t, ctx, 2)
+ contexttest.LoadGitRepo(t, ctx)
+ defer ctx.Repo.GitRepo.Close()
+
+ repo := ctx.Repo.Repository
+ opts := getUpdateRepoFilesRenameOptions(repo)
+
+ // test
+ filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, ctx.Doer, opts)
+
+ // asserts
+ assert.NoError(t, err)
+ gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
+ defer gitRepo.Close()
+
+ commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch)
+ lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
+ expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String())
+ for _, file := range filesResponse.Files {
+ file.LastCommitterDate, file.LastAuthorDate = nil, nil // there might be different time in one operation, so we ignore them
+ }
+ assert.Len(t, filesResponse.Files, 4)
+ assert.Equal(t, expectedFileResponse.Files, filesResponse.Files)
+ })
+}
+
// Test opts with branch names removed, should get same results as above test
func TestChangeRepoFilesWithoutBranchNames(t *testing.T) {
// setup
diff --git a/tests/integration/setting_test.go b/tests/integration/setting_test.go
index 9dad9ca716..64fd28c5e9 100644
--- a/tests/integration/setting_test.go
+++ b/tests/integration/setting_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@@ -92,8 +93,7 @@ func TestSettingShowUserEmailProfile(t *testing.T) {
func TestSettingLandingPage(t *testing.T) {
defer tests.PrepareTestEnv(t)()
-
- landingPage := setting.LandingPageURL
+ defer test.MockVariableValue(&setting.LandingPageURL)()
setting.LandingPageURL = setting.LandingPageHome
req := NewRequest(t, "GET", "/")
@@ -113,6 +113,4 @@ func TestSettingLandingPage(t *testing.T) {
req = NewRequest(t, "GET", "/")
resp = MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, "/user/login", resp.Header().Get("Location"))
-
- setting.LandingPageURL = landingPage
}
diff --git a/tests/integration/signup_test.go b/tests/integration/signup_test.go
index e86851352e..c08f57d33e 100644
--- a/tests/integration/signup_test.go
+++ b/tests/integration/signup_test.go
@@ -22,8 +22,7 @@ import (
func TestSignup(t *testing.T) {
defer tests.PrepareTestEnv(t)()
-
- setting.Service.EnableCaptcha = false
+ defer test.MockVariableValue(&setting.Service.EnableCaptcha, false)()
req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
"user_name": "exampleUser",
@@ -40,9 +39,8 @@ func TestSignup(t *testing.T) {
func TestSignupAsRestricted(t *testing.T) {
defer tests.PrepareTestEnv(t)()
-
- setting.Service.EnableCaptcha = false
- setting.Service.DefaultUserIsRestricted = true
+ defer test.MockVariableValue(&setting.Service.EnableCaptcha, false)()
+ defer test.MockVariableValue(&setting.Service.DefaultUserIsRestricted, true)()
req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
"user_name": "restrictedUser",
@@ -62,8 +60,7 @@ func TestSignupAsRestricted(t *testing.T) {
func TestSignupEmailValidation(t *testing.T) {
defer tests.PrepareTestEnv(t)()
-
- setting.Service.EnableCaptcha = false
+ defer test.MockVariableValue(&setting.Service.EnableCaptcha, false)()
tests := []struct {
email string
diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go
index fbdda9b3af..b34a986be3 100644
--- a/tests/integration/ssh_key_test.go
+++ b/tests/integration/ssh_key_test.go
@@ -27,7 +27,7 @@ func doCheckRepositoryEmptyStatus(ctx APITestContext, isEmpty bool) func(*testin
func doAddChangesToCheckout(dstPath, filename string) func(*testing.T) {
return func(t *testing.T) {
- assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now())), 0o644))
+ assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), fmt.Appendf(nil, "# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now()), 0o644))
assert.NoError(t, git.AddChanges(dstPath, true))
signature := git.Signature{
Email: "test@example.com",
diff --git a/tests/integration/timetracking_test.go b/tests/integration/timetracking_test.go
index 8985dfdbce..4e8109be96 100644
--- a/tests/integration/timetracking_test.go
+++ b/tests/integration/timetracking_test.go
@@ -46,11 +46,11 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo
AssertHTMLElement(t, htmlDoc, ".issue-add-time", canTrackTime)
issueLink := path.Join(user, repo, "issues", issue)
- req = NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "toggle"), map[string]string{
+ reqStart := NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "start"), map[string]string{
"_csrf": htmlDoc.GetCSRF(),
})
if canTrackTime {
- session.MakeRequest(t, req, http.StatusOK)
+ session.MakeRequest(t, reqStart, http.StatusOK)
req = NewRequest(t, "GET", issueLink)
resp = session.MakeRequest(t, req, http.StatusOK)
@@ -65,10 +65,10 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo
// Sleep for 1 second to not get wrong order for stopping timer
time.Sleep(time.Second)
- req = NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "toggle"), map[string]string{
+ reqStop := NewRequestWithValues(t, "POST", path.Join(issueLink, "times", "stopwatch", "stop"), map[string]string{
"_csrf": htmlDoc.GetCSRF(),
})
- session.MakeRequest(t, req, http.StatusOK)
+ session.MakeRequest(t, reqStop, http.StatusOK)
req = NewRequest(t, "GET", issueLink)
resp = session.MakeRequest(t, req, http.StatusOK)
@@ -77,6 +77,6 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo
events = htmlDoc.doc.Find(".event > span.text")
assert.Contains(t, events.Last().Text(), "worked for ")
} else {
- session.MakeRequest(t, req, http.StatusNotFound)
+ session.MakeRequest(t, reqStart, http.StatusNotFound)
}
}
diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go
index 2c9a916ec2..34692d9cab 100644
--- a/tests/integration/user_test.go
+++ b/tests/integration/user_test.go
@@ -257,8 +257,8 @@ func TestListStopWatches(t *testing.T) {
}
func TestUserLocationMapLink(t *testing.T) {
- setting.Service.UserLocationMapURL = "https://example/foo/"
defer tests.PrepareTestEnv(t)()
+ defer test.MockVariableValue(&setting.Service.UserLocationMapURL, "https://example/foo/")()
session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
diff --git a/tests/integration/webfinger_test.go b/tests/integration/webfinger_test.go
index 2afaed4a45..757d442cd2 100644
--- a/tests/integration/webfinger_test.go
+++ b/tests/integration/webfinger_test.go
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@@ -20,11 +21,7 @@ import (
func TestWebfinger(t *testing.T) {
defer tests.PrepareTestEnv(t)()
-
- setting.Federation.Enabled = true
- defer func() {
- setting.Federation.Enabled = false
- }()
+ defer test.MockVariableValue(&setting.Federation.Enabled, true)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
diff --git a/tests/integration/workflow_run_api_check_test.go b/tests/integration/workflow_run_api_check_test.go
new file mode 100644
index 0000000000..6a80bb5118
--- /dev/null
+++ b/tests/integration/workflow_run_api_check_test.go
@@ -0,0 +1,167 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAPIWorkflowRun(t *testing.T) {
+ t.Run("AdminRuns", func(t *testing.T) {
+ testAPIWorkflowRunBasic(t, "/api/v1/admin/actions", "User1", 802, auth_model.AccessTokenScopeReadAdmin, auth_model.AccessTokenScopeReadRepository)
+ })
+ t.Run("UserRuns", func(t *testing.T) {
+ testAPIWorkflowRunBasic(t, "/api/v1/user/actions", "User2", 803, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
+ })
+ t.Run("OrgRuns", func(t *testing.T) {
+ testAPIWorkflowRunBasic(t, "/api/v1/orgs/org3/actions", "User1", 802, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadRepository)
+ })
+ t.Run("RepoRuns", func(t *testing.T) {
+ testAPIWorkflowRunBasic(t, "/api/v1/repos/org3/repo5/actions", "User2", 802, auth_model.AccessTokenScopeReadRepository)
+ })
+}
+
+func testAPIWorkflowRunBasic(t *testing.T, apiRootURL, userUsername string, runID int64, scope ...auth_model.AccessTokenScope) {
+ defer tests.PrepareTestEnv(t)()
+ token := getUserToken(t, userUsername, scope...)
+
+ apiRunsURL := fmt.Sprintf("%s/%s", apiRootURL, "runs")
+ req := NewRequest(t, "GET", apiRunsURL).AddTokenAuth(token)
+ runnerListResp := MakeRequest(t, req, http.StatusOK)
+ runnerList := api.ActionWorkflowRunsResponse{}
+ DecodeJSON(t, runnerListResp, &runnerList)
+
+ foundRun := false
+
+ for _, run := range runnerList.Entries {
+ // Verify filtering works
+ verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", run.Status, "", "", "", "")
+ verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, run.Conclusion, "", "", "", "", "")
+ verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", run.HeadBranch, "", "")
+ verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", run.Event, "", "", "")
+ verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, "")
+ verifyWorkflowRunCanbeFoundWithStatusFilter(t, apiRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, run.HeadSha)
+
+ // Verify run url works
+ req := NewRequest(t, "GET", run.URL).AddTokenAuth(token)
+ runResp := MakeRequest(t, req, http.StatusOK)
+ apiRun := api.ActionWorkflowRun{}
+ DecodeJSON(t, runResp, &apiRun)
+ assert.Equal(t, run.ID, apiRun.ID)
+ assert.Equal(t, run.Status, apiRun.Status)
+ assert.Equal(t, run.Conclusion, apiRun.Conclusion)
+ assert.Equal(t, run.Event, apiRun.Event)
+
+ // Verify jobs list works
+ req = NewRequest(t, "GET", fmt.Sprintf("%s/%s", run.URL, "jobs")).AddTokenAuth(token)
+ jobsResp := MakeRequest(t, req, http.StatusOK)
+ jobList := api.ActionWorkflowJobsResponse{}
+ DecodeJSON(t, jobsResp, &jobList)
+
+ if run.ID == runID {
+ foundRun = true
+ assert.Len(t, jobList.Entries, 1)
+ for _, job := range jobList.Entries {
+ // Check the jobs list of the run
+ verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", run.URL, "jobs"), token, job.ID, "", job.Status)
+ verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", run.URL, "jobs"), token, job.ID, job.Conclusion, "")
+ // Check the run independent job list
+ verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", apiRootURL, "jobs"), token, job.ID, "", job.Status)
+ verifyWorkflowJobCanbeFoundWithStatusFilter(t, fmt.Sprintf("%s/%s", apiRootURL, "jobs"), token, job.ID, job.Conclusion, "")
+
+ // Verify job url works
+ req := NewRequest(t, "GET", job.URL).AddTokenAuth(token)
+ jobsResp := MakeRequest(t, req, http.StatusOK)
+ apiJob := api.ActionWorkflowJob{}
+ DecodeJSON(t, jobsResp, &apiJob)
+ assert.Equal(t, job.ID, apiJob.ID)
+ assert.Equal(t, job.RunID, apiJob.RunID)
+ assert.Equal(t, job.Status, apiJob.Status)
+ assert.Equal(t, job.Conclusion, apiJob.Conclusion)
+ }
+ }
+ }
+ assert.True(t, foundRun, "Expected to find run with ID %d", runID)
+}
+
+func verifyWorkflowRunCanbeFoundWithStatusFilter(t *testing.T, runAPIURL, token string, id int64, conclusion, status, event, branch, actor, headSHA string) {
+ filter := url.Values{}
+ if conclusion != "" {
+ filter.Add("status", conclusion)
+ }
+ if status != "" {
+ filter.Add("status", status)
+ }
+ if event != "" {
+ filter.Set("event", event)
+ }
+ if branch != "" {
+ filter.Set("branch", branch)
+ }
+ if actor != "" {
+ filter.Set("actor", actor)
+ }
+ if headSHA != "" {
+ filter.Set("head_sha", headSHA)
+ }
+ req := NewRequest(t, "GET", runAPIURL+"?"+filter.Encode()).AddTokenAuth(token)
+ runResp := MakeRequest(t, req, http.StatusOK)
+ runList := api.ActionWorkflowRunsResponse{}
+ DecodeJSON(t, runResp, &runList)
+
+ found := false
+ for _, run := range runList.Entries {
+ if conclusion != "" {
+ assert.Equal(t, conclusion, run.Conclusion)
+ }
+ if status != "" {
+ assert.Equal(t, status, run.Status)
+ }
+ if event != "" {
+ assert.Equal(t, event, run.Event)
+ }
+ if branch != "" {
+ assert.Equal(t, branch, run.HeadBranch)
+ }
+ if actor != "" {
+ assert.Equal(t, actor, run.Actor.UserName)
+ }
+ found = found || run.ID == id
+ }
+ assert.True(t, found, "Expected to find run with ID %d", id)
+}
+
+func verifyWorkflowJobCanbeFoundWithStatusFilter(t *testing.T, runAPIURL, token string, id int64, conclusion, status string) {
+ filter := conclusion
+ if filter == "" {
+ filter = status
+ }
+ if filter == "" {
+ return
+ }
+ req := NewRequest(t, "GET", runAPIURL+"?status="+filter).AddTokenAuth(token)
+ jobListResp := MakeRequest(t, req, http.StatusOK)
+ jobList := api.ActionWorkflowJobsResponse{}
+ DecodeJSON(t, jobListResp, &jobList)
+
+ found := false
+ for _, job := range jobList.Entries {
+ if conclusion != "" {
+ assert.Equal(t, conclusion, job.Conclusion)
+ } else {
+ assert.Equal(t, status, job.Status)
+ }
+ found = found || job.ID == id
+ }
+ assert.True(t, found, "Expected to find job with ID %d", id)
+}
diff --git a/tests/test_utils.go b/tests/test_utils.go
index 4d8a8635d6..ad692058a6 100644
--- a/tests/test_utils.go
+++ b/tests/test_utils.go
@@ -1,7 +1,6 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-//nolint:forbidigo
package tests
import (
@@ -55,7 +54,7 @@ func InitTest(requireGitea bool) {
// Notice: when doing "ssh push", Gitea executes sub processes, debugger won't work for the sub processes.
giteaConf = "tests/sqlite.ini"
_ = os.Setenv("GITEA_CONF", giteaConf)
- fmt.Printf("Environment variable $GITEA_CONF not set, use default: %s\n", giteaConf)
+ _, _ = fmt.Fprintf(os.Stderr, "Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
if !setting.EnableSQLite3 {
testlogger.Fatalf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify` + "\n")
}
diff --git a/tools/generate-svg.js b/tools/generate-svg.js
index ec04bf655e..12f3db039d 100755
--- a/tools/generate-svg.js
+++ b/tools/generate-svg.js
@@ -25,6 +25,7 @@ async function processAssetsSvgFile(file, {prefix, fullName} = {}) {
plugins: [
{name: 'preset-default'},
{name: 'removeDimensions'},
+ {name: 'removeTitle'},
{name: 'prefixIds', params: {prefix: () => name}},
{name: 'addClassesToSVGElement', params: {classNames: ['svg', name]}},
{
diff --git a/tools/lint-go-gopls.sh b/tools/lint-go-gopls.sh
index a222ea14d7..2cd26ca6fe 100755
--- a/tools/lint-go-gopls.sh
+++ b/tools/lint-go-gopls.sh
@@ -11,7 +11,7 @@ IGNORE_PATTERNS=(
# current absolute path, indicating a error was found. This is necessary
# because the tool does not set non-zero exit code when errors are found.
# ref: https://github.com/golang/go/issues/67078
-ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}"));
+ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check -severity=warning "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}"));
NUM_ERRORS=$(echo -n "$ERROR_LINES" | wc -l)
if [ "$NUM_ERRORS" -eq "0" ]; then
diff --git a/web_src/css/actions.css b/web_src/css/actions.css
index 665893a287..c43ebe21a0 100644
--- a/web_src/css/actions.css
+++ b/web_src/css/actions.css
@@ -59,6 +59,7 @@
.run-list-ref {
display: inline-block !important;
+ max-width: 105px;
}
@media (max-width: 767.98px) {
diff --git a/web_src/css/base.css b/web_src/css/base.css
index a28fb7b92a..529ddd5386 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -30,6 +30,15 @@
--page-spacing: 16px; /* space between page elements */
--page-margin-x: 32px; /* minimum space on left and right side of page */
--page-space-bottom: 64px; /* space between last page element and footer */
+
+ /* z-index */
+ --z-index-modal: 1001; /* modal dialog, hard-coded from Fomantic modal.css */
+ --z-index-toast: 1002; /* should be larger than modal */
+
+ --font-size-label: 12px; /* font size of individual labels */
+
+ --gap-inline: 0.25rem; /* gap for inline texts and elements, for example: the spaces for sentence with labels, button text, etc */
+ --gap-block: 0.25rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */
}
@media (min-width: 768px) and (max-width: 1200px) {
@@ -318,6 +327,16 @@ a.label,
background: var(--color-hover);
}
+.empty-placeholder {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-top: 40px;
+ padding-bottom: 40px;
+ text-align: center;
+ text-wrap: balance;
+}
+
.inline-code-block {
padding: 2px 4px;
border-radius: .24em;
@@ -815,10 +834,6 @@ overflow-menu .ui.label {
display: block;
}
-.code-view .lines-num span::after {
- cursor: pointer;
-}
-
.lines-type-marker {
vertical-align: top;
white-space: nowrap;
@@ -855,39 +870,13 @@ overflow-menu .ui.label {
.lines-escape {
width: 0;
white-space: nowrap;
+ padding: 0;
}
.lines-code {
padding-left: 5px;
}
-.file-view tr.active {
- color: inherit !important;
- background: inherit !important;
-}
-
-.file-view tr.active .lines-num,
-.file-view tr.active .lines-code {
- background: var(--color-highlight-bg) !important;
-}
-
-.file-view tr.active:last-of-type .lines-code {
- border-bottom-right-radius: var(--border-radius);
-}
-
-.file-view tr.active .lines-num {
- position: relative;
-}
-
-.file-view tr.active .lines-num::before {
- content: "";
- position: absolute;
- left: 0;
- width: 2px;
- height: 100%;
- background: var(--color-highlight-fg);
-}
-
.code-inner {
font: 12px var(--fonts-monospace);
white-space: pre-wrap;
@@ -938,12 +927,12 @@ overflow-menu .ui.label {
margin-right: 4px;
}
-.top-line-blame {
+tr.top-line-blame {
border-top: 1px solid var(--color-secondary);
}
-.code-view tr.top-line-blame:first-of-type {
- border-top: none;
+tr.top-line-blame:first-of-type {
+ border-top: none; /* merge code lines belonging to the same commit into one block */
}
.lines-code .bottom-line,
@@ -951,15 +940,6 @@ overflow-menu .ui.label {
border-bottom: 1px solid var(--color-secondary);
}
-.code-view {
- background: var(--color-code-bg);
- border-radius: var(--border-radius);
-}
-
-.code-view table {
- width: 100%;
-}
-
.migrate .svg.gitea-git {
color: var(--color-git);
}
@@ -1118,7 +1098,7 @@ table th[data-sortt-desc] .svg {
.flex-text-inline {
display: inline-flex;
align-items: center;
- gap: .25rem;
+ gap: var(--gap-inline);
vertical-align: middle;
min-width: 0; /* make ellipsis work */
}
@@ -1146,7 +1126,7 @@ table th[data-sortt-desc] .svg {
.flex-text-block {
display: flex;
align-items: center;
- gap: .5rem;
+ gap: var(--gap-block);
min-width: 0;
}
@@ -1161,7 +1141,7 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil
.ui.dropdown .menu.flex-items-menu > .item:not(.hidden, .filtered, .tw-hidden) {
display: flex !important;
align-items: center;
- gap: .5rem;
+ gap: var(--gap-block);
min-width: 0;
}
.ui.dropdown .menu.flex-items-menu > .item img,
diff --git a/web_src/css/editor/combomarkdowneditor.css b/web_src/css/editor/combomarkdowneditor.css
index 835286b795..046010c6c8 100644
--- a/web_src/css/editor/combomarkdowneditor.css
+++ b/web_src/css/editor/combomarkdowneditor.css
@@ -100,67 +100,3 @@
border-bottom: 1px solid var(--color-secondary);
padding-bottom: 1rem;
}
-
-text-expander {
- display: block;
- position: relative;
-}
-
-text-expander .suggestions {
- position: absolute;
- min-width: 180px;
- padding: 0;
- margin-top: 24px;
- list-style: none;
- background: var(--color-box-body);
- border-radius: var(--border-radius);
- border: 1px solid var(--color-secondary);
- box-shadow: 0 .5rem 1rem var(--color-shadow);
- z-index: 100; /* needs to be > 20 to be on top of dropzone's .dz-details */
-}
-
-text-expander .suggestions li {
- display: flex;
- align-items: center;
- cursor: pointer;
- padding: 4px 8px;
- font-weight: var(--font-weight-medium);
-}
-
-text-expander .suggestions li + li {
- border-top: 1px solid var(--color-secondary-alpha-40);
-}
-
-text-expander .suggestions li:first-child {
- border-radius: var(--border-radius) var(--border-radius) 0 0;
-}
-
-text-expander .suggestions li:last-child {
- border-radius: 0 0 var(--border-radius) var(--border-radius);
-}
-
-text-expander .suggestions li:only-child {
- border-radius: var(--border-radius);
-}
-
-text-expander .suggestions li:hover {
- background: var(--color-hover);
-}
-
-text-expander .suggestions .fullname {
- font-weight: var(--font-weight-normal);
- margin-left: 4px;
- color: var(--color-text-light-1);
-}
-
-text-expander .suggestions li[aria-selected="true"],
-text-expander .suggestions li[aria-selected="true"] span {
- background: var(--color-primary);
- color: var(--color-primary-contrast);
-}
-
-text-expander .suggestions img {
- width: 24px;
- height: 24px;
- margin-right: 8px;
-}
diff --git a/web_src/css/features/expander.css b/web_src/css/features/expander.css
new file mode 100644
index 0000000000..f560b2a9fd
--- /dev/null
+++ b/web_src/css/features/expander.css
@@ -0,0 +1,96 @@
+text-expander .suggestions,
+.tribute-container {
+ position: absolute;
+ max-height: min(300px, 95vh);
+ max-width: min(500px, 95vw);
+ overflow-x: hidden;
+ overflow-y: auto;
+ white-space: nowrap;
+ background: var(--color-menu);
+ box-shadow: 0 6px 18px var(--color-shadow);
+ border-radius: var(--border-radius);
+ border: 1px solid var(--color-secondary);
+ z-index: 100; /* needs to be > 20 to be on top of dropzone's .dz-details */
+}
+
+text-expander {
+ display: block;
+ position: relative;
+}
+
+text-expander .suggestions {
+ padding: 0;
+ margin-top: 24px;
+ list-style: none;
+}
+
+text-expander .suggestions li,
+.tribute-item {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ gap: 6px;
+ font-weight: var(--font-weight-medium);
+}
+
+text-expander .suggestions li,
+.tribute-container li {
+ padding: 3px 6px;
+}
+
+text-expander .suggestions li + li,
+.tribute-container li + li {
+ border-top: 1px solid var(--color-secondary);
+}
+
+text-expander .suggestions li:first-child {
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
+}
+
+text-expander .suggestions li:last-child {
+ border-radius: 0 0 var(--border-radius) var(--border-radius);
+}
+
+text-expander .suggestions li:only-child {
+ border-radius: var(--border-radius);
+}
+
+text-expander .suggestions .fullname,
+.tribute-container li .fullname {
+ font-weight: var(--font-weight-normal);
+ color: var(--color-text-light-1);
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+text-expander .suggestions li:hover,
+text-expander .suggestions li:hover *,
+text-expander .suggestions li[aria-selected="true"],
+text-expander .suggestions li[aria-selected="true"] *,
+.tribute-container li.highlight,
+.tribute-container li.highlight * {
+ background: var(--color-primary);
+ color: var(--color-primary-contrast);
+}
+
+text-expander .suggestions img,
+.tribute-item img {
+ width: 21px;
+ height: 21px;
+ object-fit: contain;
+ aspect-ratio: 1;
+}
+
+.tribute-container {
+ display: block;
+}
+
+.tribute-container ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.tribute-container li.no-match {
+ cursor: default;
+}
diff --git a/web_src/css/features/gitgraph.css b/web_src/css/features/gitgraph.css
index 865c82e003..8bdafc3c99 100644
--- a/web_src/css/features/gitgraph.css
+++ b/web_src/css/features/gitgraph.css
@@ -10,14 +10,6 @@
align-items: center;
}
-#git-graph-container .color-buttons {
- margin-right: 0;
-}
-
-#git-graph-container .ui.header.dividing {
- padding-bottom: 10px;
-}
-
#git-graph-container #flow-select-refs-dropdown {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
@@ -31,25 +23,6 @@
align-items: center;
}
-#git-graph-container #flow-select-refs-dropdown .ui.label .truncate {
- display: inline-block;
- max-width: 140px;
- overflow: hidden;
- text-overflow: ellipsis;
- vertical-align: top;
- white-space: nowrap;
-}
-
-#git-graph-container #flow-select-refs-dropdown .default.text {
- padding-top: 4px;
- padding-bottom: 4px;
-}
-
-#git-graph-container #flow-select-refs-dropdown input.search {
- position: relative;
- top: 1px;
-}
-
#git-graph-container li {
list-style-type: none;
height: 24px;
@@ -57,16 +30,20 @@
white-space: nowrap;
display: flex;
align-items: center;
- gap: 0.25em;
+ gap: 0.5em;
}
-#git-graph-container li .ui.label.commit-id-short {
- padding-top: 2px;
- padding-bottom: 2px;
+#git-graph-container li > span {
+ flex-shrink: 0;
}
-#git-graph-container li .node-relation {
- font-family: var(--fonts-monospace);
+#git-graph-container li > span.message {
+ flex-shrink: 1;
+}
+
+#git-graph-container li .ui.label.commit-id-short {
+ padding: 2px 4px;
+ height: 20px;
}
#git-graph-container li .author {
@@ -78,17 +55,6 @@
font-size: 80%;
}
-#git-graph-container li a:not(.ui):hover {
- text-decoration: underline;
-}
-
-#git-graph-container li a em {
- color: var(--color-red);
- border-bottom: 1px dotted var(--color-secondary);
- text-decoration: none;
- font-style: normal;
-}
-
#git-graph-container #rel-container {
max-width: 30%;
overflow-x: auto;
@@ -105,34 +71,23 @@
width: 100%;
}
-#git-graph-container #rev-list li.highlight.hover {
- background-color: var(--color-secondary-alpha-30);
-}
-
-#git-graph-container #rev-list .commit-refs .button {
+#git-graph-container li .commit-refs .ui.button,
+#git-graph-container li .commit-refs .ui.label.tag-label {
padding: 2px 4px;
margin-right: 0.25em;
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
+ line-height: var(--line-height-default);
min-height: 0;
}
-#git-graph-container #graph-raw-list {
- margin: 0;
-}
-
#git-graph-container.monochrome #rel-container .flow-group {
stroke: var(--color-secondary-dark-5);
fill: var(--color-secondary-dark-5);
}
-#git-graph-container.monochrome #rel-container .flow-group.highlight {
- stroke: var(--color-secondary-dark-12);
- fill: var(--color-secondary-dark-12);
-}
-
#git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-1 {
stroke: #499a37;
fill: #499a37;
diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css
index 80c9d89638..7fd5150970 100644
--- a/web_src/css/features/projects.css
+++ b/web_src/css/features/projects.css
@@ -5,12 +5,13 @@
flex-wrap: nowrap;
overflow: auto;
margin: 0 0.5em;
+ min-height: max(calc(100vh - 400px), 300px);
max-height: calc(100vh - 120px);
}
.project-header {
padding: 0.5em 0;
- overflow-x: auto; /* in fullscreen mode, the position is fixed, so we can't use "flex wrap" which would change the height */
+ flex-wrap: wrap;
}
.project-header h2 {
@@ -101,17 +102,11 @@
opacity: 0;
}
-.fullscreen.projects-view .project-header {
- position: fixed;
- z-index: 1000;
- top: 0;
- left: 0;
- right: 0;
- padding: 0.5em;
- width: 100%;
- max-width: 100%;
- background-color: var(--color-body);
- border-bottom: 1px solid var(--color-secondary);
+.fullscreen.projects-view {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
}
/* Hide project-description in full-screen due to its variable height. No need to show it for better space use. */
@@ -120,9 +115,7 @@
}
.fullscreen.projects-view #project-board {
- position: absolute;
- top: 60px;
- left: 0;
- right: 0;
- max-height: calc(100vh - 70px);
+ flex: 1;
+ max-height: unset;
+ padding-bottom: 0.5em;
}
diff --git a/web_src/css/features/tribute.css b/web_src/css/features/tribute.css
deleted file mode 100644
index 99a026b9bc..0000000000
--- a/web_src/css/features/tribute.css
+++ /dev/null
@@ -1,32 +0,0 @@
-@import "tributejs/dist/tribute.css";
-
-.tribute-container {
- box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.25);
- border-radius: var(--border-radius);
-}
-
-.tribute-container ul {
- margin-top: 0 !important;
- background: var(--color-body) !important;
-}
-
-.tribute-container li {
- padding: 3px 0.5rem !important;
-}
-
-.tribute-container li span.fullname {
- font-weight: var(--font-weight-normal);
- font-size: 0.8rem;
-}
-
-.tribute-container li.highlight,
-.tribute-container li:hover {
- background: var(--color-primary) !important;
- color: var(--color-primary-contrast) !important;
-}
-
-.tribute-item {
- display: flex;
- align-items: center;
- gap: 6px;
-}
diff --git a/web_src/css/form.css b/web_src/css/form.css
index cf8fe96bea..c51eba1bc9 100644
--- a/web_src/css/form.css
+++ b/web_src/css/form.css
@@ -220,6 +220,7 @@ textarea:focus,
color: var(--color-secondary-dark-5);
padding-bottom: 0.6em;
display: inline-block;
+ text-wrap: balance;
}
.m-captcha-style {
diff --git a/web_src/css/home.css b/web_src/css/home.css
index 77d2ecf92b..195d1f5d96 100644
--- a/web_src/css/home.css
+++ b/web_src/css/home.css
@@ -21,7 +21,7 @@
}
.home .hero .svg {
- color: var(--color-green);
+ color: var(--color-logo);
height: 40px;
width: 50px;
vertical-align: bottom;
@@ -40,7 +40,7 @@
}
.home a {
- color: var(--color-green);
+ color: var(--color-logo);
}
.page-footer {
diff --git a/web_src/css/index.css b/web_src/css/index.css
index 84795d6d27..291cd04b2b 100644
--- a/web_src/css/index.css
+++ b/web_src/css/index.css
@@ -39,7 +39,7 @@
@import "./features/imagediff.css";
@import "./features/codeeditor.css";
@import "./features/projects.css";
-@import "./features/tribute.css";
+@import "./features/expander.css";
@import "./features/cropper.css";
@import "./features/console.css";
@@ -62,7 +62,7 @@
@import "./repo/issue-label.css";
@import "./repo/issue-list.css";
@import "./repo/list-header.css";
-@import "./repo/linebutton.css";
+@import "./repo/file-view.css";
@import "./repo/wiki.css";
@import "./repo/header.css";
@import "./repo/home.css";
@@ -70,6 +70,7 @@
@import "./repo/reactions.css";
@import "./repo/clone.css";
@import "./repo/commit-sign.css";
+@import "./repo/packages.css";
@import "./editor/fileeditor.css";
@import "./editor/combomarkdowneditor.css";
diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css
index ace028b4d0..c6a89edf25 100644
--- a/web_src/css/markup/content.css
+++ b/web_src/css/markup/content.css
@@ -2,7 +2,7 @@
overflow: hidden;
font-size: 16px;
line-height: 1.5 !important;
- overflow-wrap: anywhere;
+ overflow-wrap: break-word;
}
.markup > *:first-child {
@@ -134,6 +134,13 @@
margin-bottom: 16px;
}
+/* override p:last-child from base.css.
+Fomantic assumes that <p>/<hX> elements only have margins between elements, but not for the first's top or last's bottom.
+In markup content, we always use bottom margin for all elements */
+.markup p:last-child {
+ margin-bottom: 16px;
+}
+
.markup hr {
height: 4px;
padding: 0;
@@ -309,10 +316,18 @@
box-sizing: initial;
}
+.file-view.markup {
+ padding: 1em 2em;
+}
+
+.file-view.markup:has(.file-not-rendered-prompt) {
+ padding: 0; /* let the file-not-rendered-prompt layout itself */
+}
+
/* this background ensures images can break <hr>. We can only do this on
cases where the background is known and not transparent. */
-.markup.file-view img,
-.markup.file-view video,
+.file-view.markup img,
+.file-view.markup video,
.comment-body .markup img, /* regular comment */
.comment-body .markup video,
.comment-content .markup img, /* code comment */
diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css
index 8edf31ddbd..deaaf83680 100644
--- a/web_src/css/modules/animations.css
+++ b/web_src/css/modules/animations.css
@@ -52,8 +52,7 @@ form.single-button-form.is-loading .button {
}
.markup pre.is-loading,
-.editor-loading.is-loading,
-.pdf-content.is-loading {
+.editor-loading.is-loading {
height: var(--height-loading);
}
diff --git a/web_src/css/modules/breadcrumb.css b/web_src/css/modules/breadcrumb.css
index ca488c2150..77e31ef627 100644
--- a/web_src/css/modules/breadcrumb.css
+++ b/web_src/css/modules/breadcrumb.css
@@ -1,14 +1,10 @@
.breadcrumb {
display: flex;
- flex-wrap: wrap;
align-items: center;
gap: 3px;
+ overflow-wrap: anywhere;
}
.breadcrumb .breadcrumb-divider {
color: var(--color-text-light-2);
}
-
-.breadcrumb > * {
- display: inline;
-}
diff --git a/web_src/css/modules/dimmer.css b/web_src/css/modules/dimmer.css
index 8924821370..7d1ca6171a 100644
--- a/web_src/css/modules/dimmer.css
+++ b/web_src/css/modules/dimmer.css
@@ -20,7 +20,7 @@
opacity: 1;
}
-.ui.dimmer > * {
+.ui.dimmer > .ui.modal {
position: static;
margin-top: auto !important;
margin-bottom: auto !important;
diff --git a/web_src/css/modules/label.css b/web_src/css/modules/label.css
index 1e42668aa1..f5d0decdf6 100644
--- a/web_src/css/modules/label.css
+++ b/web_src/css/modules/label.css
@@ -4,25 +4,20 @@
.ui.label {
display: inline-flex;
align-items: center;
- gap: .25rem;
- min-width: 0;
vertical-align: middle;
- line-height: 1;
+ gap: var(--gap-inline);
+ min-width: 0;
+ max-width: 100%;
background: var(--color-label-bg);
color: var(--color-label-text);
- padding: 0.3em 0.5em;
- font-size: 0.85714286rem;
+ padding: 2px 6px;
+ font-size: var(--font-size-label);
font-weight: var(--font-weight-medium);
border: 0 solid transparent;
- border-radius: 0.28571429rem;
+ border-radius: var(--border-radius);
white-space: nowrap;
-}
-
-.ui.label:first-child {
- margin-left: 0;
-}
-.ui.label:last-child {
- margin-right: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
a.ui.label {
@@ -94,10 +89,6 @@ a.ui.label:hover {
color: var(--color-label-text);
}
-.ui.label.visible:not(.dropdown) {
- display: inline-block !important;
-}
-
.ui.basic.label {
background: var(--color-button);
border: 1px solid var(--color-light-border);
@@ -292,3 +283,58 @@ a.ui.ui.ui.basic.grey.label:hover {
.ui.large.label {
font-size: 1rem;
}
+
+/* To let labels break up and wrap across multiple lines (issue title, comment event), use "display: contents here" to apply parent layout.
+If the labels-list itself needs some layouts, use extra classes or "tw" helpers. */
+.labels-list {
+ display: contents;
+ font-size: var(--font-size-label); /* it must match the label font size, otherwise the height mismatches */
+}
+
+.labels-list a {
+ max-width: 100%; /* for ellipsis */
+}
+
+.labels-list .ui.label {
+ min-height: 20px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.with-labels-list-inline .labels-list .ui.label + .ui.label {
+ margin-left: var(--gap-inline);
+}
+
+.with-labels-list-inline .labels-list .ui.label {
+ line-height: var(--line-height-default);
+}
+
+/* Scoped labels with different colors on left and right */
+.ui.label.scope-parent {
+ background: none !important;
+ padding: 0 !important;
+ gap: 0 !important;
+}
+
+.ui.label.scope-parent > .ui.label {
+ margin: 0 !important; /* scoped label's margin is handled by the parent */
+}
+
+.ui.label.scope-left {
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.ui.label.scope-middle {
+ border-radius: 0;
+}
+
+.ui.label.scope-right {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.ui.label.archived-label {
+ filter: grayscale(0.5);
+ opacity: 0.5;
+}
diff --git a/web_src/css/modules/list.css b/web_src/css/modules/list.css
index 73760390de..46422cb97d 100644
--- a/web_src/css/modules/list.css
+++ b/web_src/css/modules/list.css
@@ -5,7 +5,6 @@
list-style-type: none;
margin: 1em 0;
padding: 0;
- font-size: 1em;
}
.ui.list:first-child {
diff --git a/web_src/css/modules/navbar.css b/web_src/css/modules/navbar.css
index b09b271ad4..149766a586 100644
--- a/web_src/css/modules/navbar.css
+++ b/web_src/css/modules/navbar.css
@@ -101,19 +101,6 @@
}
}
-#navbar .ui.dropdown .navbar-profile-admin {
- display: block;
- position: absolute;
- font-size: 9px;
- font-weight: var(--font-weight-bold);
- color: var(--color-nav-bg);
- background: var(--color-primary);
- padding: 2px 3px;
- border-radius: 10px;
- top: -1px;
- left: 18px;
-}
-
#navbar a.item:hover .notification_count,
#navbar a.item:hover .header-stopwatch-dot {
border-color: var(--color-nav-hover-bg);
@@ -129,8 +116,8 @@
background: var(--color-primary);
border: 2px solid var(--color-nav-bg);
position: absolute;
- left: 6px;
- top: -9px;
+ left: calc(100% - 9px);
+ bottom: calc(100% - 9px);
min-width: 17px;
height: 17px;
border-radius: 11px; /* (height + 2 * borderThickness) / 2 */
diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css
index 1145f3b1b5..330d3b176e 100644
--- a/web_src/css/modules/toast.css
+++ b/web_src/css/modules/toast.css
@@ -3,7 +3,7 @@
position: fixed;
opacity: 0;
transition: all .2s ease;
- z-index: 500;
+ z-index: var(--z-index-toast);
border-radius: var(--border-radius);
box-shadow: 0 8px 24px var(--color-shadow);
display: flex;
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 306db34300..a72709c382 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -73,10 +73,21 @@
overflow: hidden;
}
+.issue-content-right .ui.list {
+ margin: 0.5em 0;
+ max-width: 100%;
+}
+
.issue-sidebar-combo > .ui.dropdown .item:not(.checked) .item-check-mark {
visibility: hidden;
}
+.issue-content-right .ui.list.labels-list {
+ display: flex;
+ gap: var(--gap-inline);
+ flex-wrap: wrap;
+}
+
@media (max-width: 767.98px) {
.issue-content-left,
.issue-content-right {
@@ -139,11 +150,6 @@ td .commit-summary {
}
}
-.repo-path {
- display: flex;
- overflow-wrap: anywhere;
-}
-
.repository.file.list .non-diff-file-content .header .icon {
font-size: 1em;
}
@@ -177,42 +183,6 @@ td .commit-summary {
cursor: default;
}
-.view-raw {
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.view-raw > * {
- max-width: 100%;
-}
-
-.view-raw audio,
-.view-raw video,
-.view-raw img {
- margin: 1rem 0;
- border-radius: 0;
- object-fit: contain;
-}
-
-.view-raw img[src$=".svg" i] {
- max-height: 600px !important;
- max-width: 600px !important;
-}
-
-.pdf-content {
- width: 100%;
- height: 600px;
- border: none !important;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.pdf-content .pdf-fallback-button {
- margin: 50px auto;
-}
-
.repository.file.list .non-diff-file-content .plain-text {
padding: 1em 2em;
}
@@ -235,10 +205,6 @@ td .commit-summary {
padding: 0 !important;
}
-.non-diff-file-content .pdfobject {
- border-radius: 0 0 var(--border-radius) var(--border-radius);
-}
-
.repo-editor-header {
width: 100%;
}
@@ -523,7 +489,7 @@ td .commit-summary {
.repository.view.issue .comment-list .timeline-item,
.repository.view.issue .comment-list .timeline-item-group {
- padding: 16px 0;
+ padding: 8px 0;
}
.repository.view.issue .comment-list .timeline-item-group .timeline-item {
@@ -577,6 +543,11 @@ td .commit-summary {
justify-content: center;
}
+.repository.view.issue .comment-list .timeline-item.commits-list .badge {
+ margin-right: 0;
+ height: 28px;
+}
+
.repository.view.issue .comment-list .timeline-item .badge .svg {
width: 22px;
height: 22px;
@@ -601,10 +572,6 @@ td .commit-summary {
padding-top: 0;
}
-.repository.view.issue .comment-list .timeline-item.commits-list .ui.avatar {
- margin-right: 0.25em;
-}
-
.repository.view.issue .comment-list .timeline-item.event > .commit-status-link {
float: right;
margin-right: 8px;
@@ -1226,33 +1193,6 @@ td .commit-summary {
font-weight: var(--font-weight-normal);
}
-.empty-placeholder {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding-top: 40px;
- padding-bottom: 40px;
-}
-
-.repository.packages .file-size {
- white-space: nowrap;
-}
-
-.file-view.markup {
- padding: 1em 2em;
-}
-
-.file-view.markup:has(.file-not-rendered-prompt) {
- padding: 0; /* let the file-not-rendered-prompt layout itself */
-}
-
-.file-not-rendered-prompt {
- padding: 1rem;
- text-align: center;
- font-size: 1rem !important; /* use consistent styles for various containers (code, markup, etc) */
- line-height: var(--line-height-default) !important; /* same as above */
-}
-
.repository .activity-header {
display: flex;
justify-content: space-between;
@@ -1575,49 +1515,6 @@ td .commit-summary {
border-bottom-right-radius: 4px;
}
-.labels-list {
- display: inline-flex;
- flex-wrap: wrap;
- gap: 2.5px;
- align-items: center;
-}
-
-.labels-list .label, .scope-parent > .label {
- padding: 0 6px;
- min-height: 20px;
- line-height: 1.3; /* there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly */
-}
-
-/* Scoped labels with different colors on left and right */
-.ui.label.scope-parent {
- background: none !important;
- padding: 0 !important;
- gap: 0 !important;
-}
-
-.archived-label {
- filter: grayscale(0.5);
- opacity: 0.5;
-}
-
-.ui.label.scope-left {
- border-bottom-right-radius: 0;
- border-top-right-radius: 0;
- margin-right: 0;
-}
-
-.ui.label.scope-middle {
- border-radius: 0;
- margin-left: 0;
- margin-right: 0;
-}
-
-.ui.label.scope-right {
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- margin-left: 0;
-}
-
.repo-button-row {
margin: 8px 0;
display: flex;
@@ -1865,6 +1762,7 @@ tbody.commit-list {
border-radius: 0;
display: flex;
flex-direction: column;
+ gap: 0.5em;
}
/* fomantic's last-child selector does not work with hidden last child */
@@ -2054,10 +1952,6 @@ tbody.commit-list {
box-shadow: 0 0.5rem 1rem var(--color-shadow) !important;
}
-.migrate-entry .description {
- text-wrap: balance;
-}
-
.commits-table .commits-table-right form {
display: flex;
align-items: center;
diff --git a/web_src/css/repo/file-view.css b/web_src/css/repo/file-view.css
new file mode 100644
index 0000000000..907f136afe
--- /dev/null
+++ b/web_src/css/repo/file-view.css
@@ -0,0 +1,92 @@
+.file-view tr.active .lines-num,
+.file-view tr.active .lines-escape,
+.file-view tr.active .lines-code {
+ background: var(--color-highlight-bg);
+}
+
+/* set correct border radius on the last active lines, to avoid border overflow */
+.file-view tr.active:last-of-type .lines-code {
+ border-bottom-right-radius: var(--border-radius);
+}
+
+.file-view tr.active .lines-num {
+ position: relative;
+}
+
+/* add a darker "handler" at the beginning of the active line */
+.file-view tr.active .lines-num::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ width: 2px;
+ height: 100%;
+ background: var(--color-highlight-fg);
+}
+
+.file-view .file-not-rendered-prompt {
+ padding: 1rem;
+ text-align: center;
+ font-size: 1rem !important; /* use consistent styles for various containers (code, markup, etc) */
+ line-height: var(--line-height-default) !important; /* same as above */
+}
+
+/* ".code-view" is always used with ".file-view", to show the code of a file */
+.file-view.code-view {
+ background: var(--color-code-bg);
+ border-radius: var(--border-radius);
+}
+
+.file-view.code-view table {
+ width: 100%;
+}
+
+.file-view.code-view .lines-num span::after {
+ cursor: pointer;
+}
+
+.file-view.code-view .lines-num:hover {
+ color: var(--color-text-dark);
+}
+
+.file-view.code-view .ui.button.code-line-button {
+ border: 1px solid var(--color-secondary);
+ padding: 1px 4px;
+ margin: 0;
+ min-height: 0;
+ position: absolute;
+ left: 6px;
+}
+
+.file-view.code-view .ui.button.code-line-button:hover {
+ background: var(--color-secondary);
+}
+
+.view-raw {
+ display: flex;
+ justify-content: center;
+}
+
+.view-raw > * {
+ max-width: 100%;
+}
+
+.view-raw audio,
+.view-raw video,
+.view-raw img {
+ margin: 1rem;
+ border-radius: 0;
+ object-fit: contain;
+}
+
+.view-raw img[src$=".svg" i] {
+ max-height: 600px !important;
+ max-width: 600px !important;
+}
+
+.file-view-render-container {
+ width: 100%;
+}
+
+.file-view-render-container :last-child {
+ border-radius: 0 0 var(--border-radius) var(--border-radius); /* to match the "ui segment" bottom radius */
+}
diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css
index 61b0a1f962..ee371f1b1c 100644
--- a/web_src/css/repo/home.css
+++ b/web_src/css/repo/home.css
@@ -67,6 +67,7 @@
.repo-view-content {
flex: 1;
+ min-width: 0;
}
.language-stats {
diff --git a/web_src/css/repo/issue-card.css b/web_src/css/repo/issue-card.css
index fb832bd05a..327919b1fe 100644
--- a/web_src/css/repo/issue-card.css
+++ b/web_src/css/repo/issue-card.css
@@ -7,6 +7,7 @@
padding: 8px 10px;
border: 1px solid var(--color-secondary);
background: var(--color-card);
+ color: var(--color-text); /* it can't inherit from parent because the card already has its own background */
}
.issue-card-icon,
@@ -28,13 +29,16 @@
display: flex;
width: 100%;
justify-content: space-between;
- gap: 0.25em;
+ gap: 1em;
}
-.issue-card-assignees {
+.issue-card-bottom-part {
display: flex;
+ flex: 1;
align-items: center;
gap: 0.25em;
- justify-content: end;
flex-wrap: wrap;
+ overflow: hidden;
+ max-width: fit-content;
+ max-height: fit-content;
}
diff --git a/web_src/css/repo/issue-label.css b/web_src/css/repo/issue-label.css
index 0a25d31da9..f75c73b50f 100644
--- a/web_src/css/repo/issue-label.css
+++ b/web_src/css/repo/issue-label.css
@@ -4,41 +4,46 @@
margin: 0;
}
-.issue-label-list .item {
+.issue-label-list > .item {
border-bottom: 1px solid var(--color-secondary);
display: flex;
padding: 1em 0;
margin: 0;
}
-.issue-label-list .item:first-child {
+.issue-label-list > .item:first-child {
padding-top: 0;
}
-.issue-label-list .item:last-child {
+.issue-label-list > .item:last-child {
border-bottom: none;
padding-bottom: 0;
}
-.issue-label-list .item .label-title {
+.issue-label-list > .item .label-title {
width: 33%;
+ padding-right: 1em;
}
-.issue-label-list .item .label-issues {
+.issue-label-list > .item .label-issues {
width: 33%;
+ padding-right: 1em;
}
-.issue-label-list .item .label-operation {
+.issue-label-list > .item .label-operation {
width: 33%;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5em;
+ justify-content: end;
+ align-items: center;
}
-.issue-label-list .item a {
+.issue-label-list > .item .label-operation a {
font-size: 12px;
- padding-right: 10px;
- color: var(--color-text-light);
}
-.issue-label-list .item.org-label {
+.issue-label-list > .item.org-label {
opacity: 0.7;
}
diff --git a/web_src/css/repo/linebutton.css b/web_src/css/repo/linebutton.css
deleted file mode 100644
index f7e3d48b48..0000000000
--- a/web_src/css/repo/linebutton.css
+++ /dev/null
@@ -1,16 +0,0 @@
-.code-view .lines-num:hover {
- color: var(--color-text-dark) !important;
-}
-
-.ui.button.code-line-button {
- border: 1px solid var(--color-secondary);
- padding: 1px 4px;
- margin: 0;
- min-height: 0;
- position: absolute;
- left: 6px;
-}
-
-.ui.button.code-line-button:hover {
- background: var(--color-secondary);
-}
diff --git a/web_src/css/repo/packages.css b/web_src/css/repo/packages.css
new file mode 100644
index 0000000000..75675f5243
--- /dev/null
+++ b/web_src/css/repo/packages.css
@@ -0,0 +1,25 @@
+.packages-content {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+}
+
+.packages-content-left {
+ margin: 0 !important;
+ width: calc(100% - 250px - 16px);
+}
+
+.packages-content-right {
+ margin: 0 !important;
+ width: 250px;
+}
+
+@media (max-width: 767.98px) {
+ .packages-content {
+ flex-direction: column;
+ }
+ .packages-content-left,
+ .packages-content-right {
+ width: 100%;
+ }
+}
diff --git a/web_src/css/repo/release-tag.css b/web_src/css/repo/release-tag.css
index a37f46b14c..4b42c992ef 100644
--- a/web_src/css/repo/release-tag.css
+++ b/web_src/css/repo/release-tag.css
@@ -70,8 +70,12 @@
flex-wrap: wrap;
}
+#release-list .release-entry .attachment-list > .item a {
+ min-width: 300px;
+}
+
#release-list .release-entry .attachment-list .attachment-right-info {
- flex-grow: 1;
+ flex-shrink: 0;
min-width: 300px;
}
@@ -87,10 +91,6 @@
border-bottom: none;
}
-#tags-table .tag-list-row {
- padding: 8px 12px;
-}
-
#tags-table .tag-list-row-title {
font-size: 18px;
font-weight: var(--font-weight-normal);
diff --git a/web_src/css/repo/wiki.css b/web_src/css/repo/wiki.css
index ca59dadb9c..144cb1206c 100644
--- a/web_src/css/repo/wiki.css
+++ b/web_src/css/repo/wiki.css
@@ -39,10 +39,6 @@
min-width: 150px;
}
-.repository.wiki .wiki-content-sidebar .ui.message.unicode-escape-prompt p {
- display: none;
-}
-
.repository.wiki .wiki-content-footer {
margin-top: 1em;
}
diff --git a/web_src/css/shared/flex-list.css b/web_src/css/shared/flex-list.css
index 0f54779252..e94e9e9cc2 100644
--- a/web_src/css/shared/flex-list.css
+++ b/web_src/css/shared/flex-list.css
@@ -17,6 +17,7 @@
.flex-item .flex-item-main {
display: flex;
flex-direction: column;
+ gap: 0.25em;
flex-grow: 1;
flex-basis: 60%; /* avoid wrapping the "flex-item-trailing" too aggressively */
min-width: 0; /* make the "text truncate" work, otherwise the flex axis is not limited and the text just overflows */
@@ -33,14 +34,6 @@
color: var(--color-primary) !important;
}
-.flex-item .flex-item-icon {
- align-self: baseline; /* mainly used by the issue list, to align the leading icon with the title */
-}
-
-.flex-item .flex-item-icon + .flex-item-main {
- align-self: baseline;
-}
-
.flex-item .flex-item-trailing {
display: flex;
gap: 0.5rem;
@@ -54,7 +47,9 @@
display: inline-flex;
flex-wrap: wrap;
align-items: center;
- gap: .25rem;
+ /* labels are under effect of this gap here because they are display:contents. Ideally we should make wrapping
+ of labels work without display: contents and set this to a static value again. */
+ gap: var(--gap-inline);
max-width: 100%;
color: var(--color-text);
font-size: 16px;
diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css
index 5ddee0a746..48fbd14dfb 100644
--- a/web_src/css/themes/theme-gitea-dark.css
+++ b/web_src/css/themes/theme-gitea-dark.css
@@ -185,6 +185,7 @@ gitea-theme-meta-info {
--color-orange-badge-bg: #f2711c1a;
--color-orange-badge-hover-bg: #f2711c4d;
--color-git: #f05133;
+ --color-logo: #609926;
/* target-based colors */
--color-body: #1b1f23;
--color-box-header: #1a1d1f;
diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css
index 1a4183c0d2..eaff717417 100644
--- a/web_src/css/themes/theme-gitea-light.css
+++ b/web_src/css/themes/theme-gitea-light.css
@@ -185,6 +185,7 @@ gitea-theme-meta-info {
--color-orange-badge-bg: #f2711c1a;
--color-orange-badge-hover-bg: #f2711c4d;
--color-git: #f05133;
+ --color-logo: #609926;
/* target-based colors */
--color-body: #ffffff;
--color-box-header: #f1f3f5;
diff --git a/web_src/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js
index 9d0e07b33b..3ad0984865 100644
--- a/web_src/fomantic/build/components/dropdown.js
+++ b/web_src/fomantic/build/components/dropdown.js
@@ -525,6 +525,7 @@ $.fn.dropdown = function(parameters) {
return true;
}
if(settings.onShow.call(element) !== false) {
+ $module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
module.animate.show(function() {
if( module.can.click() ) {
module.bind.intent();
@@ -752,7 +753,7 @@ $.fn.dropdown = function(parameters) {
if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
module.show();
}
- settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
+ $module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
}
;
if(settings.useLabels && module.has.maxSelections()) {
@@ -3993,8 +3994,6 @@ $.fn.dropdown.settings = {
onShow : function(){},
onHide : function(){},
- onAfterFiltered: function(){}, // GITEA-PATCH: callback to correctly handle the filtered items
-
/* Component */
name : 'Dropdown',
namespace : 'dropdown',
diff --git a/web_src/fomantic/build/components/modal.js b/web_src/fomantic/build/components/modal.js
index 420ecc250b..3f578ccfcc 100644
--- a/web_src/fomantic/build/components/modal.js
+++ b/web_src/fomantic/build/components/modal.js
@@ -467,7 +467,7 @@ $.fn.modal = function(parameters) {
ignoreRepeatedEvents = false;
return false;
}
-
+ $module.fomanticExt.onModalBeforeHidden.call(element); // GITEA-PATCH: handle more UI updates before hidden
if( module.is.animating() || module.is.active() ) {
if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
module.remove.active();
@@ -641,7 +641,7 @@ $.fn.modal = function(parameters) {
$module
.off('mousedown' + elementEventNamespace)
;
- }
+ }
$dimmer
.off('mousedown' + elementEventNamespace)
;
@@ -877,7 +877,7 @@ $.fn.modal = function(parameters) {
? $(document).scrollTop() + settings.padding
: $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding),
marginLeft: -(module.cache.width / 2)
- })
+ })
;
} else {
$module
@@ -886,7 +886,7 @@ $.fn.modal = function(parameters) {
? -(module.cache.height / 2)
: settings.padding / 2,
marginLeft: -(module.cache.width / 2)
- })
+ })
;
}
module.verbose('Setting modal offset for legacy mode');
diff --git a/web_src/js/bootstrap.ts b/web_src/js/bootstrap.ts
index 9e41673b86..96a2759a23 100644
--- a/web_src/js/bootstrap.ts
+++ b/web_src/js/bootstrap.ts
@@ -2,6 +2,7 @@
// to make sure the error handler always works, we should never import `window.config`, because
// some user's custom template breaks it.
import type {Intent} from './types.ts';
+import {html} from './utils/html.ts';
// This sets up the URL prefix used in webpack's chunk loading.
// This file must be imported before any lazy-loading is being attempted.
@@ -23,7 +24,7 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') {
let msgDiv = msgContainer.querySelector<HTMLDivElement>(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
if (!msgDiv) {
const el = document.createElement('div');
- el.innerHTML = `<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
+ el.innerHTML = html`<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
msgDiv = el.childNodes[0] as HTMLDivElement;
}
// merge duplicated messages into "the message (count)" format
diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue
index eaa9b0ffb1..296cb61cff 100644
--- a/web_src/js/components/ActivityHeatmap.vue
+++ b/web_src/js/components/ActivityHeatmap.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
// TODO: Switch to upstream after https://github.com/razorness/vue3-calendar-heatmap/pull/34 is merged
import {CalendarHeatmap} from '@silverwind/vue3-calendar-heatmap';
-import {onMounted, ref} from 'vue';
+import {onMounted, shallowRef} from 'vue';
import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap';
defineProps<{
@@ -24,7 +24,7 @@ const colorRange = [
'var(--color-primary-dark-4)',
];
-const endDate = ref(new Date());
+const endDate = shallowRef(new Date());
onMounted(() => {
// work around issue with first legend color being rendered twice and legend cut off
diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue
index 0aae202d42..5ec4499e48 100644
--- a/web_src/js/components/ContextPopup.vue
+++ b/web_src/js/components/ContextPopup.vue
@@ -2,16 +2,16 @@
import {SvgIcon} from '../svg.ts';
import {GET} from '../modules/fetch.ts';
import {getIssueColor, getIssueIcon} from '../features/issue.ts';
-import {computed, onMounted, ref} from 'vue';
+import {computed, onMounted, shallowRef, useTemplateRef} from 'vue';
import type {IssuePathInfo} from '../types.ts';
const {appSubUrl, i18n} = window.config;
-const loading = ref(false);
-const issue = ref(null);
-const renderedLabels = ref('');
+const loading = shallowRef(false);
+const issue = shallowRef(null);
+const renderedLabels = shallowRef('');
const i18nErrorOccurred = i18n.error_occurred;
-const i18nErrorMessage = ref(null);
+const i18nErrorMessage = shallowRef(null);
const createdAt = computed(() => new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'}));
const body = computed(() => {
@@ -22,7 +22,7 @@ const body = computed(() => {
return body;
});
-const root = ref<HTMLElement | null>(null);
+const root = useTemplateRef('root');
onMounted(() => {
root.value.addEventListener('ce-load-context-popup', (e: CustomEventInit<IssuePathInfo>) => {
diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue
index fc6a7bd281..e938814ec6 100644
--- a/web_src/js/components/DashboardRepoList.vue
+++ b/web_src/js/components/DashboardRepoList.vue
@@ -6,7 +6,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
const {appSubUrl, assetUrlPrefix, pageData} = window.config;
-type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning';
+type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning' | 'skipped';
type CommitStatusMap = {
[status in CommitStatus]: {
@@ -22,6 +22,7 @@ const commitStatus: CommitStatusMap = {
error: {name: 'gitea-exclamation', color: 'red'},
failure: {name: 'octicon-x', color: 'red'},
warning: {name: 'gitea-exclamation', color: 'yellow'},
+ skipped: {name: 'octicon-skip', color: 'grey'},
};
export default defineComponent({
@@ -38,7 +39,7 @@ export default defineComponent({
return {
tab,
repos: [],
- reposTotalCount: 0,
+ reposTotalCount: null,
reposFilter,
archivedFilter,
privateFilter,
@@ -112,9 +113,6 @@ export default defineComponent({
const el = document.querySelector('#dashboard-repo-list');
this.changeReposFilter(this.reposFilter);
fomanticQuery(el.querySelector('.ui.dropdown')).dropdown();
- nextTick(() => {
- this.$refs.search?.focus();
- });
this.textArchivedFilterTitles = {
'archived': this.textShowOnlyArchived,
@@ -218,7 +216,9 @@ export default defineComponent({
this.searchRepos();
},
- changePage(page: number) {
+ async changePage(page: number) {
+ if (this.isLoading) return;
+
this.page = page;
if (this.page > this.finalPage) {
this.page = this.finalPage;
@@ -228,7 +228,7 @@ export default defineComponent({
}
this.repos = [];
this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
- this.searchRepos();
+ await this.searchRepos();
},
async searchRepos() {
@@ -240,12 +240,20 @@ export default defineComponent({
let response, json;
try {
+ const firstLoad = this.reposTotalCount === null;
if (!this.reposTotalCount) {
const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
response = await GET(totalCountSearchURL);
this.reposTotalCount = parseInt(response.headers.get('X-Total-Count') ?? '0');
}
-
+ if (firstLoad && this.reposTotalCount) {
+ nextTick(() => {
+ // MDN: If there's no focused element, this is the Document.body or Document.documentElement.
+ if ((document.activeElement === document.body || document.activeElement === document.documentElement)) {
+ this.$refs.search.focus({preventScroll: true});
+ }
+ });
+ }
response = await GET(searchedURL);
json = await response.json();
} catch {
@@ -298,7 +306,7 @@ export default defineComponent({
return commitStatus[status].color;
},
- reposFilterKeyControl(e: KeyboardEvent) {
+ async reposFilterKeyControl(e: KeyboardEvent) {
switch (e.key) {
case 'Enter':
document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click();
@@ -307,7 +315,7 @@ export default defineComponent({
if (this.activeIndex > 0) {
this.activeIndex--;
} else if (this.page > 1) {
- this.changePage(this.page - 1);
+ await this.changePage(this.page - 1);
this.activeIndex = this.searchLimit - 1;
}
break;
@@ -316,17 +324,17 @@ export default defineComponent({
this.activeIndex++;
} else if (this.page < this.finalPage) {
this.activeIndex = 0;
- this.changePage(this.page + 1);
+ await this.changePage(this.page + 1);
}
break;
case 'ArrowRight':
if (this.page < this.finalPage) {
- this.changePage(this.page + 1);
+ await this.changePage(this.page + 1);
}
break;
case 'ArrowLeft':
if (this.page > 1) {
- this.changePage(this.page - 1);
+ await this.changePage(this.page - 1);
}
break;
}
@@ -347,7 +355,7 @@ export default defineComponent({
<h4 class="ui top attached header tw-flex tw-items-center">
<div class="tw-flex-1 tw-flex tw-items-center">
{{ textMyRepos }}
- <span class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
+ <span v-if="reposTotalCount" class="ui grey label tw-ml-2">{{ reposTotalCount }}</span>
</div>
<a class="tw-flex tw-items-center muted" :href="subUrl + '/repo/create' + (isOrganization ? '?org=' + organizationId : '')" :data-tooltip-content="textNewRepo">
<svg-icon name="octicon-plus"/>
@@ -418,7 +426,7 @@ export default defineComponent({
</div>
<div v-if="repos.length" class="ui attached table segment tw-rounded-b">
<ul class="repo-owner-name-list">
- <li class="tw-flex tw-items-center tw-py-2" v-for="repo, index in repos" :class="{'active': index === activeIndex}" :key="repo.id">
+ <li class="tw-flex tw-items-center tw-py-2" v-for="(repo, index) in repos" :class="{'active': index === activeIndex}" :key="repo.id">
<a class="repo-list-link muted" :href="repo.link">
<svg-icon :name="repoIcon(repo)" :size="16" class="repo-list-icon"/>
<div class="text truncate">{{ repo.full_name }}</div>
@@ -426,7 +434,7 @@ export default defineComponent({
<svg-icon name="octicon-archive" :size="16"/>
</div>
</a>
- <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link" :data-tooltip-content="repo.locale_latest_commit_status_state">
+ <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link || null" :data-tooltip-content="repo.locale_latest_commit_status_state">
<!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl -->
<svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/>
</a>
diff --git a/web_src/js/components/DiffCommitSelector.vue b/web_src/js/components/DiffCommitSelector.vue
index a375343979..4b18694bd2 100644
--- a/web_src/js/components/DiffCommitSelector.vue
+++ b/web_src/js/components/DiffCommitSelector.vue
@@ -2,7 +2,7 @@
import {defineComponent} from 'vue';
import {SvgIcon} from '../svg.ts';
import {GET} from '../modules/fetch.ts';
-import {generateAriaId} from '../modules/fomantic/base.ts';
+import {generateElemId} from '../utils/dom.ts';
type Commit = {
id: string,
@@ -35,8 +35,8 @@ export default defineComponent({
commits: [] as Array<Commit>,
hoverActivated: false,
lastReviewCommitSha: '',
- uniqueIdMenu: generateAriaId(),
- uniqueIdShowAll: generateAriaId(),
+ uniqueIdMenu: generateElemId('diff-commit-selector-menu-'),
+ uniqueIdShowAll: generateElemId('diff-commit-selector-show-all-'),
};
},
computed: {
diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue
index 5426a672cb..981d10c1c1 100644
--- a/web_src/js/components/DiffFileTree.vue
+++ b/web_src/js/components/DiffFileTree.vue
@@ -60,8 +60,8 @@ function updateState(visible: boolean) {
</script>
<template>
+ <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
<div v-if="store.fileTreeIsVisible" class="diff-file-tree-items">
- <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
<DiffFileTreeItem v-for="item in store.diffFileTree.TreeRoot.Children" :key="item.FullName" :item="item"/>
</div>
</template>
diff --git a/web_src/js/components/DiffFileTreeItem.vue b/web_src/js/components/DiffFileTreeItem.vue
index 24bf590082..f15f093ff8 100644
--- a/web_src/js/components/DiffFileTreeItem.vue
+++ b/web_src/js/components/DiffFileTreeItem.vue
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import {SvgIcon, type SvgName} from '../svg.ts';
-import {ref} from 'vue';
+import {shallowRef} from 'vue';
import {type DiffStatus, type DiffTreeEntry, diffTreeStore} from '../modules/diff-file.ts';
const props = defineProps<{
@@ -8,7 +8,7 @@ const props = defineProps<{
}>();
const store = diffTreeStore();
-const collapsed = ref(props.item.IsViewed);
+const collapsed = shallowRef(props.item.IsViewed);
function getIconForDiffStatus(pType: DiffStatus) {
const diffTypes: Record<DiffStatus, { name: SvgName, classes: Array<string> }> = {
diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue
index 4f291f5ca1..b2c28414c0 100644
--- a/web_src/js/components/PullRequestMergeForm.vue
+++ b/web_src/js/components/PullRequestMergeForm.vue
@@ -1,19 +1,19 @@
<script lang="ts" setup>
-import {computed, onMounted, onUnmounted, ref, watch} from 'vue';
+import {computed, onMounted, onUnmounted, shallowRef, watch} from 'vue';
import {SvgIcon} from '../svg.ts';
import {toggleElem} from '../utils/dom.ts';
const {csrfToken, pageData} = window.config;
-const mergeForm = ref(pageData.pullRequestMergeForm);
+const mergeForm = pageData.pullRequestMergeForm;
-const mergeTitleFieldValue = ref('');
-const mergeMessageFieldValue = ref('');
-const deleteBranchAfterMerge = ref(false);
-const autoMergeWhenSucceed = ref(false);
+const mergeTitleFieldValue = shallowRef('');
+const mergeMessageFieldValue = shallowRef('');
+const deleteBranchAfterMerge = shallowRef(false);
+const autoMergeWhenSucceed = shallowRef(false);
-const mergeStyle = ref('');
-const mergeStyleDetail = ref({
+const mergeStyle = shallowRef('');
+const mergeStyleDetail = shallowRef({
hideMergeMessageTexts: false,
textDoMerge: '',
mergeTitleFieldText: '',
@@ -21,33 +21,33 @@ const mergeStyleDetail = ref({
hideAutoMerge: false,
});
-const mergeStyleAllowedCount = ref(0);
+const mergeStyleAllowedCount = shallowRef(0);
-const showMergeStyleMenu = ref(false);
-const showActionForm = ref(false);
+const showMergeStyleMenu = shallowRef(false);
+const showActionForm = shallowRef(false);
const mergeButtonStyleClass = computed(() => {
- if (mergeForm.value.allOverridableChecksOk) return 'primary';
+ if (mergeForm.allOverridableChecksOk) return 'primary';
return autoMergeWhenSucceed.value ? 'primary' : 'red';
});
const forceMerge = computed(() => {
- return mergeForm.value.canMergeNow && !mergeForm.value.allOverridableChecksOk;
+ return mergeForm.canMergeNow && !mergeForm.allOverridableChecksOk;
});
watch(mergeStyle, (val) => {
- mergeStyleDetail.value = mergeForm.value.mergeStyles.find((e: any) => e.name === val);
+ mergeStyleDetail.value = mergeForm.mergeStyles.find((e: any) => e.name === val);
for (const elem of document.querySelectorAll('[data-pull-merge-style]')) {
toggleElem(elem, elem.getAttribute('data-pull-merge-style') === val);
}
});
onMounted(() => {
- mergeStyleAllowedCount.value = mergeForm.value.mergeStyles.reduce((v: any, msd: any) => v + (msd.allowed ? 1 : 0), 0);
+ mergeStyleAllowedCount.value = mergeForm.mergeStyles.reduce((v: any, msd: any) => v + (msd.allowed ? 1 : 0), 0);
- let mergeStyle = mergeForm.value.mergeStyles.find((e: any) => e.allowed && e.name === mergeForm.value.defaultMergeStyle)?.name;
- if (!mergeStyle) mergeStyle = mergeForm.value.mergeStyles.find((e: any) => e.allowed)?.name;
- switchMergeStyle(mergeStyle, !mergeForm.value.canMergeNow);
+ let mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed && e.name === mergeForm.defaultMergeStyle)?.name;
+ if (!mergeStyle) mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed)?.name;
+ switchMergeStyle(mergeStyle, !mergeForm.canMergeNow);
document.addEventListener('mouseup', hideMergeStyleMenu);
});
@@ -63,7 +63,7 @@ function hideMergeStyleMenu() {
function toggleActionForm(show: boolean) {
showActionForm.value = show;
if (!show) return;
- deleteBranchAfterMerge.value = mergeForm.value.defaultDeleteBranchAfterMerge;
+ deleteBranchAfterMerge.value = mergeForm.defaultDeleteBranchAfterMerge;
mergeTitleFieldValue.value = mergeStyleDetail.value.mergeTitleFieldText;
mergeMessageFieldValue.value = mergeStyleDetail.value.mergeMessageFieldText;
}
@@ -74,7 +74,7 @@ function switchMergeStyle(name: string, autoMerge = false) {
}
function clearMergeMessage() {
- mergeMessageFieldValue.value = mergeForm.value.defaultMergeMessage;
+ mergeMessageFieldValue.value = mergeForm.defaultMergeMessage;
}
</script>
diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue
index af300622b4..2eb2211269 100644
--- a/web_src/js/components/RepoActionView.vue
+++ b/web_src/js/components/RepoActionView.vue
@@ -177,7 +177,7 @@ export default defineComponent({
},
},
- async mounted() { // eslint-disable-line @typescript-eslint/no-misused-promises
+ async mounted() {
// load job data and then auto-reload periodically
// need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
await this.loadJob();
@@ -439,7 +439,8 @@ export default defineComponent({
});
</script>
<template>
- <div class="ui container action-view-container">
+ <!-- make the view container full width to make users easier to read logs -->
+ <div class="ui fluid container">
<div class="action-view-header">
<div class="action-info-summary">
<div class="action-info-summary-title">
diff --git a/web_src/js/components/RepoActivityTopAuthors.vue b/web_src/js/components/RepoActivityTopAuthors.vue
index 77b85bd7e2..bbdfda41d0 100644
--- a/web_src/js/components/RepoActivityTopAuthors.vue
+++ b/web_src/js/components/RepoActivityTopAuthors.vue
@@ -1,9 +1,9 @@
<script lang="ts" setup>
// @ts-expect-error - module exports no types
import {VueBarGraph} from 'vue-bar-graph';
-import {computed, onMounted, ref} from 'vue';
+import {computed, onMounted, shallowRef, useTemplateRef} from 'vue';
-const colors = ref({
+const colors = shallowRef({
barColor: 'green',
textColor: 'black',
textAltColor: 'white',
@@ -41,8 +41,8 @@ const graphWidth = computed(() => {
return activityTopAuthors.length * 40;
});
-const styleElement = ref<HTMLElement | null>(null);
-const altStyleElement = ref<HTMLElement | null>(null);
+const styleElement = useTemplateRef('styleElement');
+const altStyleElement = useTemplateRef('altStyleElement');
onMounted(() => {
const refStyle = window.getComputedStyle(styleElement.value);
diff --git a/web_src/js/components/RepoCodeFrequency.vue b/web_src/js/components/RepoCodeFrequency.vue
index f04fc065b6..f331a26fe9 100644
--- a/web_src/js/components/RepoCodeFrequency.vue
+++ b/web_src/js/components/RepoCodeFrequency.vue
@@ -23,7 +23,7 @@ import {
import {chartJsColors} from '../utils/color.ts';
import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
-import {onMounted, ref} from 'vue';
+import {onMounted, shallowRef} from 'vue';
const {pageData} = window.config;
@@ -47,10 +47,10 @@ defineProps<{
};
}>();
-const isLoading = ref(false);
-const errorText = ref('');
-const repoLink = ref(pageData.repoLink || []);
-const data = ref<DayData[]>([]);
+const isLoading = shallowRef(false);
+const errorText = shallowRef('');
+const repoLink = pageData.repoLink;
+const data = shallowRef<DayData[]>([]);
onMounted(() => {
fetchGraphData();
@@ -61,7 +61,7 @@ async function fetchGraphData() {
try {
let response: Response;
do {
- response = await GET(`${repoLink.value}/activity/code-frequency/data`);
+ response = await GET(`${repoLink}/activity/code-frequency/data`);
if (response.status === 202) {
await sleep(1000); // wait for 1 second before retrying
}
diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue
index 1006ea30bb..754acb997d 100644
--- a/web_src/js/components/RepoContributors.vue
+++ b/web_src/js/components/RepoContributors.vue
@@ -397,7 +397,7 @@ export default defineComponent({
<div class="ui top attached header tw-flex tw-flex-1">
<b class="ui right">#{{ index + 1 }}</b>
<a :href="contributor.home_link">
- <img class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link" alt="">
+ <img loading="lazy" class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link" alt="">
</a>
<div class="tw-ml-2">
<a v-if="contributor.home_link !== ''" :href="contributor.home_link"><h4>{{ contributor.name }}</h4></a>
diff --git a/web_src/js/components/RepoRecentCommits.vue b/web_src/js/components/RepoRecentCommits.vue
index 1b3d8fd459..27aa27dfc3 100644
--- a/web_src/js/components/RepoRecentCommits.vue
+++ b/web_src/js/components/RepoRecentCommits.vue
@@ -21,7 +21,7 @@ import {
import {chartJsColors} from '../utils/color.ts';
import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
-import {onMounted, ref} from 'vue';
+import {onMounted, ref, shallowRef} from 'vue';
const {pageData} = window.config;
@@ -43,9 +43,9 @@ defineProps<{
};
}>();
-const isLoading = ref(false);
-const errorText = ref('');
-const repoLink = ref(pageData.repoLink || []);
+const isLoading = shallowRef(false);
+const errorText = shallowRef('');
+const repoLink = pageData.repoLink;
const data = ref<DayData[]>([]);
onMounted(() => {
@@ -57,7 +57,7 @@ async function fetchGraphData() {
try {
let response: Response;
do {
- response = await GET(`${repoLink.value}/activity/recent-commits/data`);
+ response = await GET(`${repoLink}/activity/recent-commits/data`);
if (response.status === 202) {
await sleep(1000); // wait for 1 second before retrying
}
diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue
index c692142792..1f90f92586 100644
--- a/web_src/js/components/ViewFileTree.vue
+++ b/web_src/js/components/ViewFileTree.vue
@@ -1,11 +1,9 @@
<script lang="ts" setup>
import ViewFileTreeItem from './ViewFileTreeItem.vue';
-import {onMounted, ref} from 'vue';
-import {pathEscapeSegments} from '../utils/url.ts';
-import {GET} from '../modules/fetch.ts';
-import {createElementFromHTML} from '../utils/dom.ts';
+import {onMounted, useTemplateRef} from 'vue';
+import {createViewFileTreeStore} from './ViewFileTreeStore.ts';
-const elRoot = ref<HTMLElement | null>(null);
+const elRoot = useTemplateRef('elRoot');
const props = defineProps({
repoLink: {type: String, required: true},
@@ -13,52 +11,20 @@ const props = defineProps({
currentRefNameSubURL: {type: String, required: true},
});
-const files = ref([]);
-const selectedItem = ref('');
-
-async function loadChildren(treePath: string, subPath: string = '') {
- const response = await GET(`${props.repoLink}/tree-view/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`);
- const json = await response.json();
- const poolSvgs = [];
- for (const [svgId, svgContent] of Object.entries(json.renderedIconPool ?? {})) {
- if (!document.querySelector(`.global-svg-icon-pool #${svgId}`)) poolSvgs.push(svgContent);
- }
- if (poolSvgs.length) {
- const svgContainer = createElementFromHTML('<div class="global-svg-icon-pool tw-hidden"></div>');
- svgContainer.innerHTML = poolSvgs.join('');
- document.body.append(svgContainer);
- }
- return json.fileTreeNodes ?? null;
-}
-
-async function loadViewContent(url: string) {
- url = url.includes('?') ? url.replace('?', '?only_content=true') : `${url}?only_content=true`;
- const response = await GET(url);
- document.querySelector('.repo-view-content').innerHTML = await response.text();
-}
-
-async function navigateTreeView(treePath: string) {
- const url = `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`;
- window.history.pushState({treePath, url}, null, url);
- selectedItem.value = treePath;
- await loadViewContent(url);
-}
-
+const store = createViewFileTreeStore(props);
onMounted(async () => {
- selectedItem.value = props.treePath;
- files.value = await loadChildren('', props.treePath);
+ store.rootFiles = await store.loadChildren('', props.treePath);
elRoot.value.closest('.is-loading')?.classList?.remove('is-loading');
window.addEventListener('popstate', (e) => {
- selectedItem.value = e.state?.treePath || '';
- if (e.state?.url) loadViewContent(e.state.url);
+ store.selectedItem = e.state?.treePath || '';
+ if (e.state?.url) store.loadViewContent(e.state.url);
});
});
</script>
<template>
<div class="view-file-tree-items" ref="elRoot">
- <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
- <ViewFileTreeItem v-for="item in files" :key="item.name" :item="item" :selected-item="selectedItem" :navigate-view-content="navigateTreeView" :load-children="loadChildren"/>
+ <ViewFileTreeItem v-for="item in store.rootFiles" :key="item.name" :item="item" :store="store"/>
</div>
</template>
diff --git a/web_src/js/components/ViewFileTreeItem.vue b/web_src/js/components/ViewFileTreeItem.vue
index c39fa1f4ae..5173c7eb46 100644
--- a/web_src/js/components/ViewFileTreeItem.vue
+++ b/web_src/js/components/ViewFileTreeItem.vue
@@ -1,10 +1,12 @@
<script lang="ts" setup>
import {SvgIcon} from '../svg.ts';
-import {ref} from 'vue';
+import {isPlainClick} from '../utils/dom.ts';
+import {shallowRef} from 'vue';
+import {type createViewFileTreeStore} from './ViewFileTreeStore.ts';
type Item = {
entryName: string;
- entryMode: string;
+ entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
entryIcon: string;
entryIconOpen: string;
fullPath: string;
@@ -14,103 +16,67 @@ type Item = {
const props = defineProps<{
item: Item,
- navigateViewContent:(treePath: string) => void,
- loadChildren:(treePath: string, subPath?: string) => Promise<Item[]>,
- selectedItem?: string,
+ store: ReturnType<typeof createViewFileTreeStore>
}>();
-const isLoading = ref(false);
-const children = ref(props.item.children);
-const collapsed = ref(!props.item.children);
+const store = props.store;
+const isLoading = shallowRef(false);
+const children = shallowRef(props.item.children);
+const collapsed = shallowRef(!props.item.children);
const doLoadChildren = async () => {
collapsed.value = !collapsed.value;
- if (!collapsed.value && props.loadChildren) {
+ if (!collapsed.value) {
isLoading.value = true;
try {
- children.value = await props.loadChildren(props.item.fullPath);
+ children.value = await store.loadChildren(props.item.fullPath);
} finally {
isLoading.value = false;
}
}
};
-const doLoadDirContent = () => {
- doLoadChildren();
- props.navigateViewContent(props.item.fullPath);
+const onItemClick = (e: MouseEvent) => {
+ // only handle the click event with page partial reloading if the user didn't press any special key
+ // let browsers handle special keys like "Ctrl+Click"
+ if (!isPlainClick(e)) return;
+ e.preventDefault();
+ if (props.item.entryMode === 'tree') doLoadChildren();
+ store.navigateTreeView(props.item.fullPath);
};
-const doLoadFileContent = () => {
- props.navigateViewContent(props.item.fullPath);
-};
-
-const doGotoSubModule = () => {
- location.href = props.item.submoduleUrl;
-};
</script>
-<!--title instead of tooltip above as the tooltip needs too much work with the current methods, i.e. not being loaded or staying open for "too long"-->
<template>
- <div
- v-if="item.entryMode === 'commit'" class="tree-item type-submodule"
- :title="item.entryName"
- @click.stop="doGotoSubModule"
- >
- <!-- submodule -->
- <div class="item-content">
- <!-- eslint-disable-next-line vue/no-v-html -->
- <span class="tw-contents" v-html="item.entryIcon"/>
- <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span>
- </div>
- </div>
- <div
- v-else-if="item.entryMode === 'symlink'" class="tree-item type-symlink"
- :class="{'selected': selectedItem === item.fullPath}"
+ <a
+ class="tree-item silenced"
+ :class="{
+ 'selected': store.selectedItem === item.fullPath,
+ 'type-submodule': item.entryMode === 'commit',
+ 'type-directory': item.entryMode === 'tree',
+ 'type-symlink': item.entryMode === 'symlink',
+ 'type-file': item.entryMode === 'blob' || item.entryMode === 'exec',
+ }"
:title="item.entryName"
- @click.stop="doLoadFileContent"
+ :href="store.buildTreePathWebUrl(item.fullPath)"
+ @click.stop="onItemClick"
>
- <!-- symlink -->
- <div class="item-content">
- <!-- eslint-disable-next-line vue/no-v-html -->
- <span class="tw-contents" v-html="item.entryIcon"/>
- <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span>
- </div>
- </div>
- <div
- v-else-if="item.entryMode !== 'tree'" class="tree-item type-file"
- :class="{'selected': selectedItem === item.fullPath}"
- :title="item.entryName"
- @click.stop="doLoadFileContent"
- >
- <!-- file -->
- <div class="item-content">
- <!-- eslint-disable-next-line vue/no-v-html -->
- <span class="tw-contents" v-html="item.entryIcon"/>
- <span class="gt-ellipsis tw-flex-1">{{ item.entryName }}</span>
- </div>
- </div>
- <div
- v-else class="tree-item type-directory"
- :class="{'selected': selectedItem === item.fullPath}"
- :title="item.entryName"
- @click.stop="doLoadDirContent"
- >
- <!-- directory -->
- <div class="item-toggle">
+ <div v-if="item.entryMode === 'tree'" class="item-toggle">
<SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/>
- <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop="doLoadChildren"/>
+ <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop.prevent="doLoadChildren"/>
</div>
<div class="item-content">
<!-- eslint-disable-next-line vue/no-v-html -->
<span class="tw-contents" v-html="(!collapsed && item.entryIconOpen) ? item.entryIconOpen : item.entryIcon"/>
<span class="gt-ellipsis">{{ item.entryName }}</span>
</div>
- </div>
+ </a>
<div v-if="children?.length" v-show="!collapsed" class="sub-items">
- <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :selected-item="selectedItem" :navigate-view-content="navigateViewContent" :load-children="loadChildren"/>
+ <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :store="store"/>
</div>
</template>
+
<style scoped>
.sub-items {
display: flex;
diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts
new file mode 100644
index 0000000000..e2155bd58a
--- /dev/null
+++ b/web_src/js/components/ViewFileTreeStore.ts
@@ -0,0 +1,45 @@
+import {reactive} from 'vue';
+import {GET} from '../modules/fetch.ts';
+import {pathEscapeSegments} from '../utils/url.ts';
+import {createElementFromHTML} from '../utils/dom.ts';
+import {html} from '../utils/html.ts';
+
+export function createViewFileTreeStore(props: { repoLink: string, treePath: string, currentRefNameSubURL: string}) {
+ const store = reactive({
+ rootFiles: [],
+ selectedItem: props.treePath,
+
+ async loadChildren(treePath: string, subPath: string = '') {
+ const response = await GET(`${props.repoLink}/tree-view/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}?sub_path=${encodeURIComponent(subPath)}`);
+ const json = await response.json();
+ const poolSvgs = [];
+ for (const [svgId, svgContent] of Object.entries(json.renderedIconPool ?? {})) {
+ if (!document.querySelector(`.global-svg-icon-pool #${svgId}`)) poolSvgs.push(svgContent);
+ }
+ if (poolSvgs.length) {
+ const svgContainer = createElementFromHTML(html`<div class="global-svg-icon-pool tw-hidden"></div>`);
+ svgContainer.innerHTML = poolSvgs.join('');
+ document.body.append(svgContainer);
+ }
+ return json.fileTreeNodes ?? null;
+ },
+
+ async loadViewContent(url: string) {
+ url = url.includes('?') ? url.replace('?', '?only_content=true') : `${url}?only_content=true`;
+ const response = await GET(url);
+ document.querySelector('.repo-view-content').innerHTML = await response.text();
+ },
+
+ async navigateTreeView(treePath: string) {
+ const url = store.buildTreePathWebUrl(treePath);
+ window.history.pushState({treePath, url}, null, url);
+ store.selectedItem = treePath;
+ await store.loadViewContent(url);
+ },
+
+ buildTreePathWebUrl(treePath: string) {
+ return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`;
+ },
+ });
+ return store;
+}
diff --git a/web_src/js/features/common-button.test.ts b/web_src/js/features/common-button.test.ts
new file mode 100644
index 0000000000..f41bafbc79
--- /dev/null
+++ b/web_src/js/features/common-button.test.ts
@@ -0,0 +1,14 @@
+import {assignElementProperty} from './common-button.ts';
+
+test('assignElementProperty', () => {
+ const elForm = document.createElement('form');
+ assignElementProperty(elForm, 'action', '/test-link');
+ expect(elForm.action).contains('/test-link'); // the DOM always returns absolute URL
+ assignElementProperty(elForm, 'text-content', 'dummy');
+ expect(elForm.textContent).toBe('dummy');
+
+ const elInput = document.createElement('input');
+ expect(elInput.readOnly).toBe(false);
+ assignElementProperty(elInput, 'read-only', 'true');
+ expect(elInput.readOnly).toBe(true);
+});
diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts
index 003bfbce5d..3b112b116b 100644
--- a/web_src/js/features/common-button.ts
+++ b/web_src/js/features/common-button.ts
@@ -1,5 +1,5 @@
import {POST} from '../modules/fetch.ts';
-import {addDelegatedEventListener, hideElem, showElem, toggleElem} from '../utils/dom.ts';
+import {addDelegatedEventListener, hideElem, isElemVisible, showElem, toggleElem} from '../utils/dom.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {camelize} from 'vue';
@@ -43,13 +43,16 @@ export function initGlobalDeleteButton(): void {
fomanticQuery(modal).modal({
closable: false,
- onApprove: async () => {
+ onApprove: () => {
// if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."`
if (btn.getAttribute('data-type') === 'form') {
const formSelector = btn.getAttribute('data-form');
const form = document.querySelector<HTMLFormElement>(formSelector);
if (!form) throw new Error(`no form named ${formSelector} found`);
+ modal.classList.add('is-loading'); // the form is not in the modal, so also add loading indicator to the modal
+ form.classList.add('is-loading');
form.submit();
+ return false; // prevent modal from closing automatically
}
// prepare an AJAX form by data attributes
@@ -62,12 +65,15 @@ export function initGlobalDeleteButton(): void {
postData.append('id', value);
}
}
-
- const response = await POST(btn.getAttribute('data-url'), {data: postData});
- if (response.ok) {
- const data = await response.json();
- window.location.href = data.redirect;
- }
+ (async () => {
+ const response = await POST(btn.getAttribute('data-url'), {data: postData});
+ if (response.ok) {
+ const data = await response.json();
+ window.location.href = data.redirect;
+ }
+ })();
+ modal.classList.add('is-loading'); // the request is in progress, so also add loading indicator to the modal
+ return false; // prevent modal from closing automatically
},
}).modal('show');
});
@@ -79,10 +85,11 @@ function onShowPanelClick(el: HTMLElement, e: MouseEvent) {
// if it has "toggle" class, it toggles the panel
e.preventDefault();
const sel = el.getAttribute('data-panel');
- if (el.classList.contains('toggle')) {
- toggleElem(sel);
- } else {
- showElem(sel);
+ const elems = el.classList.contains('toggle') ? toggleElem(sel) : showElem(sel);
+ for (const elem of elems) {
+ if (isElemVisible(elem as HTMLElement)) {
+ elem.querySelector<HTMLElement>('[autofocus]')?.focus();
+ }
}
}
@@ -102,6 +109,21 @@ function onHidePanelClick(el: HTMLElement, e: MouseEvent) {
throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code
}
+export function assignElementProperty(el: any, name: string, val: string) {
+ name = camelize(name);
+ const old = el[name];
+ if (typeof old === 'boolean') {
+ el[name] = val === 'true';
+ } else if (typeof old === 'number') {
+ el[name] = parseFloat(val);
+ } else if (typeof old === 'string') {
+ el[name] = val;
+ } else {
+ // in the future, we could introduce a better typing system like `data-modal-form.action:string="..."`
+ throw new Error(`cannot assign element property ${name} by value ${val}`);
+ }
+}
+
function onShowModalClick(el: HTMLElement, e: MouseEvent) {
// A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute.
// Each "data-modal-{target}" attribute will be filled to target element's value or text-content.
@@ -109,7 +131,7 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
// * Then, try to query '[name=target]'
// * Then, try to query '.target'
// * Then, try to query 'target' as HTML tag
- // If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set.
+ // If there is a ".{prop-name}" part like "data-modal-form.action", the "form" element's "action" property will be set, the "prop-name" will be camel-cased to "propName".
e.preventDefault();
const modalSelector = el.getAttribute('data-modal');
const elModal = document.querySelector(modalSelector);
@@ -122,19 +144,20 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
}
const attrTargetCombo = attrib.name.substring(modalAttrPrefix.length);
- const [attrTargetName, attrTargetAttr] = attrTargetCombo.split('.');
- // try to find target by: "#target" -> "[name=target]" -> ".target" -> "<target> tag"
+ const [attrTargetName, attrTargetProp] = attrTargetCombo.split('.');
+ // try to find target by: "#target" -> "[name=target]" -> ".target" -> "<target> tag", and then try the modal itself
const attrTarget = elModal.querySelector(`#${attrTargetName}`) ||
elModal.querySelector(`[name=${attrTargetName}]`) ||
elModal.querySelector(`.${attrTargetName}`) ||
- elModal.querySelector(`${attrTargetName}`);
+ elModal.querySelector(`${attrTargetName}`) ||
+ (elModal.matches(`${attrTargetName}`) || elModal.matches(`#${attrTargetName}`) || elModal.matches(`.${attrTargetName}`) ? elModal : null);
if (!attrTarget) {
if (!window.config.runModeIsProd) throw new Error(`attr target "${attrTargetCombo}" not found for modal`);
continue;
}
- if (attrTargetAttr) {
- (attrTarget as any)[camelize(attrTargetAttr)] = attrib.value;
+ if (attrTargetProp) {
+ assignElementProperty(attrTarget, attrTargetProp, attrib.value);
} else if (attrTarget.matches('input, textarea')) {
(attrTarget as HTMLInputElement | HTMLTextAreaElement).value = attrib.value; // FIXME: add more supports like checkbox
} else {
@@ -142,13 +165,7 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
}
}
- fomanticQuery(elModal).modal('setting', {
- onApprove: () => {
- // "form-fetch-action" can handle network errors gracefully,
- // so keep the modal dialog to make users can re-submit the form if anything wrong happens.
- if (elModal.querySelector('.form-fetch-action')) return false;
- },
- }).modal('show');
+ fomanticQuery(elModal).modal('show');
}
export function initGlobalButtons(): void {
diff --git a/web_src/js/features/common-fetch-action.ts b/web_src/js/features/common-fetch-action.ts
index 2da481e521..3ca361b6e2 100644
--- a/web_src/js/features/common-fetch-action.ts
+++ b/web_src/js/features/common-fetch-action.ts
@@ -1,11 +1,11 @@
import {request} from '../modules/fetch.ts';
-import {showErrorToast} from '../modules/toast.ts';
-import {addDelegatedEventListener, submitEventSubmitter} from '../utils/dom.ts';
-import {confirmModal} from './comp/ConfirmModal.ts';
+import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
+import {addDelegatedEventListener, createElementFromHTML, submitEventSubmitter} from '../utils/dom.ts';
+import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts';
import type {RequestOpts} from '../types.ts';
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
-const {appSubUrl, i18n} = window.config;
+const {appSubUrl} = window.config;
// fetchActionDoRedirect does real redirection to bypass the browser's limitations of "location"
// more details are in the backend's fetch-redirect handler
@@ -23,10 +23,20 @@ function fetchActionDoRedirect(redirect: string) {
}
async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: RequestOpts) {
+ const showErrorForResponse = (code: number, message: string) => {
+ showErrorToast(`Error ${code || 'request'}: ${message}`);
+ };
+
+ let respStatus = 0;
+ let respText = '';
try {
+ hideToastsAll();
const resp = await request(url, opt);
- if (resp.status === 200) {
- let {redirect} = await resp.json();
+ respStatus = resp.status;
+ respText = await resp.text();
+ const respJson = JSON.parse(respText);
+ if (respStatus === 200) {
+ let {redirect} = respJson;
redirect = redirect || actionElem.getAttribute('data-redirect');
ignoreAreYouSure(actionElem); // ignore the areYouSure check before reloading
if (redirect) {
@@ -35,29 +45,32 @@ async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: R
window.location.reload();
}
return;
- } else if (resp.status >= 400 && resp.status < 500) {
- const data = await resp.json();
+ }
+
+ if (respStatus >= 400 && respStatus < 500 && respJson?.errorMessage) {
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
- if (data.errorMessage) {
- showErrorToast(data.errorMessage, {useHtmlBody: data.renderFormat === 'html'});
- } else {
- showErrorToast(`server error: ${resp.status}`);
- }
+ showErrorToast(respJson.errorMessage, {useHtmlBody: respJson.renderFormat === 'html'});
} else {
- showErrorToast(`server error: ${resp.status}`);
+ showErrorForResponse(respStatus, respText);
}
} catch (e) {
- if (e.name !== 'AbortError') {
- console.error('error when doRequest', e);
- showErrorToast(`${i18n.network_error} ${e}`);
+ if (e.name === 'SyntaxError') {
+ showErrorForResponse(respStatus, (respText || '').substring(0, 100));
+ } else if (e.name !== 'AbortError') {
+ console.error('fetchActionDoRequest error', e);
+ showErrorForResponse(respStatus, `${e}`);
}
}
actionElem.classList.remove('is-loading', 'loading-icon-2px');
}
-async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) {
+async function onFormFetchActionSubmit(formEl: HTMLFormElement, e: SubmitEvent) {
e.preventDefault();
+ await submitFormFetchAction(formEl, submitEventSubmitter(e));
+}
+
+export async function submitFormFetchAction(formEl: HTMLFormElement, formSubmitter?: HTMLElement) {
if (formEl.classList.contains('is-loading')) return;
formEl.classList.add('is-loading');
@@ -66,9 +79,8 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) {
}
const formMethod = formEl.getAttribute('method') || 'get';
- const formActionUrl = formEl.getAttribute('action');
+ const formActionUrl = formEl.getAttribute('action') || window.location.href;
const formData = new FormData(formEl);
- const formSubmitter = submitEventSubmitter(e);
const [submitterName, submitterValue] = [formSubmitter?.getAttribute('name'), formSubmitter?.getAttribute('value')];
if (submitterName) {
formData.append(submitterName, submitterValue || '');
@@ -96,36 +108,52 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) {
await fetchActionDoRequest(formEl, reqUrl, reqOpt);
}
-async function linkAction(el: HTMLElement, e: Event) {
+async function onLinkActionClick(el: HTMLElement, e: Event) {
// A "link-action" can post AJAX request to its "data-url"
// Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading.
- // If the "link-action" has "data-modal-confirm" attribute, a confirm modal dialog will be shown before taking action.
+ // If the "link-action" has "data-modal-confirm" attribute, a "confirm modal dialog" will be shown before taking action.
+ // Attribute "data-modal-confirm" can be a modal element by "#the-modal-id", or a string content for the modal dialog.
e.preventDefault();
const url = el.getAttribute('data-url');
const doRequest = async () => {
- if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but A doesn't have disabled attribute
+ if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but "A" doesn't have the "disabled" attribute
await fetchActionDoRequest(el, url, {method: el.getAttribute('data-link-action-method') || 'POST'});
if ('disabled' in el) el.disabled = false;
};
- const modalConfirmContent = el.getAttribute('data-modal-confirm') ||
- el.getAttribute('data-modal-confirm-content') || '';
- if (!modalConfirmContent) {
+ let elModal: HTMLElement | null = null;
+ const dataModalConfirm = el.getAttribute('data-modal-confirm') || '';
+ if (dataModalConfirm.startsWith('#')) {
+ // eslint-disable-next-line unicorn/prefer-query-selector
+ elModal = document.getElementById(dataModalConfirm.substring(1));
+ if (elModal) {
+ elModal = createElementFromHTML(elModal.outerHTML);
+ elModal.removeAttribute('id');
+ }
+ }
+ if (!elModal) {
+ const modalConfirmContent = dataModalConfirm || el.getAttribute('data-modal-confirm-content') || '';
+ if (modalConfirmContent) {
+ const isRisky = el.classList.contains('red') || el.classList.contains('negative');
+ elModal = createConfirmModal({
+ header: el.getAttribute('data-modal-confirm-header') || '',
+ content: modalConfirmContent,
+ confirmButtonColor: isRisky ? 'red' : 'primary',
+ });
+ }
+ }
+
+ if (!elModal) {
await doRequest();
return;
}
- const isRisky = el.classList.contains('red') || el.classList.contains('negative');
- if (await confirmModal({
- header: el.getAttribute('data-modal-confirm-header') || '',
- content: modalConfirmContent,
- confirmButtonColor: isRisky ? 'red' : 'primary',
- })) {
+ if (await confirmModal(elModal)) {
await doRequest();
}
}
export function initGlobalFetchAction() {
- addDelegatedEventListener(document, 'submit', '.form-fetch-action', formFetchAction);
- addDelegatedEventListener(document, 'click', '.link-action', linkAction);
+ addDelegatedEventListener(document, 'submit', '.form-fetch-action', onFormFetchActionSubmit);
+ addDelegatedEventListener(document, 'click', '.link-action', onLinkActionClick);
}
diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts
index e207364794..037529bd10 100644
--- a/web_src/js/features/common-issue-list.ts
+++ b/web_src/js/features/common-issue-list.ts
@@ -1,4 +1,4 @@
-import {isElemHidden, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts';
+import {isElemVisible, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts';
import {GET} from '../modules/fetch.ts';
const {appSubUrl} = window.config;
@@ -28,7 +28,7 @@ export function parseIssueListQuickGotoLink(repoLink: string, searchText: string
}
export function initCommonIssueListQuickGoto() {
- const goto = document.querySelector('#issue-list-quick-goto');
+ const goto = document.querySelector<HTMLElement>('#issue-list-quick-goto');
if (!goto) return;
const form = goto.closest('form');
@@ -37,7 +37,7 @@ export function initCommonIssueListQuickGoto() {
form.addEventListener('submit', (e) => {
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
- let doQuickGoto = !isElemHidden(goto);
+ let doQuickGoto = isElemVisible(goto);
const submitter = submitEventSubmitter(e);
if (submitter !== form && submitter !== input && submitter !== goto) doQuickGoto = false;
if (!doQuickGoto) return;
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts
index d3773a89c4..afa3957e21 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.ts
+++ b/web_src/js/features/comp/ComboMarkdownEditor.ts
@@ -1,7 +1,7 @@
import '@github/markdown-toolbar-element';
import '@github/text-expander-element';
import {attachTribute} from '../tribute.ts';
-import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.ts';
+import {hideElem, showElem, autosize, isElemVisible, generateElemId} from '../../utils/dom.ts';
import {
EventUploadStateChanged,
initEasyMDEPaste,
@@ -25,8 +25,6 @@ import {createTippy} from '../../modules/tippy.ts';
import {fomanticQuery} from '../../modules/fomantic/base.ts';
import type EasyMDE from 'easymde';
-let elementIdCounter = 0;
-
/**
* validate if the given textarea is non-empty.
* @param {HTMLTextAreaElement} textarea - The textarea element to be validated.
@@ -125,7 +123,7 @@ export class ComboMarkdownEditor {
setupTextarea() {
this.textarea = this.container.querySelector('.markdown-text-editor');
this.textarea._giteaComboMarkdownEditor = this;
- this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`;
+ this.textarea.id = generateElemId(`_combo_markdown_editor_`);
this.textarea.addEventListener('input', () => triggerEditorContentChanged(this.container));
this.applyEditorHeights(this.textarea, this.options.editorHeights);
@@ -213,16 +211,16 @@ export class ComboMarkdownEditor {
// Fomantic Tab requires the "data-tab" to be globally unique.
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
+ const tabIdSuffix = generateElemId();
this.tabEditor = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-writer');
this.tabPreviewer = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-previewer');
- this.tabEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`);
- this.tabPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`);
+ this.tabEditor.setAttribute('data-tab', `markdown-writer-${tabIdSuffix}`);
+ this.tabPreviewer.setAttribute('data-tab', `markdown-previewer-${tabIdSuffix}`);
const panelEditor = this.container.querySelector('.ui.tab[data-tab-panel="markdown-writer"]');
const panelPreviewer = this.container.querySelector('.ui.tab[data-tab-panel="markdown-previewer"]');
- panelEditor.setAttribute('data-tab', `markdown-writer-${elementIdCounter}`);
- panelPreviewer.setAttribute('data-tab', `markdown-previewer-${elementIdCounter}`);
- elementIdCounter++;
+ panelEditor.setAttribute('data-tab', `markdown-writer-${tabIdSuffix}`);
+ panelPreviewer.setAttribute('data-tab', `markdown-previewer-${tabIdSuffix}`);
this.tabEditor.addEventListener('click', () => {
requestAnimationFrame(() => {
diff --git a/web_src/js/features/comp/ConfirmModal.ts b/web_src/js/features/comp/ConfirmModal.ts
index 1ce490ec2e..97a73eace6 100644
--- a/web_src/js/features/comp/ConfirmModal.ts
+++ b/web_src/js/features/comp/ConfirmModal.ts
@@ -1,24 +1,33 @@
import {svg} from '../../svg.ts';
-import {htmlEscape} from 'escape-goat';
+import {html, htmlRaw} from '../../utils/html.ts';
import {createElementFromHTML} from '../../utils/dom.ts';
import {fomanticQuery} from '../../modules/fomantic/base.ts';
const {i18n} = window.config;
-export function confirmModal({header = '', content = '', confirmButtonColor = 'primary'} = {}): Promise<boolean> {
- return new Promise((resolve) => {
- const headerHtml = header ? `<div class="header">${htmlEscape(header)}</div>` : '';
- const modal = createElementFromHTML(`
- <div class="ui g-modal-confirm modal">
- ${headerHtml}
- <div class="content">${htmlEscape(content)}</div>
- <div class="actions">
- <button class="ui cancel button">${svg('octicon-x')} ${htmlEscape(i18n.modal_cancel)}</button>
- <button class="ui ${confirmButtonColor} ok button">${svg('octicon-check')} ${htmlEscape(i18n.modal_confirm)}</button>
- </div>
+type ConfirmModalOptions = {
+ header?: string;
+ content?: string;
+ confirmButtonColor?: 'primary' | 'red' | 'green' | 'blue';
+}
+
+export function createConfirmModal({header = '', content = '', confirmButtonColor = 'primary'}:ConfirmModalOptions = {}): HTMLElement {
+ const headerHtml = header ? html`<div class="header">${header}</div>` : '';
+ return createElementFromHTML(html`
+ <div class="ui g-modal-confirm modal">
+ ${htmlRaw(headerHtml)}
+ <div class="content">${content}</div>
+ <div class="actions">
+ <button class="ui cancel button">${htmlRaw(svg('octicon-x'))} ${i18n.modal_cancel}</button>
+ <button class="ui ${confirmButtonColor} ok button">${htmlRaw(svg('octicon-check'))} ${i18n.modal_confirm}</button>
</div>
- `);
- document.body.append(modal);
+ </div>
+ `.trim());
+}
+
+export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise<boolean> {
+ if (!(modal instanceof HTMLElement)) modal = createConfirmModal(modal);
+ return new Promise((resolve) => {
const $modal = fomanticQuery(modal);
$modal.modal({
onApprove() {
diff --git a/web_src/js/features/comp/EditorUpload.test.ts b/web_src/js/features/comp/EditorUpload.test.ts
index 55f3f74389..e6e5f4de13 100644
--- a/web_src/js/features/comp/EditorUpload.test.ts
+++ b/web_src/js/features/comp/EditorUpload.test.ts
@@ -1,4 +1,4 @@
-import {removeAttachmentLinksFromMarkdown} from './EditorUpload.ts';
+import {pasteAsMarkdownLink, removeAttachmentLinksFromMarkdown} from './EditorUpload.ts';
test('removeAttachmentLinksFromMarkdown', () => {
expect(removeAttachmentLinksFromMarkdown('a foo b', 'foo')).toBe('a foo b');
@@ -12,3 +12,13 @@ test('removeAttachmentLinksFromMarkdown', () => {
expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo"> b', 'foo')).toBe('a b');
expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo" width="100"/> b', 'foo')).toBe('a b');
});
+
+test('preparePasteAsMarkdownLink', () => {
+ expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'bar')).toBeNull();
+ expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'https://gitea.com')).toBeNull();
+ expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'bar')).toBeNull();
+ expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[foo](https://gitea.com)');
+ expect(pasteAsMarkdownLink({value: '..(url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBe('[url](https://gitea.com)');
+ expect(pasteAsMarkdownLink({value: '[](url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBeNull();
+ expect(pasteAsMarkdownLink({value: 'https://example.com', selectionStart: 0, selectionEnd: 19}, 'https://gitea.com')).toBeNull();
+});
diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts
index f6d5731422..bf78f58daf 100644
--- a/web_src/js/features/comp/EditorUpload.ts
+++ b/web_src/js/features/comp/EditorUpload.ts
@@ -114,21 +114,30 @@ async function handleUploadFiles(editor: CodeMirrorEditor | TextareaEditor, drop
export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string) {
text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), '');
- text = text.replace(new RegExp(`<img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), '');
+ text = text.replace(new RegExp(`[<]img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), '');
return text;
}
-function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, text: string, isShiftDown: boolean) {
+export function pasteAsMarkdownLink(textarea: {value: string, selectionStart: number, selectionEnd: number}, pastedText: string): string | null {
+ const {value, selectionStart, selectionEnd} = textarea;
+ const selectedText = value.substring(selectionStart, selectionEnd);
+ const trimmedText = pastedText.trim();
+ const beforeSelection = value.substring(0, selectionStart);
+ const afterSelection = value.substring(selectionEnd);
+ const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')');
+ const asMarkdownLink = selectedText && isUrl(trimmedText) && !isUrl(selectedText) && !isInMarkdownLink;
+ return asMarkdownLink ? `[${selectedText}](${trimmedText})` : null;
+}
+
+function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, pastedText: string, isShiftDown: boolean) {
// pasting with "shift" means "paste as original content" in most applications
if (isShiftDown) return; // let the browser handle it
// when pasting links over selected text, turn it into [text](link)
- const {value, selectionStart, selectionEnd} = textarea;
- const selectedText = value.substring(selectionStart, selectionEnd);
- const trimmedText = text.trim();
- if (selectedText && isUrl(trimmedText) && !isUrl(selectedText)) {
+ const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText);
+ if (pastedAsMarkdown) {
e.preventDefault();
- replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`);
+ replaceTextareaSelection(textarea, pastedAsMarkdown);
}
// else, let the browser handle it
}
diff --git a/web_src/js/features/comp/LabelEdit.ts b/web_src/js/features/comp/LabelEdit.ts
index 55351cd900..423440129c 100644
--- a/web_src/js/features/comp/LabelEdit.ts
+++ b/web_src/js/features/comp/LabelEdit.ts
@@ -1,5 +1,6 @@
import {toggleElem} from '../../utils/dom.ts';
import {fomanticQuery} from '../../modules/fomantic/base.ts';
+import {submitFormFetchAction} from '../common-fetch-action.ts';
function nameHasScope(name: string): boolean {
return /.*[^/]\/[^/].*/.test(name);
@@ -70,7 +71,8 @@ export function initCompLabelEdit(pageSelector: string) {
form.reportValidity();
return false;
}
- form.submit();
+ submitFormFetchAction(form);
+ return false;
},
}).modal('show');
};
diff --git a/web_src/js/features/comp/SearchUserBox.ts b/web_src/js/features/comp/SearchUserBox.ts
index 9fedb3ed24..4b13a2141f 100644
--- a/web_src/js/features/comp/SearchUserBox.ts
+++ b/web_src/js/features/comp/SearchUserBox.ts
@@ -1,4 +1,4 @@
-import {htmlEscape} from 'escape-goat';
+import {htmlEscape} from '../../utils/html.ts';
import {fomanticQuery} from '../../modules/fomantic/base.ts';
const {appSubUrl} = window.config;
diff --git a/web_src/js/features/comp/TextExpander.ts b/web_src/js/features/comp/TextExpander.ts
index 5be234629d..2d79fe5029 100644
--- a/web_src/js/features/comp/TextExpander.ts
+++ b/web_src/js/features/comp/TextExpander.ts
@@ -97,6 +97,7 @@ export function initTextExpander(expander: TextExpanderElement) {
li.append(img);
const nameSpan = document.createElement('span');
+ nameSpan.classList.add('name');
nameSpan.textContent = name;
li.append(nameSpan);
diff --git a/web_src/js/features/copycontent.ts b/web_src/js/features/copycontent.ts
index d58f6c8246..0fec2a6235 100644
--- a/web_src/js/features/copycontent.ts
+++ b/web_src/js/features/copycontent.ts
@@ -9,17 +9,17 @@ const {i18n} = window.config;
export function initCopyContent() {
registerGlobalEventFunc('click', 'onCopyContentButtonClick', async (btn: HTMLElement) => {
if (btn.classList.contains('disabled') || btn.classList.contains('is-loading')) return;
- let content;
- let isRasterImage = false;
- const link = btn.getAttribute('data-link');
+ const rawFileLink = btn.getAttribute('data-raw-file-link');
- // when data-link is present, we perform a fetch. this is either because
- // the text to copy is not in the DOM, or it is an image which should be
+ let content, isRasterImage = false;
+
+ // when "data-raw-link" is present, we perform a fetch. this is either because
+ // the text to copy is not in the DOM, or it is an image that should be
// fetched to copy in full resolution
- if (link) {
+ if (rawFileLink) {
btn.classList.add('is-loading', 'loading-icon-2px');
try {
- const res = await GET(link, {credentials: 'include', redirect: 'follow'});
+ const res = await GET(rawFileLink, {credentials: 'include', redirect: 'follow'});
const contentType = res.headers.get('content-type');
if (contentType.startsWith('image/') && !contentType.startsWith('image/svg')) {
diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts
index b2ba7651c4..20f7ceb6c3 100644
--- a/web_src/js/features/dropzone.ts
+++ b/web_src/js/features/dropzone.ts
@@ -1,5 +1,5 @@
import {svg} from '../svg.ts';
-import {htmlEscape} from 'escape-goat';
+import {html} from '../utils/html.ts';
import {clippie} from 'clippie';
import {showTemporaryTooltip} from '../modules/tippy.ts';
import {GET, POST} from '../modules/fetch.ts';
@@ -33,14 +33,14 @@ export function generateMarkdownLinkForAttachment(file: Partial<CustomDropzoneFi
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
// method to change image size in Markdown that is supported by all implementations.
// Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}"
- fileMarkdown = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(file.name)}" src="attachments/${htmlEscape(file.uuid)}">`;
+ fileMarkdown = html`<img width="${Math.round(width / dppx)}" alt="${file.name}" src="attachments/${file.uuid}">`;
} else {
// Markdown always renders the image with a relative path, so the final URL is "/sub-path/owner/repo/attachments/{uuid}"
// TODO: it should also use relative path for consistency, because absolute is ambiguous for "/sub-path/attachments" or "/attachments"
fileMarkdown = `![${file.name}](/attachments/${file.uuid})`;
}
} else if (isVideoFile(file)) {
- fileMarkdown = `<video src="attachments/${htmlEscape(file.uuid)}" title="${htmlEscape(file.name)}" controls></video>`;
+ fileMarkdown = html`<video src="attachments/${file.uuid}" title="${file.name}" controls></video>`;
}
return fileMarkdown;
}
diff --git a/web_src/js/features/emoji.ts b/web_src/js/features/emoji.ts
index 135620e51e..69afe491e2 100644
--- a/web_src/js/features/emoji.ts
+++ b/web_src/js/features/emoji.ts
@@ -1,4 +1,5 @@
import emojis from '../../../assets/emoji.json' with {type: 'json'};
+import {html} from '../utils/html.ts';
const {assetUrlPrefix, customEmojis} = window.config;
@@ -24,12 +25,11 @@ for (const key of emojiKeys) {
export function emojiHTML(name: string) {
let inner;
if (Object.hasOwn(customEmojis, name)) {
- inner = `<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`;
+ inner = html`<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`;
} else {
inner = emojiString(name);
}
-
- return `<span class="emoji" title=":${name}:">${inner}</span>`;
+ return html`<span class="emoji" title=":${name}:">${inner}</span>`;
}
// retrieve string for given emoji name
diff --git a/web_src/js/features/file-view.ts b/web_src/js/features/file-view.ts
new file mode 100644
index 0000000000..d803f53c0d
--- /dev/null
+++ b/web_src/js/features/file-view.ts
@@ -0,0 +1,76 @@
+import type {FileRenderPlugin} from '../render/plugin.ts';
+import {newRenderPlugin3DViewer} from '../render/plugins/3d-viewer.ts';
+import {newRenderPluginPdfViewer} from '../render/plugins/pdf-viewer.ts';
+import {registerGlobalInitFunc} from '../modules/observer.ts';
+import {createElementFromHTML, showElem, toggleClass} from '../utils/dom.ts';
+import {html} from '../utils/html.ts';
+import {basename} from '../utils.ts';
+
+const plugins: FileRenderPlugin[] = [];
+
+function initPluginsOnce(): void {
+ if (plugins.length) return;
+ plugins.push(newRenderPlugin3DViewer(), newRenderPluginPdfViewer());
+}
+
+function findFileRenderPlugin(filename: string, mimeType: string): FileRenderPlugin | null {
+ return plugins.find((plugin) => plugin.canHandle(filename, mimeType)) || null;
+}
+
+function showRenderRawFileButton(elFileView: HTMLElement, renderContainer: HTMLElement | null): void {
+ const toggleButtons = elFileView.querySelector('.file-view-toggle-buttons');
+ showElem(toggleButtons);
+ const displayingRendered = Boolean(renderContainer);
+ toggleClass(toggleButtons.querySelectorAll('.file-view-toggle-source'), 'active', !displayingRendered); // it may not exist
+ toggleClass(toggleButtons.querySelector('.file-view-toggle-rendered'), 'active', displayingRendered);
+ // TODO: if there is only one button, hide it?
+}
+
+async function renderRawFileToContainer(container: HTMLElement, rawFileLink: string, mimeType: string) {
+ const elViewRawPrompt = container.querySelector('.file-view-raw-prompt');
+ if (!rawFileLink || !elViewRawPrompt) throw new Error('unexpected file view container');
+
+ let rendered = false, errorMsg = '';
+ try {
+ const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType);
+ if (plugin) {
+ container.classList.add('is-loading');
+ container.setAttribute('data-render-name', plugin.name); // not used yet
+ await plugin.render(container, rawFileLink);
+ rendered = true;
+ }
+ } catch (e) {
+ errorMsg = `${e}`;
+ } finally {
+ container.classList.remove('is-loading');
+ }
+
+ if (rendered) {
+ elViewRawPrompt.remove();
+ return;
+ }
+
+ // remove all children from the container, and only show the raw file link
+ container.replaceChildren(elViewRawPrompt);
+
+ if (errorMsg) {
+ const elErrorMessage = createElementFromHTML(html`<div class="ui error message">${errorMsg}</div>`);
+ elViewRawPrompt.insertAdjacentElement('afterbegin', elErrorMessage);
+ }
+}
+
+export function initRepoFileView(): void {
+ registerGlobalInitFunc('initRepoFileView', async (elFileView: HTMLElement) => {
+ initPluginsOnce();
+ const rawFileLink = elFileView.getAttribute('data-raw-file-link');
+ const mimeType = elFileView.getAttribute('data-mime-type') || ''; // not used yet
+ // TODO: we should also provide the prefetched file head bytes to let the plugin decide whether to render or not
+ const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType);
+ if (!plugin) return;
+
+ const renderContainer = elFileView.querySelector<HTMLElement>('.file-view-render-container');
+ showRenderRawFileButton(elFileView, renderContainer);
+ // maybe in the future multiple plugins can render the same file, so we should not assume only one plugin will render it
+ if (renderContainer) await renderRawFileToContainer(renderContainer, rawFileLink, mimeType);
+ });
+}
diff --git a/web_src/js/features/install.ts b/web_src/js/features/install.ts
index 34df4757f9..ca4bcce881 100644
--- a/web_src/js/features/install.ts
+++ b/web_src/js/features/install.ts
@@ -104,7 +104,7 @@ function initPreInstall() {
}
function initPostInstall() {
- const el = document.querySelector('#goto-user-login');
+ const el = document.querySelector('#goto-after-install');
if (!el) return;
const targetUrl = el.getAttribute('href');
diff --git a/web_src/js/features/repo-code.ts b/web_src/js/features/repo-code.ts
index c699de59e6..bf7fd762b0 100644
--- a/web_src/js/features/repo-code.ts
+++ b/web_src/js/features/repo-code.ts
@@ -110,10 +110,15 @@ function showLineButton() {
}
export function initRepoCodeView() {
- if (!document.querySelector('.code-view .lines-num')) return;
+ // When viewing a file or blame, there is always a ".file-view" element,
+ // but the ".code-view" class is only present when viewing the "code" of a file; it is not present when viewing a PDF file.
+ // Since the ".file-view" will be dynamically reloaded when navigating via the left file tree (eg: view a PDF file, then view a source code file, etc.)
+ // the "code-view" related event listeners should always be added when the current page contains ".file-view" element.
+ if (!document.querySelector('.repo-view-container .file-view')) return;
+ // "file code view" and "blame" pages need this "line number button" feature
let selRangeStart: string;
- addDelegatedEventListener(document, 'click', '.lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
+ addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
if (!selRangeStart || !e.shiftKey) {
selRangeStart = el.getAttribute('id');
selectRange(selRangeStart);
@@ -125,12 +130,14 @@ export function initRepoCodeView() {
showLineButton();
});
+ // apply the selected range from the URL hash
const onHashChange = () => {
if (!window.location.hash) return;
+ if (!document.querySelector('.code-view .lines-num')) return;
const range = window.location.hash.substring(1);
const first = selectRange(range);
if (first) {
- // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
+ // set scrollRestoration to 'manual' when there is a hash in the URL, so that the scroll position will not be remembered after refreshing
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
first.scrollIntoView({block: 'start'});
showLineButton();
diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts
index 0f77508f70..f3ca13460c 100644
--- a/web_src/js/features/repo-editor.ts
+++ b/web_src/js/features/repo-editor.ts
@@ -1,4 +1,4 @@
-import {htmlEscape} from 'escape-goat';
+import {html, htmlRaw} from '../utils/html.ts';
import {createCodeEditor} from './codeeditor.ts';
import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts';
import {attachRefIssueContextPopup} from './contextpopup.ts';
@@ -7,6 +7,7 @@ import {initDropzone} from './dropzone.ts';
import {confirmModal} from './comp/ConfirmModal.ts';
import {applyAreYouSure, ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
+import {submitFormFetchAction} from './common-fetch-action.ts';
function initEditPreviewTab(elForm: HTMLFormElement) {
const elTabMenu = elForm.querySelector('.repo-editor-menu');
@@ -86,10 +87,10 @@ export function initRepoEditor() {
if (i < parts.length - 1) {
if (trimValue.length) {
const linkElement = createElementFromHTML(
- `<span class="section"><a href="#">${htmlEscape(value)}</a></span>`,
+ html`<span class="section"><a href="#">${value}</a></span>`,
);
const dividerElement = createElementFromHTML(
- `<div class="breadcrumb-divider">/</div>`,
+ html`<div class="breadcrumb-divider">/</div>`,
);
links.push(linkElement);
dividers.push(dividerElement);
@@ -112,7 +113,7 @@ export function initRepoEditor() {
if (!warningDiv) {
warningDiv = document.createElement('div');
warningDiv.classList.add('ui', 'warning', 'message', 'flash-message', 'flash-warning', 'space-related');
- warningDiv.innerHTML = '<p>File path contains leading or trailing whitespace.</p>';
+ warningDiv.innerHTML = html`<p>File path contains leading or trailing whitespace.</p>`;
// Add display 'block' because display is set to 'none' in formantic\build\semantic.css
warningDiv.style.display = 'block';
const inputContainer = document.querySelector('.repo-editor-header');
@@ -141,38 +142,36 @@ export function initRepoEditor() {
}
});
+ const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form');
+
// on the upload page, there is no editor(textarea)
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
if (!editArea) return;
- const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form');
+ // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
+ // to enable or disable the commit button
+ const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
+ const dirtyFileClass = 'dirty-file';
+
+ const syncCommitButtonState = () => {
+ const dirty = elForm.classList.contains(dirtyFileClass);
+ commitButton.disabled = !dirty;
+ };
+ // Registering a custom listener for the file path and the file content
+ // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
+ applyAreYouSure(elForm, {
+ silent: true,
+ dirtyClass: dirtyFileClass,
+ fieldSelector: ':input:not(.commit-form-wrapper :input)',
+ change: syncCommitButtonState,
+ });
+ syncCommitButtonState(); // disable the "commit" button when no content changes
+
initEditPreviewTab(elForm);
(async () => {
const editor = await createCodeEditor(editArea, filenameInput);
- // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
- // to enable or disable the commit button
- const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
- const dirtyFileClass = 'dirty-file';
-
- // Disabling the button at the start
- if (document.querySelector<HTMLInputElement>('input[name="page_has_posted"]').value !== 'true') {
- commitButton.disabled = true;
- }
-
- // Registering a custom listener for the file path and the file content
- // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
- applyAreYouSure(elForm, {
- silent: true,
- dirtyClass: dirtyFileClass,
- fieldSelector: ':input:not(.commit-form-wrapper :input)',
- change($form: any) {
- const dirty = $form[0]?.classList.contains(dirtyFileClass);
- commitButton.disabled = !dirty;
- },
- });
-
// Update the editor from query params, if available,
// only after the dirtyFileClass initialization
const params = new URLSearchParams(window.location.search);
@@ -181,7 +180,7 @@ export function initRepoEditor() {
editor.setValue(value);
}
- commitButton?.addEventListener('click', async (e) => {
+ commitButton.addEventListener('click', async (e) => {
// A modal which asks if an empty file should be committed
if (!editArea.value) {
e.preventDefault();
@@ -190,14 +189,15 @@ export function initRepoEditor() {
content: elForm.getAttribute('data-text-empty-confirm-content'),
})) {
ignoreAreYouSure(elForm);
- elForm.submit();
+ submitFormFetchAction(elForm);
}
}
});
})();
}
-export function renderPreviewPanelContent(previewPanel: Element, content: string) {
- previewPanel.innerHTML = `<div class="render-content markup">${content}</div>`;
+export function renderPreviewPanelContent(previewPanel: Element, htmlContent: string) {
+ // the content is from the server, so it is safe to use innerHTML
+ previewPanel.innerHTML = html`<div class="render-content markup">${htmlRaw(htmlContent)}</div>`;
attachRefIssueContextPopup(previewPanel.querySelectorAll('p .ref-issue'));
}
diff --git a/web_src/js/features/repo-graph.ts b/web_src/js/features/repo-graph.ts
index 7579ee42c6..036a55f715 100644
--- a/web_src/js/features/repo-graph.ts
+++ b/web_src/js/features/repo-graph.ts
@@ -1,4 +1,4 @@
-import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
+import {toggleClass} from '../utils/dom.ts';
import {GET} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
@@ -6,87 +6,59 @@ export function initRepoGraphGit() {
const graphContainer = document.querySelector<HTMLElement>('#git-graph-container');
if (!graphContainer) return;
- document.querySelector('#flow-color-monochrome')?.addEventListener('click', () => {
- document.querySelector('#flow-color-monochrome').classList.add('active');
- document.querySelector('#flow-color-colored')?.classList.remove('active');
- graphContainer.classList.remove('colored');
- graphContainer.classList.add('monochrome');
+ const elColorMonochrome = document.querySelector<HTMLElement>('#flow-color-monochrome');
+ const elColorColored = document.querySelector<HTMLElement>('#flow-color-colored');
+ const toggleColorMode = (mode: 'monochrome' | 'colored') => {
+ toggleClass(graphContainer, 'monochrome', mode === 'monochrome');
+ toggleClass(graphContainer, 'colored', mode === 'colored');
+
+ toggleClass(elColorMonochrome, 'active', mode === 'monochrome');
+ toggleClass(elColorColored, 'active', mode === 'colored');
+
const params = new URLSearchParams(window.location.search);
- params.set('mode', 'monochrome');
- const queryString = params.toString();
- if (queryString) {
- window.history.replaceState({}, '', `?${queryString}`);
- } else {
- window.history.replaceState({}, '', window.location.pathname);
- }
- for (const link of document.querySelectorAll('.pagination a')) {
+ params.set('mode', mode);
+ window.history.replaceState(null, '', `?${params.toString()}`);
+ for (const link of document.querySelectorAll('#git-graph-body .pagination a')) {
const href = link.getAttribute('href');
if (!href) continue;
const url = new URL(href, window.location.href);
const params = url.searchParams;
- params.set('mode', 'monochrome');
+ params.set('mode', mode);
url.search = `?${params.toString()}`;
link.setAttribute('href', url.href);
}
- });
+ };
+ elColorMonochrome.addEventListener('click', () => toggleColorMode('monochrome'));
+ elColorColored.addEventListener('click', () => toggleColorMode('colored'));
- document.querySelector('#flow-color-colored')?.addEventListener('click', () => {
- document.querySelector('#flow-color-colored').classList.add('active');
- document.querySelector('#flow-color-monochrome')?.classList.remove('active');
- graphContainer.classList.add('colored');
- graphContainer.classList.remove('monochrome');
- for (const link of document.querySelectorAll('.pagination a')) {
- const href = link.getAttribute('href');
- if (!href) continue;
- const url = new URL(href, window.location.href);
- const params = url.searchParams;
- params.delete('mode');
- url.search = `?${params.toString()}`;
- link.setAttribute('href', url.href);
- }
- const params = new URLSearchParams(window.location.search);
- params.delete('mode');
- const queryString = params.toString();
- if (queryString) {
- window.history.replaceState({}, '', `?${queryString}`);
- } else {
- window.history.replaceState({}, '', window.location.pathname);
- }
- });
+ const elGraphBody = document.querySelector<HTMLElement>('#git-graph-body');
const url = new URL(window.location.href);
const params = url.searchParams;
- const updateGraph = () => {
+ const loadGitGraph = async () => {
const queryString = params.toString();
const ajaxUrl = new URL(url);
ajaxUrl.searchParams.set('div-only', 'true');
- window.history.replaceState({}, '', queryString ? `?${queryString}` : window.location.pathname);
- document.querySelector('#pagination').innerHTML = '';
- hideElem('#rel-container');
- hideElem('#rev-container');
- showElem('#loading-indicator');
- (async () => {
- const response = await GET(String(ajaxUrl));
- const html = await response.text();
- const div = document.createElement('div');
- div.innerHTML = html;
- document.querySelector('#pagination').innerHTML = div.querySelector('#pagination').innerHTML;
- document.querySelector('#rel-container').innerHTML = div.querySelector('#rel-container').innerHTML;
- document.querySelector('#rev-container').innerHTML = div.querySelector('#rev-container').innerHTML;
- hideElem('#loading-indicator');
- showElem('#rel-container');
- showElem('#rev-container');
- })();
+ window.history.replaceState(null, '', queryString ? `?${queryString}` : window.location.pathname);
+
+ elGraphBody.classList.add('is-loading');
+ try {
+ const resp = await GET(ajaxUrl.toString());
+ elGraphBody.innerHTML = await resp.text();
+ } finally {
+ elGraphBody.classList.remove('is-loading');
+ }
};
+
const dropdownSelected = params.getAll('branch');
if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') {
dropdownSelected.splice(0, 0, '...flow-hide-pr-refs');
}
- const flowSelectRefsDropdown = document.querySelector('#flow-select-refs-dropdown');
- const $dropdown = fomanticQuery(flowSelectRefsDropdown);
- $dropdown.dropdown({
- clearable: true,
- fullTextSeach: 'exact',
+ const $dropdown = fomanticQuery('#flow-select-refs-dropdown');
+ $dropdown.dropdown({clearable: true});
+ $dropdown.dropdown('set selected', dropdownSelected);
+ // must add the callback after setting the selected items, otherwise each "selected" item will trigger the callback
+ $dropdown.dropdown('setting', {
onRemove(toRemove: string) {
if (toRemove === '...flow-hide-pr-refs') {
params.delete('hide-pr-refs');
@@ -99,7 +71,7 @@ export function initRepoGraphGit() {
}
}
}
- updateGraph();
+ loadGitGraph();
},
onAdd(toAdd: string) {
if (toAdd === '...flow-hide-pr-refs') {
@@ -107,50 +79,7 @@ export function initRepoGraphGit() {
} else {
params.append('branch', toAdd);
}
- updateGraph();
+ loadGitGraph();
},
});
- $dropdown.dropdown('set selected', dropdownSelected);
-
- graphContainer.addEventListener('mouseenter', (e: DOMEvent<MouseEvent>) => {
- if (e.target.matches('#rev-list li')) {
- const flow = e.target.getAttribute('data-flow');
- if (flow === '0') return;
- document.querySelector(`#flow-${flow}`)?.classList.add('highlight');
- e.target.classList.add('hover');
- for (const item of document.querySelectorAll(`#rev-list li[data-flow='${flow}']`)) {
- item.classList.add('highlight');
- }
- } else if (e.target.matches('#rel-container .flow-group')) {
- e.target.classList.add('highlight');
- const flow = e.target.getAttribute('data-flow');
- for (const item of document.querySelectorAll(`#rev-list li[data-flow='${flow}']`)) {
- item.classList.add('highlight');
- }
- } else if (e.target.matches('#rel-container .flow-commit')) {
- const rev = e.target.getAttribute('data-rev');
- document.querySelector(`#rev-list li#commit-${rev}`)?.classList.add('hover');
- }
- });
-
- graphContainer.addEventListener('mouseleave', (e: DOMEvent<MouseEvent>) => {
- if (e.target.matches('#rev-list li')) {
- const flow = e.target.getAttribute('data-flow');
- if (flow === '0') return;
- document.querySelector(`#flow-${flow}`)?.classList.remove('highlight');
- e.target.classList.remove('hover');
- for (const item of document.querySelectorAll(`#rev-list li[data-flow='${flow}']`)) {
- item.classList.remove('highlight');
- }
- } else if (e.target.matches('#rel-container .flow-group')) {
- e.target.classList.remove('highlight');
- const flow = e.target.getAttribute('data-flow');
- for (const item of document.querySelectorAll(`#rev-list li[data-flow='${flow}']`)) {
- item.classList.remove('highlight');
- }
- } else if (e.target.matches('#rel-container .flow-commit')) {
- const rev = e.target.getAttribute('data-rev');
- document.querySelector(`#rev-list li#commit-${rev}`)?.classList.remove('hover');
- }
- });
}
diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts
index 8cd4483357..762fbf51bb 100644
--- a/web_src/js/features/repo-issue-list.ts
+++ b/web_src/js/features/repo-issue-list.ts
@@ -1,6 +1,6 @@
import {updateIssuesMeta} from './repo-common.ts';
-import {toggleElem, isElemHidden, queryElems} from '../utils/dom.ts';
-import {htmlEscape} from 'escape-goat';
+import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts';
+import {html} from '../utils/html.ts';
import {confirmModal} from './comp/ConfirmModal.ts';
import {showErrorToast} from '../modules/toast.ts';
import {createSortable} from '../modules/sortable.ts';
@@ -33,8 +33,8 @@ function initRepoIssueListCheckboxes() {
toggleElem('#issue-filters', !anyChecked);
toggleElem('#issue-actions', anyChecked);
// there are two panels but only one select-all checkbox, so move the checkbox to the visible panel
- const panels = document.querySelectorAll('#issue-filters, #issue-actions');
- const visiblePanel = Array.from(panels).find((el) => !isElemHidden(el));
+ const panels = document.querySelectorAll<HTMLElement>('#issue-filters, #issue-actions');
+ const visiblePanel = Array.from(panels).find((el) => isElemVisible(el));
const toolbarLeft = visiblePanel.querySelector('.issue-list-toolbar-left');
toolbarLeft.prepend(issueSelectAll);
};
@@ -138,10 +138,10 @@ function initDropdownUserRemoteSearch(el: Element) {
// the content is provided by backend IssuePosters handler
processedResults.length = 0;
for (const item of resp.results) {
- let html = `<img class="ui avatar tw-align-middle" src="${htmlEscape(item.avatar_link)}" aria-hidden="true" alt width="20" height="20"><span class="gt-ellipsis">${htmlEscape(item.username)}</span>`;
- if (item.full_name) html += `<span class="search-fullname tw-ml-2">${htmlEscape(item.full_name)}</span>`;
+ let nameHtml = html`<img class="ui avatar tw-align-middle" src="${item.avatar_link}" aria-hidden="true" alt width="20" height="20"><span class="gt-ellipsis">${item.username}</span>`;
+ if (item.full_name) nameHtml += html`<span class="search-fullname tw-ml-2">${item.full_name}</span>`;
if (selectedUsername.toLowerCase() === item.username.toLowerCase()) selectedUsername = item.username;
- processedResults.push({value: item.username, name: html});
+ processedResults.push({value: item.username, name: nameHtml});
}
resp.results = processedResults;
return resp;
diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts
index bc7d4dee19..49e8fc40a2 100644
--- a/web_src/js/features/repo-issue.ts
+++ b/web_src/js/features/repo-issue.ts
@@ -1,4 +1,4 @@
-import {htmlEscape} from 'escape-goat';
+import {html, htmlEscape} from '../utils/html.ts';
import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts';
import {
addDelegatedEventListener,
@@ -17,6 +17,7 @@ import {showErrorToast} from '../modules/toast.ts';
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
+import {registerGlobalInitFunc} from '../modules/observer.ts';
const {appSubUrl} = window.config;
@@ -45,8 +46,7 @@ export function initRepoIssueSidebarDependency() {
if (String(issue.id) === currIssueId) continue;
filteredResponse.results.push({
value: issue.id,
- name: `<div class="gt-ellipsis">#${issue.number} ${htmlEscape(issue.title)}</div>
-<div class="text small tw-break-anywhere">${htmlEscape(issue.repository.full_name)}</div>`,
+ name: html`<div class="gt-ellipsis">#${issue.number} ${issue.title}</div><div class="text small tw-break-anywhere">${issue.repository.full_name}</div>`,
});
}
return filteredResponse;
@@ -416,25 +416,20 @@ export function initRepoIssueWipNewTitle() {
export function initRepoIssueWipToggle() {
// Toggle WIP for existing PR
- queryElems(document, '.toggle-wip', (el) => el.addEventListener('click', async (e) => {
+ registerGlobalInitFunc('initPullRequestWipToggle', (toggleWip) => toggleWip.addEventListener('click', async (e) => {
e.preventDefault();
- const toggleWip = el;
const title = toggleWip.getAttribute('data-title');
const wipPrefix = toggleWip.getAttribute('data-wip-prefix');
const updateUrl = toggleWip.getAttribute('data-update-url');
- try {
- const params = new URLSearchParams();
- params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`);
-
- const response = await POST(updateUrl, {data: params});
- if (!response.ok) {
- throw new Error('Failed to toggle WIP status');
- }
- window.location.reload();
- } catch (error) {
- console.error(error);
+ const params = new URLSearchParams();
+ params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`);
+ const response = await POST(updateUrl, {data: params});
+ if (!response.ok) {
+ showErrorToast(`Failed to toggle 'work in progress' status`);
+ return;
}
+ window.location.reload();
}));
}
diff --git a/web_src/js/features/repo-new.ts b/web_src/js/features/repo-new.ts
index 0e4d78872d..e2aa13f490 100644
--- a/web_src/js/features/repo-new.ts
+++ b/web_src/js/features/repo-new.ts
@@ -1,5 +1,5 @@
import {hideElem, querySingleVisibleElem, showElem, toggleElem} from '../utils/dom.ts';
-import {htmlEscape} from 'escape-goat';
+import {htmlEscape} from '../utils/html.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {sanitizeRepoName} from './repo-common.ts';
diff --git a/web_src/js/features/repo-projects.ts b/web_src/js/features/repo-projects.ts
index dc4aa8274b..ad0feb6101 100644
--- a/web_src/js/features/repo-projects.ts
+++ b/web_src/js/features/repo-projects.ts
@@ -114,7 +114,6 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
window.location.reload(); // newly added column, need to reload the page
return;
}
- fomanticQuery(elModal).modal('hide');
// update the newly saved column title and color in the project board (to avoid reload)
const elEditButton = writableProjectBoard.querySelector<HTMLButtonElement>(`.show-project-column-modal-edit[${attrDataColumnId}="${columnId}"]`);
@@ -134,6 +133,8 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
elBoardColumn.style.removeProperty('color');
queryElemChildren<HTMLElement>(elBoardColumn, '.divider', (divider) => divider.style.removeProperty('color'));
}
+
+ fomanticQuery(elModal).modal('hide');
} finally {
elForm.classList.remove('is-loading');
}
diff --git a/web_src/js/features/repo-wiki.ts b/web_src/js/features/repo-wiki.ts
index f94d3ef3d1..6ae0947077 100644
--- a/web_src/js/features/repo-wiki.ts
+++ b/web_src/js/features/repo-wiki.ts
@@ -2,6 +2,7 @@ import {validateTextareaNonEmpty, initComboMarkdownEditor} from './comp/ComboMar
import {fomanticMobileScreen} from '../modules/fomantic.ts';
import {POST} from '../modules/fetch.ts';
import type {ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
+import {html, htmlRaw} from '../utils/html.ts';
async function initRepoWikiFormEditor() {
const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea');
@@ -30,7 +31,7 @@ async function initRepoWikiFormEditor() {
const response = await POST(editor.previewUrl, {data: formData});
const data = await response.text();
lastContent = newContent;
- previewTarget.innerHTML = `<div class="render-content markup ui segment">${data}</div>`;
+ previewTarget.innerHTML = html`<div class="render-content markup ui segment">${htmlRaw(data)}</div>`;
} catch (error) {
console.error('Error rendering preview:', error);
} finally {
diff --git a/web_src/js/features/stopwatch.ts b/web_src/js/features/stopwatch.ts
index a5cd5ae7c4..07f9c435b8 100644
--- a/web_src/js/features/stopwatch.ts
+++ b/web_src/js/features/stopwatch.ts
@@ -134,7 +134,7 @@ function updateStopwatchData(data: any) {
const {repo_owner_name, repo_name, issue_index, seconds} = watch;
const issueUrl = `${appSubUrl}/${repo_owner_name}/${repo_name}/issues/${issue_index}`;
document.querySelector('.stopwatch-link')?.setAttribute('href', issueUrl);
- document.querySelector('.stopwatch-commit')?.setAttribute('action', `${issueUrl}/times/stopwatch/toggle`);
+ document.querySelector('.stopwatch-commit')?.setAttribute('action', `${issueUrl}/times/stopwatch/stop`);
document.querySelector('.stopwatch-cancel')?.setAttribute('action', `${issueUrl}/times/stopwatch/cancel`);
const stopwatchIssue = document.querySelector('.stopwatch-issue');
if (stopwatchIssue) stopwatchIssue.textContent = `${repo_owner_name}/${repo_name}#${issue_index}`;
diff --git a/web_src/js/features/tribute.ts b/web_src/js/features/tribute.ts
index cf98377ae7..43c21ebe6d 100644
--- a/web_src/js/features/tribute.ts
+++ b/web_src/js/features/tribute.ts
@@ -1,5 +1,5 @@
import {emojiKeys, emojiHTML, emojiString} from './emoji.ts';
-import {htmlEscape} from 'escape-goat';
+import {html, htmlRaw} from '../utils/html.ts';
type TributeItem = Record<string, any>;
@@ -26,17 +26,18 @@ export async function attachTribute(element: HTMLElement) {
return emojiString(item.original);
},
menuItemTemplate: (item: TributeItem) => {
- return `<div class="tribute-item">${emojiHTML(item.original)}<span>${htmlEscape(item.original)}</span></div>`;
+ return html`<div class="tribute-item">${htmlRaw(emojiHTML(item.original))}<span>${item.original}</span></div>`;
},
}, { // mentions
values: window.config.mentionValues ?? [],
requireLeadingSpace: true,
menuItemTemplate: (item: TributeItem) => {
- return `
+ const fullNameHtml = item.original.fullname && item.original.fullname !== '' ? html`<span class="fullname">${item.original.fullname}</span>` : '';
+ return html`
<div class="tribute-item">
- <img alt src="${htmlEscape(item.original.avatar)}" width="21" height="21"/>
- <span class="name">${htmlEscape(item.original.name)}</span>
- ${item.original.fullname && item.original.fullname !== '' ? `<span class="fullname">${htmlEscape(item.original.fullname)}</span>` : ''}
+ <img alt src="${item.original.avatar}" width="21" height="21"/>
+ <span class="name">${item.original.name}</span>
+ ${htmlRaw(fullNameHtml)}
</div>
`;
},
diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts
index 9e97ec0492..e4b540122d 100644
--- a/web_src/js/globals.d.ts
+++ b/web_src/js/globals.d.ts
@@ -25,11 +25,6 @@ declare module 'htmx.org/dist/htmx.esm.js' {
export default value;
}
-declare module 'uint8-to-base64' {
- export function encode(arrayBuffer: Uint8Array): string;
- export function decode(base64str: string): Uint8Array;
-}
-
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
const value = await import('swagger-ui-dist');
export default value.SwaggerUIBundle;
diff --git a/web_src/js/index.ts b/web_src/js/index.ts
index 7e84773bc1..347aad2709 100644
--- a/web_src/js/index.ts
+++ b/web_src/js/index.ts
@@ -19,7 +19,7 @@ import {initRepoIssueContentHistory} from './features/repo-issue-content.ts';
import {initStopwatch} from './features/stopwatch.ts';
import {initFindFileInRepo} from './features/repo-findfile.ts';
import {initMarkupContent} from './markup/content.ts';
-import {initPdfViewer} from './render/pdf.ts';
+import {initRepoFileView} from './features/file-view.ts';
import {initUserAuthOauth2, initUserCheckAppUrl} from './features/user-auth.ts';
import {initRepoPullRequestAllowMaintainerEdit, initRepoPullRequestReview, initRepoIssueSidebarDependency, initRepoIssueFilterItemLabel} from './features/repo-issue.ts';
import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts';
@@ -159,10 +159,11 @@ onDomReady(() => {
initUserAuthWebAuthnRegister,
initUserSettings,
initRepoDiffView,
- initPdfViewer,
initColorPickers,
initOAuth2SettingsDisableCheckbox,
+
+ initRepoFileView,
]);
// it must be the last one, then the "querySelectorAll" only needs to be executed once for global init functions.
diff --git a/web_src/js/markup/anchors.ts b/web_src/js/markup/anchors.ts
index 483d72bd5b..a0d49911fe 100644
--- a/web_src/js/markup/anchors.ts
+++ b/web_src/js/markup/anchors.ts
@@ -5,21 +5,24 @@ const removePrefix = (str: string): string => str.replace(/^user-content-/, '');
const hasPrefix = (str: string): boolean => str.startsWith('user-content-');
// scroll to anchor while respecting the `user-content` prefix that exists on the target
-function scrollToAnchor(encodedId: string): void {
- if (!encodedId) return;
- const id = decodeURIComponent(encodedId);
- const prefixedId = addPrefix(id);
- let el = document.querySelector(`#${prefixedId}`);
+function scrollToAnchor(encodedId?: string): void {
+ // FIXME: need to rewrite this function with new a better markup anchor generation logic, too many tricks here
+ let elemId: string;
+ try {
+ elemId = decodeURIComponent(encodedId ?? '');
+ } catch {} // ignore the errors, since the "encodedId" is from user's input
+ if (!elemId) return;
+
+ const prefixedId = addPrefix(elemId);
+ // eslint-disable-next-line unicorn/prefer-query-selector
+ let el = document.getElementById(prefixedId);
// check for matching user-generated `a[name]`
- if (!el) {
- el = document.querySelector(`a[name="${CSS.escape(prefixedId)}"]`);
- }
+ el = el ?? document.querySelector(`a[name="${CSS.escape(prefixedId)}"]`);
// compat for links with old 'user-content-' prefixed hashes
- if (!el && hasPrefix(id)) {
- return document.querySelector(`#${id}`)?.scrollIntoView();
- }
+ // eslint-disable-next-line unicorn/prefer-query-selector
+ el = (!el && hasPrefix(elemId)) ? document.getElementById(elemId) : el;
el?.scrollIntoView();
}
diff --git a/web_src/js/markup/html2markdown.ts b/web_src/js/markup/html2markdown.ts
index 8c2d2f8c86..5866d0d259 100644
--- a/web_src/js/markup/html2markdown.ts
+++ b/web_src/js/markup/html2markdown.ts
@@ -1,4 +1,4 @@
-import {htmlEscape} from 'escape-goat';
+import {html, htmlRaw} from '../utils/html.ts';
type Processor = (el: HTMLElement) => string | HTMLElement | void;
@@ -38,10 +38,10 @@ function prepareProcessors(ctx:ProcessorContext): Processors {
IMG(el: HTMLElement) {
const alt = el.getAttribute('alt') || 'image';
const src = el.getAttribute('src');
- const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : '';
- const heightAttr = el.hasAttribute('height') ? ` height="${htmlEscape(el.getAttribute('height') || '')}"` : '';
+ const widthAttr = el.hasAttribute('width') ? htmlRaw` width="${el.getAttribute('width') || ''}"` : '';
+ const heightAttr = el.hasAttribute('height') ? htmlRaw` height="${el.getAttribute('height') || ''}"` : '';
if (widthAttr || heightAttr) {
- return `<img alt="${htmlEscape(alt)}"${widthAttr}${heightAttr} src="${htmlEscape(src)}">`;
+ return html`<img alt="${alt}"${widthAttr}${heightAttr} src="${src}">`;
}
return `![${alt}](${src})`;
},
diff --git a/web_src/js/markup/mermaid.ts b/web_src/js/markup/mermaid.ts
index ac24b3bcba..33d9a1ed9b 100644
--- a/web_src/js/markup/mermaid.ts
+++ b/web_src/js/markup/mermaid.ts
@@ -2,6 +2,7 @@ import {isDarkTheme} from '../utils.ts';
import {makeCodeCopyButton} from './codecopy.ts';
import {displayError} from './common.ts';
import {queryElems} from '../utils/dom.ts';
+import {html, htmlRaw} from '../utils/html.ts';
const {mermaidMaxSourceCharacters} = window.config;
@@ -46,7 +47,7 @@ export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise<void
const iframe = document.createElement('iframe');
iframe.classList.add('markup-content-iframe', 'tw-invisible');
- iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`;
+ iframe.srcdoc = html`<html><head><style>${htmlRaw(iframeCss)}</style></head><body>${htmlRaw(svg)}</body></html>`;
const mermaidBlock = document.createElement('div');
mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden');
diff --git a/web_src/js/modules/fomantic/base.ts b/web_src/js/modules/fomantic/base.ts
index 18f91932a9..1970941e18 100644
--- a/web_src/js/modules/fomantic/base.ts
+++ b/web_src/js/modules/fomantic/base.ts
@@ -1,9 +1,5 @@
import $ from 'jquery';
-let ariaIdCounter = 0;
-
-export function generateAriaId() {
- return `_aria_auto_id_${ariaIdCounter++}`;
-}
+import {generateElemId} from '../../utils/dom.ts';
export function linkLabelAndInput(label: Element, input: Element) {
const labelFor = label.getAttribute('for');
@@ -12,7 +8,7 @@ export function linkLabelAndInput(label: Element, input: Element) {
if (inputId && !labelFor) { // missing "for"
label.setAttribute('for', inputId);
} else if (!inputId && !labelFor) { // missing both "id" and "for"
- const id = generateAriaId();
+ const id = generateElemId('_aria_label_input_');
input.setAttribute('id', id);
label.setAttribute('for', id);
}
diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts
index 0360b8ef95..2af428f24e 100644
--- a/web_src/js/modules/fomantic/dropdown.ts
+++ b/web_src/js/modules/fomantic/dropdown.ts
@@ -1,7 +1,6 @@
import $ from 'jquery';
-import {generateAriaId} from './base.ts';
import type {FomanticInitFunction} from '../../types.ts';
-import {queryElems} from '../../utils/dom.ts';
+import {generateElemId, queryElems} from '../../utils/dom.ts';
const ariaPatchKey = '_giteaAriaPatchDropdown';
const fomanticDropdownFn = $.fn.dropdown;
@@ -9,9 +8,9 @@ const fomanticDropdownFn = $.fn.dropdown;
// use our own `$().dropdown` function to patch Fomantic's dropdown module
export function initAriaDropdownPatch() {
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
- $.fn.dropdown.settings.onAfterFiltered = onAfterFiltered;
$.fn.dropdown = ariaDropdownFn;
$.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem;
+ $.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered;
(ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings;
}
@@ -47,7 +46,7 @@ function ariaDropdownFn(this: any, ...args: Parameters<FomanticInitFunction>) {
// make the item has role=option/menuitem, add an id if there wasn't one yet, make items as non-focusable
// the elements inside the dropdown menu item should not be focusable, the focus should always be on the dropdown primary element.
function updateMenuItem(dropdown: HTMLElement, item: HTMLElement) {
- if (!item.id) item.id = generateAriaId();
+ if (!item.id) item.id = generateElemId('_aria_dropdown_item_');
item.setAttribute('role', (dropdown as any)[ariaPatchKey].listItemRole);
item.setAttribute('tabindex', '-1');
for (const el of item.querySelectorAll('a, input, button')) el.setAttribute('tabindex', '-1');
@@ -59,7 +58,7 @@ function updateMenuItem(dropdown: HTMLElement, item: HTMLElement) {
function updateSelectionLabel(label: HTMLElement) {
// the "label" is like this: "<a|div class="ui label" data-value="1">the-label-name <i|svg class="delete icon"/></a>"
if (!label.id) {
- label.id = generateAriaId();
+ label.id = generateElemId('_aria_dropdown_label_');
}
label.tabIndex = -1;
@@ -71,11 +70,11 @@ function updateSelectionLabel(label: HTMLElement) {
}
}
-function onAfterFiltered(this: any) {
- const $dropdown = $(this);
+function onDropdownAfterFiltered(this: any) {
+ const $dropdown = $(this).closest('.ui.dropdown'); // "this" can be the "ui dropdown" or "<select>"
const hideEmptyDividers = $dropdown.dropdown('setting', 'hideDividers') === 'empty';
const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu');
- if (hideEmptyDividers) hideScopedEmptyDividers(itemsMenu);
+ if (hideEmptyDividers && itemsMenu) hideScopedEmptyDividers(itemsMenu);
}
// delegate the dropdown's template functions and callback functions to add aria attributes.
@@ -127,7 +126,7 @@ function delegateDropdownModule($dropdown: any) {
function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, menu: HTMLElement) {
// prepare static dropdown menu list popup
if (!menu.id) {
- menu.id = generateAriaId();
+ menu.id = generateElemId('_aria_dropdown_menu_');
}
$(menu).find('> .item').each((_, item) => updateMenuItem(dropdown, item));
diff --git a/web_src/js/modules/fomantic/modal.ts b/web_src/js/modules/fomantic/modal.ts
index 6a2c558890..a96c7785e1 100644
--- a/web_src/js/modules/fomantic/modal.ts
+++ b/web_src/js/modules/fomantic/modal.ts
@@ -1,5 +1,7 @@
import $ from 'jquery';
import type {FomanticInitFunction} from '../../types.ts';
+import {queryElems} from '../../utils/dom.ts';
+import {hideToastsFrom} from '../toast.ts';
const fomanticModalFn = $.fn.modal;
@@ -8,6 +10,8 @@ export function initAriaModalPatch() {
if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once');
$.fn.modal = ariaModalFn;
(ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings;
+ $.fn.fomanticExt.onModalBeforeHidden = onModalBeforeHidden;
+ $.fn.modal.settings.onApprove = onModalApproveDefault;
}
// the patched `$.fn.modal` modal function
@@ -27,3 +31,33 @@ function ariaModalFn(this: any, ...args: Parameters<FomanticInitFunction>) {
}
return ret;
}
+
+function onModalBeforeHidden(this: any) {
+ const $modal = $(this);
+ const elModal = $modal[0];
+ hideToastsFrom(elModal.closest('.ui.dimmer') ?? document.body);
+
+ // reset the form after the modal is hidden, after other modal events and handlers (e.g. "onApprove", form submit)
+ setTimeout(() => {
+ queryElems(elModal, 'form', (form: HTMLFormElement) => form.reset());
+ }, 0);
+}
+
+function onModalApproveDefault(this: any) {
+ const $modal = $(this);
+ const selectors = $modal.modal('setting', 'selector');
+ const elModal = $modal[0];
+ const elApprove = elModal.querySelector(selectors.approve);
+ const elForm = elApprove?.closest('form');
+ if (!elForm) return true; // no form, just allow closing the modal
+
+ // "form-fetch-action" can handle network errors gracefully,
+ // so keep the modal dialog to make users can re-submit the form if anything wrong happens.
+ if (elForm.matches('.form-fetch-action')) return false;
+
+ // There is an abuse for the "modal" + "form" combination, the "Approve" button is a traditional form submit button in the form.
+ // Then "approve" and "submit" occur at the same time, the modal will be closed immediately before the form is submitted.
+ // So here we prevent the modal from closing automatically by returning false, add the "is-loading" class to the form element.
+ elForm.classList.add('is-loading');
+ return false;
+}
diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts
index af715f48b9..2a1d998d76 100644
--- a/web_src/js/modules/tippy.ts
+++ b/web_src/js/modules/tippy.ts
@@ -2,6 +2,7 @@ import tippy, {followCursor} from 'tippy.js';
import {isDocumentFragmentOrElementNode} from '../utils/dom.ts';
import {formatDatetime} from '../utils/time.ts';
import type {Content, Instance, Placement, Props} from 'tippy.js';
+import {html} from '../utils/html.ts';
type TippyOpts = {
role?: string,
@@ -9,7 +10,7 @@ type TippyOpts = {
} & Partial<Props>;
const visibleInstances = new Set<Instance>();
-const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`;
+const arrowSvg = html`<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`;
export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
// the callback functions should be destructured from opts,
@@ -40,6 +41,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
}
}
visibleInstances.add(instance);
+ target.setAttribute('aria-controls', instance.popper.id);
return onShow?.(instance);
},
arrow: arrow ?? (theme === 'bare' ? false : arrowSvg),
@@ -180,13 +182,25 @@ export function initGlobalTooltips(): void {
}
export function showTemporaryTooltip(target: Element, content: Content): void {
- // if the target is inside a dropdown, the menu will be hidden soon
- // so display the tooltip on the dropdown instead
- target = target.closest('.ui.dropdown') || target;
- const tippy = target._tippy ?? attachTooltip(target, content);
- tippy.setContent(content);
- if (!tippy.state.isShown) tippy.show();
- tippy.setProps({
+ // if the target is inside a dropdown or tippy popup, the menu will be hidden soon
+ // so display the tooltip on the "aria-controls" element or dropdown instead
+ let refClientRect: DOMRect;
+ const popupTippyId = target.closest(`[data-tippy-root]`)?.id;
+ if (popupTippyId) {
+ // for example, the "Copy Permalink" button in the "File View" page for the selected lines
+ target = document.body;
+ refClientRect = document.querySelector(`[aria-controls="${CSS.escape(popupTippyId)}"]`)?.getBoundingClientRect();
+ refClientRect = refClientRect ?? new DOMRect(0, 0, 0, 0); // fallback to empty rect if not found, tippy doesn't accept null
+ } else {
+ // for example, the "Copy Link" button in the issue header dropdown menu
+ target = target.closest('.ui.dropdown') ?? target;
+ refClientRect = target.getBoundingClientRect();
+ }
+ const tooltipTippy = target._tippy ?? attachTooltip(target, content);
+ tooltipTippy.setContent(content);
+ tooltipTippy.setProps({getReferenceClientRect: () => refClientRect});
+ if (!tooltipTippy.state.isShown) tooltipTippy.show();
+ tooltipTippy.setProps({
onHidden: (tippy) => {
// reset the default tooltip content, if no default, then this temporary tooltip could be destroyed
if (!attachTooltip(target)) {
diff --git a/web_src/js/modules/toast.ts b/web_src/js/modules/toast.ts
index 36e2321743..ed807a4977 100644
--- a/web_src/js/modules/toast.ts
+++ b/web_src/js/modules/toast.ts
@@ -1,6 +1,6 @@
-import {htmlEscape} from 'escape-goat';
+import {htmlEscape} from '../utils/html.ts';
import {svg} from '../svg.ts';
-import {animateOnce, showElem} from '../utils/dom.ts';
+import {animateOnce, queryElems, showElem} from '../utils/dom.ts';
import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown
import type {Intent} from '../types.ts';
import type {SvgName} from '../svg.ts';
@@ -37,17 +37,20 @@ const levels: ToastLevels = {
type ToastOpts = {
useHtmlBody?: boolean,
- preventDuplicates?: boolean,
+ preventDuplicates?: boolean | string,
} & Options;
+type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast };
+
// See https://github.com/apvarun/toastify-js#api for options
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast {
const body = useHtmlBody ? String(message) : htmlEscape(message);
- const key = `${level}-${body}`;
+ const parent = document.querySelector('.ui.dimmer.active') ?? document.body;
+ const duplicateKey = preventDuplicates ? (preventDuplicates === true ? `${level}-${body}` : preventDuplicates) : '';
- // prevent showing duplicate toasts with same level and message, and give a visual feedback for end users
+ // prevent showing duplicate toasts with the same level and message, and give visual feedback for end users
if (preventDuplicates) {
- const toastEl = document.querySelector(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`);
+ const toastEl = parent.querySelector(`:scope > .toastify.on[data-toast-unique-key="${CSS.escape(duplicateKey)}"]`);
if (toastEl) {
const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number');
showElem(toastDupNumEl);
@@ -59,6 +62,7 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
const {icon, background, duration: levelDuration} = levels[level ?? 'info'];
const toast = Toastify({
+ selector: parent,
text: `
<div class='toast-icon'>${svg(icon)}</div>
<div class='toast-body'><span class="toast-duplicate-number tw-hidden">1</span>${body}</div>
@@ -74,7 +78,8 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
toast.showToast();
toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
- toast.toastElement.setAttribute('data-toast-unique-key', key);
+ toast.toastElement.setAttribute('data-toast-unique-key', duplicateKey);
+ (toast.toastElement as ToastifyElement)._giteaToastifyInstance = toast;
return toast;
}
@@ -89,3 +94,15 @@ export function showWarningToast(message: string, opts?: ToastOpts): Toast {
export function showErrorToast(message: string, opts?: ToastOpts): Toast {
return showToast(message, 'error', opts);
}
+
+function hideToastByElement(el: Element): void {
+ (el as ToastifyElement)?._giteaToastifyInstance?.hideToast();
+}
+
+export function hideToastsFrom(parent: Element): void {
+ queryElems(parent, ':scope > .toastify.on', hideToastByElement);
+}
+
+export function hideToastsAll(): void {
+ queryElems(document, '.toastify.on', hideToastByElement);
+}
diff --git a/web_src/js/render/pdf.ts b/web_src/js/render/pdf.ts
deleted file mode 100644
index 283b4ed85c..0000000000
--- a/web_src/js/render/pdf.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import {htmlEscape} from 'escape-goat';
-import {registerGlobalInitFunc} from '../modules/observer.ts';
-
-export async function initPdfViewer() {
- registerGlobalInitFunc('initPdfViewer', async (el: HTMLInputElement) => {
- const pdfobject = await import(/* webpackChunkName: "pdfobject" */'pdfobject');
-
- const src = el.getAttribute('data-src');
- const fallbackText = el.getAttribute('data-fallback-button-text');
- pdfobject.embed(src, el, {
- fallbackLink: htmlEscape`
- <a role="button" class="ui basic button pdf-fallback-button" href="[url]">${fallbackText}</a>
- `,
- });
- el.classList.remove('is-loading');
- });
-}
diff --git a/web_src/js/render/plugin.ts b/web_src/js/render/plugin.ts
new file mode 100644
index 0000000000..a8dd0a7c05
--- /dev/null
+++ b/web_src/js/render/plugin.ts
@@ -0,0 +1,10 @@
+export type FileRenderPlugin = {
+ // unique plugin name
+ name: string;
+
+ // test if plugin can handle a specified file
+ canHandle: (filename: string, mimeType: string) => boolean;
+
+ // render file content
+ render: (container: HTMLElement, fileUrl: string, options?: any) => Promise<void>;
+}
diff --git a/web_src/js/render/plugins/3d-viewer.ts b/web_src/js/render/plugins/3d-viewer.ts
new file mode 100644
index 0000000000..2a0929359d
--- /dev/null
+++ b/web_src/js/render/plugins/3d-viewer.ts
@@ -0,0 +1,60 @@
+import type {FileRenderPlugin} from '../plugin.ts';
+import {extname} from '../../utils.ts';
+
+// support common 3D model file formats, use online-3d-viewer library for rendering
+
+// eslint-disable-next-line multiline-comment-style
+/* a simple text STL file example:
+solid SimpleTriangle
+ facet normal 0 0 1
+ outer loop
+ vertex 0 0 0
+ vertex 1 0 0
+ vertex 0 1 0
+ endloop
+ endfacet
+endsolid SimpleTriangle
+*/
+
+export function newRenderPlugin3DViewer(): FileRenderPlugin {
+ // Some extensions are text-based formats:
+ // .3mf .amf .brep: XML
+ // .fbx: XML or BINARY
+ // .dae .gltf: JSON
+ // .ifc, .igs, .iges, .stp, .step are: TEXT
+ // .stl .ply: TEXT or BINARY
+ // .obj .off .wrl: TEXT
+ // So we need to be able to render when the file is recognized as plaintext file by backend.
+ //
+ // It needs more logic to make it overall right (render a text 3D model automatically):
+ // we need to distinguish the ambiguous filename extensions.
+ // For example: "*.obj, *.off, *.step" might be or not be a 3D model file.
+ // So when it is a text file, we can't assume that "we only render it by 3D plugin",
+ // otherwise the end users would be impossible to view its real content when the file is not a 3D model.
+ const SUPPORTED_EXTENSIONS = [
+ '.3dm', '.3ds', '.3mf', '.amf', '.bim', '.brep',
+ '.dae', '.fbx', '.fcstd', '.glb', '.gltf',
+ '.ifc', '.igs', '.iges', '.stp', '.step',
+ '.stl', '.obj', '.off', '.ply', '.wrl',
+ ];
+
+ return {
+ name: '3d-model-viewer',
+
+ canHandle(filename: string, _mimeType: string): boolean {
+ const ext = extname(filename).toLowerCase();
+ return SUPPORTED_EXTENSIONS.includes(ext);
+ },
+
+ async render(container: HTMLElement, fileUrl: string): Promise<void> {
+ // TODO: height and/or max-height?
+ const OV = await import(/* webpackChunkName: "online-3d-viewer" */'online-3d-viewer');
+ const viewer = new OV.EmbeddedViewer(container, {
+ backgroundColor: new OV.RGBAColor(59, 68, 76, 0),
+ defaultColor: new OV.RGBColor(65, 131, 196),
+ edgeSettings: new OV.EdgeSettings(false, new OV.RGBColor(0, 0, 0), 1),
+ });
+ viewer.LoadModelFromUrlList([fileUrl]);
+ },
+ };
+}
diff --git a/web_src/js/render/plugins/pdf-viewer.ts b/web_src/js/render/plugins/pdf-viewer.ts
new file mode 100644
index 0000000000..40623be055
--- /dev/null
+++ b/web_src/js/render/plugins/pdf-viewer.ts
@@ -0,0 +1,20 @@
+import type {FileRenderPlugin} from '../plugin.ts';
+
+export function newRenderPluginPdfViewer(): FileRenderPlugin {
+ return {
+ name: 'pdf-viewer',
+
+ canHandle(filename: string, _mimeType: string): boolean {
+ return filename.toLowerCase().endsWith('.pdf');
+ },
+
+ async render(container: HTMLElement, fileUrl: string): Promise<void> {
+ const PDFObject = await import(/* webpackChunkName: "pdfobject" */'pdfobject');
+ // TODO: the PDFObject library does not support dynamic height adjustment,
+ container.style.height = `${window.innerHeight - 100}px`;
+ if (!PDFObject.default.embed(fileUrl, container)) {
+ throw new Error('Unable to render the PDF file');
+ }
+ },
+ };
+}
diff --git a/web_src/js/svg.ts b/web_src/js/svg.ts
index 7b377e1ab4..50c9536f37 100644
--- a/web_src/js/svg.ts
+++ b/web_src/js/svg.ts
@@ -1,5 +1,6 @@
import {defineComponent, h, type PropType} from 'vue';
import {parseDom, serializeXml} from './utils.ts';
+import {html, htmlRaw} from './utils/html.ts';
import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg';
import giteaDoubleChevronRight from '../../public/assets/img/svg/gitea-double-chevron-right.svg';
import giteaEmptyCheckbox from '../../public/assets/img/svg/gitea-empty-checkbox.svg';
@@ -220,7 +221,7 @@ export const SvgIcon = defineComponent({
const classes = Array.from(svgOuter.classList);
if (this.symbolId) {
classes.push('tw-hidden', 'svg-symbol-container');
- svgInnerHtml = `<symbol id="${this.symbolId}" viewBox="${attrs['^viewBox']}">${svgInnerHtml}</symbol>`;
+ svgInnerHtml = html`<symbol id="${this.symbolId}" viewBox="${attrs['^viewBox']}">${htmlRaw(svgInnerHtml)}</symbol>`;
}
// create VNode
return h('svg', {
diff --git a/web_src/js/utils/dom.test.ts b/web_src/js/utils/dom.test.ts
index 6a3af91556..057ea9808c 100644
--- a/web_src/js/utils/dom.test.ts
+++ b/web_src/js/utils/dom.test.ts
@@ -25,10 +25,14 @@ test('createElementFromAttrs', () => {
});
test('querySingleVisibleElem', () => {
- let el = createElementFromHTML('<div><span>foo</span></div>');
+ let el = createElementFromHTML('<div></div>');
+ expect(querySingleVisibleElem(el, 'span')).toBeNull();
+ el = createElementFromHTML('<div><span>foo</span></div>');
expect(querySingleVisibleElem(el, 'span').textContent).toEqual('foo');
el = createElementFromHTML('<div><span style="display: none;">foo</span><span>bar</span></div>');
expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar');
+ el = createElementFromHTML('<div><span class="some-class tw-hidden">foo</span><span>bar</span></div>');
+ expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar');
el = createElementFromHTML('<div><span>foo</span><span>bar</span></div>');
expect(() => querySingleVisibleElem(el, 'span')).toThrowError('Expected exactly one visible element');
});
diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts
index 4386d38632..3b14b9bcea 100644
--- a/web_src/js/utils/dom.ts
+++ b/web_src/js/utils/dom.ts
@@ -9,24 +9,24 @@ type ElementsCallback<T extends Element> = (el: T) => Promisable<any>;
type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>;
export type DOMEvent<E extends Event, T extends Element = HTMLElement> = E & { target: Partial<T>; };
-function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) {
+function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]): ArrayLikeIterable<Element> {
if (typeof el === 'string' || el instanceof String) {
el = document.querySelectorAll(el as string);
}
if (el instanceof Node) {
func(el, ...args);
+ return [el];
} else if (el.length !== undefined) {
// this works for: NodeList, HTMLCollection, Array, jQuery
- for (const e of (el as ArrayLikeIterable<Element>)) {
- func(e, ...args);
- }
- } else {
- throw new Error('invalid argument to be shown/hidden');
+ const elems = el as ArrayLikeIterable<Element>;
+ for (const elem of elems) func(elem, ...args);
+ return elems;
}
+ throw new Error('invalid argument to be shown/hidden');
}
-export function toggleClass(el: ElementArg, className: string, force?: boolean) {
- elementsCall(el, (e: Element) => {
+export function toggleClass(el: ElementArg, className: string, force?: boolean): ArrayLikeIterable<Element> {
+ return elementsCall(el, (e: Element) => {
if (force === true) {
e.classList.add(className);
} else if (force === false) {
@@ -43,23 +43,16 @@ export function toggleClass(el: ElementArg, className: string, force?: boolean)
* @param el ElementArg
* @param force force=true to show or force=false to hide, undefined to toggle
*/
-export function toggleElem(el: ElementArg, force?: boolean) {
- toggleClass(el, 'tw-hidden', force === undefined ? force : !force);
-}
-
-export function showElem(el: ElementArg) {
- toggleElem(el, true);
+export function toggleElem(el: ElementArg, force?: boolean): ArrayLikeIterable<Element> {
+ return toggleClass(el, 'tw-hidden', force === undefined ? force : !force);
}
-export function hideElem(el: ElementArg) {
- toggleElem(el, false);
+export function showElem(el: ElementArg): ArrayLikeIterable<Element> {
+ return toggleElem(el, true);
}
-export function isElemHidden(el: ElementArg) {
- const res: boolean[] = [];
- elementsCall(el, (e) => res.push(e.classList.contains('tw-hidden')));
- if (res.length > 1) throw new Error(`isElemHidden doesn't work for multiple elements`);
- return res[0];
+export function hideElem(el: ElementArg): ArrayLikeIterable<Element> {
+ return toggleElem(el, false);
}
function applyElemsCallback<T extends Element>(elems: ArrayLikeIterable<T>, fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
@@ -168,6 +161,7 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom =
function resizeToFit() {
if (isUserResized) return;
if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return;
+ const previousMargin = textarea.style.marginBottom;
try {
const {top, bottom} = overflowOffset();
@@ -183,6 +177,9 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom =
const curHeight = parseFloat(computedStyle.height);
const maxHeight = curHeight + bottom - adjustedViewportMarginBottom;
+ // In Firefox, setting auto height momentarily may cause the page to scroll up
+ // unexpectedly, prevent this by setting a temporary margin.
+ textarea.style.marginBottom = `${textarea.clientHeight}px`;
textarea.style.height = 'auto';
let newHeight = textarea.scrollHeight + borderAddOn;
@@ -203,6 +200,12 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom =
textarea.style.height = `${newHeight}px`;
lastStyleHeight = textarea.style.height;
} finally {
+ // restore previous margin
+ if (previousMargin) {
+ textarea.style.marginBottom = previousMargin;
+ } else {
+ textarea.style.removeProperty('margin-bottom');
+ }
// ensure that the textarea is fully scrolled to the end, when the cursor
// is at the end during an input event
if (textarea.selectionStart === textarea.selectionEnd &&
@@ -275,28 +278,24 @@ export function initSubmitEventPolyfill() {
document.body.addEventListener('focus', submitEventPolyfillListener);
}
-/**
- * Check if an element is visible, equivalent to jQuery's `:visible` pseudo.
- * Note: This function doesn't account for all possible visibility scenarios.
- */
-export function isElemVisible(element: HTMLElement): boolean {
- if (!element) return false;
- // checking element.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout
- return Boolean((element.offsetWidth || element.offsetHeight || element.getClientRects().length) && element.style.display !== 'none');
+export function isElemVisible(el: HTMLElement): boolean {
+ // Check if an element is visible, equivalent to jQuery's `:visible` pseudo.
+ // This function DOESN'T account for all possible visibility scenarios, its behavior is covered by the tests of "querySingleVisibleElem"
+ if (!el) return false;
+ // checking el.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout
+ return !el.classList.contains('tw-hidden') && Boolean((el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none');
}
// replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this
export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: string) {
const before = textarea.value.slice(0, textarea.selectionStart ?? undefined);
const after = textarea.value.slice(textarea.selectionEnd ?? undefined);
- let success = true;
+ let success = false;
textarea.contentEditable = 'true';
try {
success = document.execCommand('insertText', false, text); // eslint-disable-line @typescript-eslint/no-deprecated
- } catch {
- success = false;
- }
+ } catch {} // ignore the error if execCommand is not supported or failed
textarea.contentEditable = 'false';
if (success && !textarea.value.slice(0, textarea.selectionStart ?? undefined).endsWith(text)) {
@@ -309,10 +308,10 @@ export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: st
}
}
-// Warning: Do not enter any unsanitized variables here
export function createElementFromHTML<T extends HTMLElement>(htmlString: string): T {
htmlString = htmlString.trim();
- // some tags like "tr" are special, it must use a correct parent container to create
+ // There is no way to create some elements without a proper parent, jQuery's approach: https://github.com/jquery/jquery/blob/main/src/manipulation/wrapMap.js
+ // eslint-disable-next-line github/unescaped-html-literal
if (htmlString.startsWith('<tr')) {
const container = document.createElement('table');
container.innerHTML = htmlString;
@@ -362,9 +361,19 @@ export function addDelegatedEventListener<T extends HTMLElement, E extends Event
const elem = (e.target as HTMLElement).closest(selector);
// It strictly checks "parent contains the target elem" to avoid side effects of selector running on outside the parent.
// Keep in mind that the elem could have been removed from parent by other event handlers before this event handler is called.
- // For example: tippy popup item, the tippy popup could be hidden and removed from DOM before this.
- // It is caller's responsibility make sure the elem is still in parent's DOM when this event handler is called.
+ // For example, tippy popup item, the tippy popup could be hidden and removed from DOM before this.
+ // It is the caller's responsibility to make sure the elem is still in parent's DOM when this event handler is called.
if (!elem || (parent !== document && !parent.contains(elem))) return;
listener(elem as T, e as E);
}, options);
}
+
+// Returns whether a click event is a left-click without any modifiers held
+export function isPlainClick(e: MouseEvent) {
+ return e.button === 0 && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey;
+}
+
+let elemIdCounter = 0;
+export function generateElemId(prefix: string = ''): string {
+ return `${prefix}${elemIdCounter++}`;
+}
diff --git a/web_src/js/utils/html.test.ts b/web_src/js/utils/html.test.ts
new file mode 100644
index 0000000000..3028b7bb0a
--- /dev/null
+++ b/web_src/js/utils/html.test.ts
@@ -0,0 +1,8 @@
+import {html, htmlEscape, htmlRaw} from './html.ts';
+
+test('html', async () => {
+ expect(html`<a>${'<>&\'"'}</a>`).toBe(`<a>&lt;&gt;&amp;&#39;&quot;</a>`);
+ expect(html`<a>${htmlRaw('<img>')}</a>`).toBe(`<a><img></a>`);
+ expect(html`<a>${htmlRaw`<img ${'&'}>`}</a>`).toBe(`<a><img &amp;></a>`);
+ expect(htmlEscape(`<a></a>`)).toBe(`&lt;a&gt;&lt;/a&gt;`);
+});
diff --git a/web_src/js/utils/html.ts b/web_src/js/utils/html.ts
new file mode 100644
index 0000000000..1252032ee0
--- /dev/null
+++ b/web_src/js/utils/html.ts
@@ -0,0 +1,32 @@
+export function htmlEscape(s: string, ...args: Array<any>): string {
+ if (args.length !== 0) throw new Error('use html or htmlRaw instead of htmlEscape'); // check legacy usages
+ return s.replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+}
+
+class rawObject {
+ private readonly value: string;
+ constructor(v: string) { this.value = v }
+ toString(): string { return this.value }
+}
+
+export function html(tmpl: TemplateStringsArray, ...parts: Array<any>): string {
+ let output = tmpl[0];
+ for (let i = 0; i < parts.length; i++) {
+ const value = parts[i];
+ const valueEscaped = (value instanceof rawObject) ? value.toString() : htmlEscape(String(value));
+ output = output + valueEscaped + tmpl[i + 1];
+ }
+ return output;
+}
+
+export function htmlRaw(s: string|TemplateStringsArray, ...tmplParts: Array<any>): rawObject {
+ if (typeof s === 'string') {
+ if (tmplParts.length !== 0) throw new Error("either htmlRaw('str') or htmlRaw`tmpl`");
+ return new rawObject(s);
+ }
+ return new rawObject(html(s, ...tmplParts));
+}