diff --git a/.eslintrc.yaml b/.eslintrc.yaml index ea85ab12981f1..8b9123c9884e7 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -22,13 +22,14 @@ plugins: - eslint-plugin-wc env: - es2022: true + es2024: true node: true -globals: - __webpack_public_path__: true - overrides: + - files: ["web_src/**/*"] + globals: + __webpack_public_path__: true + process: false # https://github.com/webpack/webpack/issues/15833 - files: ["web_src/**/*", "docs/**/*"] env: browser: true @@ -155,7 +156,7 @@ rules: import/no-restricted-paths: [0] import/no-self-import: [2] import/no-unassigned-import: [0] - import/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}] + import/no-unresolved: [2, {commonjs: true, ignore: [\?.+$, ^vitest/]}] import/no-unused-modules: [2, {unusedExports: true}] import/no-useless-path-segments: [2, {commonjs: true}] import/no-webpack-loader-syntax: [2] @@ -692,7 +693,7 @@ rules: unicorn/prefer-dom-node-remove: [2] unicorn/prefer-dom-node-text-content: [2] unicorn/prefer-event-target: [2] - unicorn/prefer-export-from: [2, {ignoreUsedVariables: true}] + unicorn/prefer-export-from: [0] unicorn/prefer-includes: [2] unicorn/prefer-json-parse-buffer: [0] unicorn/prefer-keyboard-event-key: [2] diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml index 37f57c8f23dfc..b481e0c2dedf4 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yaml +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -1,6 +1,6 @@ name: Feature Request description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here! -labels: ["kind/feature", "kind/proposal"] +labels: ["kind/proposal"] body: - type: markdown attributes: diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index 0d43e191a3cb7..0c382567cca7a 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -15,6 +15,8 @@ on: value: ${{ jobs.detect.outputs.templates }} docker: value: ${{ jobs.detect.outputs.docker }} + swagger: + value: ${{ jobs.detect.outputs.swagger }} jobs: detect: @@ -27,6 +29,7 @@ jobs: actions: ${{ steps.changes.outputs.actions }} templates: ${{ steps.changes.outputs.templates }} docker: ${{ steps.changes.outputs.docker }} + swagger: ${{ steps.changes.outputs.swagger }} steps: - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 @@ -36,6 +39,7 @@ jobs: backend: - "**/*.go" - "templates/**/*.tmpl" + - "assets/emoji.json" - "go.mod" - "go.sum" - "Makefile" @@ -43,6 +47,7 @@ jobs: frontend: - "**/*.js" - "web_src/**" + - "assets/emoji.json" - "package.json" - "package-lock.json" - "Makefile" @@ -63,3 +68,6 @@ jobs: - "Dockerfile.rootless" - "docker/**" - "Makefile" + + swagger: + - "templates/swagger/v1_json.tmpl" diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 71e4fe02dc709..c8bef283a9343 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -39,6 +39,18 @@ jobs: - run: make deps-py - run: make lint-templates + lint-swagger: + if: needs.files-changed.outputs.swagger == 'true' + needs: files-changed + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: make deps-frontend + - run: make lint-swagger + lint-go-windows: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml index 8aeb706182513..a96589f1e8f69 100644 --- a/.stylelintrc.yaml +++ b/.stylelintrc.yaml @@ -50,7 +50,7 @@ rules: declaration-no-important: null declaration-property-max-values: null declaration-property-unit-allowed-list: null - declaration-property-unit-disallowed-list: null + declaration-property-unit-disallowed-list: {line-height: [em]} declaration-property-value-allowed-list: null declaration-property-value-disallowed-list: null declaration-property-value-no-unknown: true diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e5c9368a122..f3d3aa3a8beab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,54 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.19.4](https://github.com/go-gitea/gitea/releases/tag/v1.19.4) - 2023-07-04 + +* SECURITY + * Fix open redirect check for more cases (#25143) (#25155) +* API + * Return `404` in the API if the requested webhooks were not found (#24823) (#24830) + * Fix `organization` field being `null` in `GET /api/v1/teams/{id}` (#24694) (#24696) +* ENHANCEMENTS + * Set `--font-weight-bold` to 600 (#24840) + * Make mailer SMTP check have timed context (#24751) (#24759) + * Do not select line numbers when selecting text from the action run logs (#24594) (#24596) +* BUGFIXES + * Fix bug when change user name (#25637) (#25645) + * Fix task list checkbox toggle to work with YAML front matter (#25184) (#25236) + * Hide limited users if viewed by anonymous ghost (#25214) (#25224) + * Add `WithPullRequest` for `actionsNotifier` (#25144) (#25196) + * Fix parallelly generating index failure with Mysql (#24567) (#25081) + * GitLab migration: Sanitize response for reaction list (#25054) (#25059) + * Fix users cannot visit issue attachment bug (#25019) (#25027) + * Fix missing reference prefix of commits when sync mirror repository (#24994) + * Only validate changed columns when update user (#24867) (#24903) + * Make DeleteIssue use correct context (#24885) + * Fix topics deleted via API not being deleted in org page (#24825) (#24829) + * Fix Actions being enabled accidentally (#24802) (#24810) + * Fix missed table name on iterate lfs meta objects (#24768) (#24774) + * Fix safari cookie session bug (#24772) + * Respect original content when creating secrets (#24745) (#24746) + * Fix Pull Mirror out-of-sync bugs (#24732) (#24733) + * Fix run list broken when trigger user deleted (#24706) (#24709) + * Fix issues list page multiple selection update milestones (#24660) (#24663) + * Fix: release page for empty or non-existing target (#24659) + * Fix close org projects (#24588) (#24591) + * Refresh the refernce of the closed PR when reopening (#24231) (#24587) + * Fix the permission of team's `Actions` unit issue (#24536) (#24545) + * Bump go.etcd.io/bbolt and blevesearch deps (#23062) (#24519) + * Fix new wiki page mirror (#24518) + * Match unqualified references when syncing pulls as well (#23070) +* DOCS + * Change branch name from master to main in some documents' links (#25126) (#25139) + * Remove unnecessary content on docs (#24976) (#25001) + * Unify doc links to use paths relative to doc folder (#24979) (#25000) + * Fix docs documenting invalid `@every` for `OLDER_THAN` cron settings (#24695) (#24698) +* MISC + * Merge different languages for language stats (#24900) (#24921) + * Hiding Secrets options when Actions feature is disabled (#24792) + * Improve decryption failure message (#24573) (#24575) + * Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572) + ## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/1.19.3) - 2023-05-03 * SECURITY diff --git a/MAINTAINERS b/MAINTAINERS index c3ac7b945f077..ac7fb6d05a58c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -52,3 +52,5 @@ Xinyi Gong (@HesterG) wxiaoguang (@wxiaoguang) Gary Moon (@garymoon) Philip Peterson (@philip-peterson) +Denys Konovalov (@denyskon) +Punit Inani (@puni9869) diff --git a/Makefile b/Makefile index e9c908de4aead..0c4b42a8c5435 100644 --- a/Makefile +++ b/Makefile @@ -226,6 +226,8 @@ help: @echo " - test-frontend test frontend files" @echo " - test-backend test backend files" @echo " - test-e2e[\#TestSpecificName] test end to end using playwright" + @echo " - update-js update js dependencies" + @echo " - update-py update py dependencies" @echo " - webpack build webpack files" @echo " - svg build svg files" @echo " - fomantic build fomantic files" @@ -358,10 +360,10 @@ lint: lint-frontend lint-backend lint-fix: lint-frontend-fix lint-backend-fix .PHONY: lint-frontend -lint-frontend: lint-js lint-css lint-md lint-swagger +lint-frontend: lint-js lint-css .PHONY: lint-frontend-fix -lint-frontend-fix: lint-js-fix lint-css-fix lint-md lint-swagger +lint-frontend-fix: lint-js-fix lint-css-fix .PHONY: lint-backend lint-backend: lint-go lint-go-vet lint-editorconfig @@ -924,13 +926,20 @@ node_modules: package-lock.json poetry install @touch .venv -.PHONY: npm-update -npm-update: node-check | node_modules - npx updates -cu +.PHONY: update-js +update-js: node-check | node_modules + npx updates -u -f package.json rm -rf node_modules package-lock.json npm install --package-lock @touch node_modules +.PHONY: update-py +update-py: node-check | node_modules + npx updates -u -f pyproject.toml + rm -rf .venv poetry.lock + poetry install + @touch .venv + .PHONY: fomantic fomantic: rm -rf $(FOMANTIC_WORK_DIR)/build diff --git a/assets/emoji.json b/assets/emoji.json index 5a1ff98a46afc..28244caa651c8 100644 --- a/assets/emoji.json +++ b/assets/emoji.json @@ -1 +1 @@ -[{"emoji":"👍","aliases":["+1","thumbsup"]},{"emoji":"👎","aliases":["-1","thumbsdown"]},{"emoji":"💯","aliases":["100"]},{"emoji":"🔢","aliases":["1234"]},{"emoji":"🥇","aliases":["1st_place_medal"]},{"emoji":"🥈","aliases":["2nd_place_medal"]},{"emoji":"🥉","aliases":["3rd_place_medal"]},{"emoji":"🎱","aliases":["8ball"]},{"emoji":"🅰️","aliases":["a"]},{"emoji":"🆎","aliases":["ab"]},{"emoji":"🧮","aliases":["abacus"]},{"emoji":"🔤","aliases":["abc"]},{"emoji":"🔡","aliases":["abcd"]},{"emoji":"🉑","aliases":["accept"]},{"emoji":"🪗","aliases":["accordion"]},{"emoji":"🩹","aliases":["adhesive_bandage"]},{"emoji":"🧑","aliases":["adult"]},{"emoji":"🚡","aliases":["aerial_tramway"]},{"emoji":"🇦🇫","aliases":["afghanistan"]},{"emoji":"✈️","aliases":["airplane"]},{"emoji":"🇦🇽","aliases":["aland_islands"]},{"emoji":"⏰","aliases":["alarm_clock"]},{"emoji":"🇦🇱","aliases":["albania"]},{"emoji":"⚗️","aliases":["alembic"]},{"emoji":"🇩🇿","aliases":["algeria"]},{"emoji":"👽","aliases":["alien"]},{"emoji":"🚑","aliases":["ambulance"]},{"emoji":"🇦🇸","aliases":["american_samoa"]},{"emoji":"🏺","aliases":["amphora"]},{"emoji":"🫀","aliases":["anatomical_heart"]},{"emoji":"⚓","aliases":["anchor"]},{"emoji":"🇦🇩","aliases":["andorra"]},{"emoji":"👼","aliases":["angel"]},{"emoji":"💢","aliases":["anger"]},{"emoji":"🇦🇴","aliases":["angola"]},{"emoji":"😠","aliases":["angry"]},{"emoji":"🇦🇮","aliases":["anguilla"]},{"emoji":"😧","aliases":["anguished"]},{"emoji":"🐜","aliases":["ant"]},{"emoji":"🇦🇶","aliases":["antarctica"]},{"emoji":"🇦🇬","aliases":["antigua_barbuda"]},{"emoji":"🍎","aliases":["apple"]},{"emoji":"♒","aliases":["aquarius"]},{"emoji":"🇦🇷","aliases":["argentina"]},{"emoji":"♈","aliases":["aries"]},{"emoji":"🇦🇲","aliases":["armenia"]},{"emoji":"◀️","aliases":["arrow_backward"]},{"emoji":"⏬","aliases":["arrow_double_down"]},{"emoji":"⏫","aliases":["arrow_double_up"]},{"emoji":"⬇️","aliases":["arrow_down"]},{"emoji":"🔽","aliases":["arrow_down_small"]},{"emoji":"▶️","aliases":["arrow_forward"]},{"emoji":"⤵️","aliases":["arrow_heading_down"]},{"emoji":"⤴️","aliases":["arrow_heading_up"]},{"emoji":"⬅️","aliases":["arrow_left"]},{"emoji":"↙️","aliases":["arrow_lower_left"]},{"emoji":"↘️","aliases":["arrow_lower_right"]},{"emoji":"➡️","aliases":["arrow_right"]},{"emoji":"↪️","aliases":["arrow_right_hook"]},{"emoji":"⬆️","aliases":["arrow_up"]},{"emoji":"↕️","aliases":["arrow_up_down"]},{"emoji":"🔼","aliases":["arrow_up_small"]},{"emoji":"↖️","aliases":["arrow_upper_left"]},{"emoji":"↗️","aliases":["arrow_upper_right"]},{"emoji":"🔃","aliases":["arrows_clockwise"]},{"emoji":"🔄","aliases":["arrows_counterclockwise"]},{"emoji":"🎨","aliases":["art"]},{"emoji":"🚛","aliases":["articulated_lorry"]},{"emoji":"🛰️","aliases":["artificial_satellite"]},{"emoji":"🧑‍🎨","aliases":["artist"]},{"emoji":"🇦🇼","aliases":["aruba"]},{"emoji":"🇦🇨","aliases":["ascension_island"]},{"emoji":"*️⃣","aliases":["asterisk"]},{"emoji":"😲","aliases":["astonished"]},{"emoji":"🧑‍🚀","aliases":["astronaut"]},{"emoji":"👟","aliases":["athletic_shoe"]},{"emoji":"🏧","aliases":["atm"]},{"emoji":"⚛️","aliases":["atom_symbol"]},{"emoji":"🇦🇺","aliases":["australia"]},{"emoji":"🇦🇹","aliases":["austria"]},{"emoji":"🛺","aliases":["auto_rickshaw"]},{"emoji":"🥑","aliases":["avocado"]},{"emoji":"🪓","aliases":["axe"]},{"emoji":"🇦🇿","aliases":["azerbaijan"]},{"emoji":"🅱️","aliases":["b"]},{"emoji":"👶","aliases":["baby"]},{"emoji":"🍼","aliases":["baby_bottle"]},{"emoji":"🐤","aliases":["baby_chick"]},{"emoji":"🚼","aliases":["baby_symbol"]},{"emoji":"🔙","aliases":["back"]},{"emoji":"🥓","aliases":["bacon"]},{"emoji":"🦡","aliases":["badger"]},{"emoji":"🏸","aliases":["badminton"]},{"emoji":"🥯","aliases":["bagel"]},{"emoji":"🛄","aliases":["baggage_claim"]},{"emoji":"🥖","aliases":["baguette_bread"]},{"emoji":"🇧🇸","aliases":["bahamas"]},{"emoji":"🇧🇭","aliases":["bahrain"]},{"emoji":"⚖️","aliases":["balance_scale"]},{"emoji":"👨‍🦲","aliases":["bald_man"]},{"emoji":"👩‍🦲","aliases":["bald_woman"]},{"emoji":"🩰","aliases":["ballet_shoes"]},{"emoji":"🎈","aliases":["balloon"]},{"emoji":"🗳️","aliases":["ballot_box"]},{"emoji":"☑️","aliases":["ballot_box_with_check"]},{"emoji":"🎍","aliases":["bamboo"]},{"emoji":"🍌","aliases":["banana"]},{"emoji":"‼️","aliases":["bangbang"]},{"emoji":"🇧🇩","aliases":["bangladesh"]},{"emoji":"🪕","aliases":["banjo"]},{"emoji":"🏦","aliases":["bank"]},{"emoji":"📊","aliases":["bar_chart"]},{"emoji":"🇧🇧","aliases":["barbados"]},{"emoji":"💈","aliases":["barber"]},{"emoji":"⚾","aliases":["baseball"]},{"emoji":"🧺","aliases":["basket"]},{"emoji":"🏀","aliases":["basketball"]},{"emoji":"🦇","aliases":["bat"]},{"emoji":"🛀","aliases":["bath"]},{"emoji":"🛁","aliases":["bathtub"]},{"emoji":"🔋","aliases":["battery"]},{"emoji":"🏖️","aliases":["beach_umbrella"]},{"emoji":"🫘","aliases":["beans"]},{"emoji":"🐻","aliases":["bear"]},{"emoji":"🧔","aliases":["bearded_person"]},{"emoji":"🦫","aliases":["beaver"]},{"emoji":"🛏️","aliases":["bed"]},{"emoji":"🐝","aliases":["bee","honeybee"]},{"emoji":"🍺","aliases":["beer"]},{"emoji":"🍻","aliases":["beers"]},{"emoji":"🪲","aliases":["beetle"]},{"emoji":"🔰","aliases":["beginner"]},{"emoji":"🇧🇾","aliases":["belarus"]},{"emoji":"🇧🇪","aliases":["belgium"]},{"emoji":"🇧🇿","aliases":["belize"]},{"emoji":"🔔","aliases":["bell"]},{"emoji":"🫑","aliases":["bell_pepper"]},{"emoji":"🛎️","aliases":["bellhop_bell"]},{"emoji":"🇧🇯","aliases":["benin"]},{"emoji":"🍱","aliases":["bento"]},{"emoji":"🇧🇲","aliases":["bermuda"]},{"emoji":"🧃","aliases":["beverage_box"]},{"emoji":"🇧🇹","aliases":["bhutan"]},{"emoji":"🚴","aliases":["bicyclist"]},{"emoji":"🚲","aliases":["bike"]},{"emoji":"🚴‍♂️","aliases":["biking_man"]},{"emoji":"🚴‍♀️","aliases":["biking_woman"]},{"emoji":"👙","aliases":["bikini"]},{"emoji":"🧢","aliases":["billed_cap"]},{"emoji":"☣️","aliases":["biohazard"]},{"emoji":"🐦","aliases":["bird"]},{"emoji":"🎂","aliases":["birthday"]},{"emoji":"🦬","aliases":["bison"]},{"emoji":"🫦","aliases":["biting_lip"]},{"emoji":"🐈‍⬛","aliases":["black_cat"]},{"emoji":"⚫","aliases":["black_circle"]},{"emoji":"🏴","aliases":["black_flag"]},{"emoji":"🖤","aliases":["black_heart"]},{"emoji":"🃏","aliases":["black_joker"]},{"emoji":"⬛","aliases":["black_large_square"]},{"emoji":"◾","aliases":["black_medium_small_square"]},{"emoji":"◼️","aliases":["black_medium_square"]},{"emoji":"✒️","aliases":["black_nib"]},{"emoji":"▪️","aliases":["black_small_square"]},{"emoji":"🔲","aliases":["black_square_button"]},{"emoji":"👱‍♂️","aliases":["blond_haired_man"]},{"emoji":"👱","aliases":["blond_haired_person"]},{"emoji":"👱‍♀️","aliases":["blond_haired_woman","blonde_woman"]},{"emoji":"🌼","aliases":["blossom"]},{"emoji":"🐡","aliases":["blowfish"]},{"emoji":"📘","aliases":["blue_book"]},{"emoji":"🚙","aliases":["blue_car"]},{"emoji":"💙","aliases":["blue_heart"]},{"emoji":"🟦","aliases":["blue_square"]},{"emoji":"🫐","aliases":["blueberries"]},{"emoji":"😊","aliases":["blush"]},{"emoji":"🐗","aliases":["boar"]},{"emoji":"⛵","aliases":["boat","sailboat"]},{"emoji":"🇧🇴","aliases":["bolivia"]},{"emoji":"💣","aliases":["bomb"]},{"emoji":"🦴","aliases":["bone"]},{"emoji":"📖","aliases":["book","open_book"]},{"emoji":"🔖","aliases":["bookmark"]},{"emoji":"📑","aliases":["bookmark_tabs"]},{"emoji":"📚","aliases":["books"]},{"emoji":"💥","aliases":["boom","collision"]},{"emoji":"🪃","aliases":["boomerang"]},{"emoji":"👢","aliases":["boot"]},{"emoji":"🇧🇦","aliases":["bosnia_herzegovina"]},{"emoji":"🇧🇼","aliases":["botswana"]},{"emoji":"⛹️‍♂️","aliases":["bouncing_ball_man","basketball_man"]},{"emoji":"⛹️","aliases":["bouncing_ball_person"]},{"emoji":"⛹️‍♀️","aliases":["bouncing_ball_woman","basketball_woman"]},{"emoji":"💐","aliases":["bouquet"]},{"emoji":"🇧🇻","aliases":["bouvet_island"]},{"emoji":"🙇","aliases":["bow"]},{"emoji":"🏹","aliases":["bow_and_arrow"]},{"emoji":"🙇‍♂️","aliases":["bowing_man"]},{"emoji":"🙇‍♀️","aliases":["bowing_woman"]},{"emoji":"🥣","aliases":["bowl_with_spoon"]},{"emoji":"🎳","aliases":["bowling"]},{"emoji":"🥊","aliases":["boxing_glove"]},{"emoji":"👦","aliases":["boy"]},{"emoji":"🧠","aliases":["brain"]},{"emoji":"🇧🇷","aliases":["brazil"]},{"emoji":"🍞","aliases":["bread"]},{"emoji":"🤱","aliases":["breast_feeding"]},{"emoji":"🧱","aliases":["bricks"]},{"emoji":"🌉","aliases":["bridge_at_night"]},{"emoji":"💼","aliases":["briefcase"]},{"emoji":"🇮🇴","aliases":["british_indian_ocean_territory"]},{"emoji":"🇻🇬","aliases":["british_virgin_islands"]},{"emoji":"🥦","aliases":["broccoli"]},{"emoji":"💔","aliases":["broken_heart"]},{"emoji":"🧹","aliases":["broom"]},{"emoji":"🟤","aliases":["brown_circle"]},{"emoji":"🤎","aliases":["brown_heart"]},{"emoji":"🟫","aliases":["brown_square"]},{"emoji":"🇧🇳","aliases":["brunei"]},{"emoji":"🧋","aliases":["bubble_tea"]},{"emoji":"🫧","aliases":["bubbles"]},{"emoji":"🪣","aliases":["bucket"]},{"emoji":"🐛","aliases":["bug"]},{"emoji":"🏗️","aliases":["building_construction"]},{"emoji":"💡","aliases":["bulb"]},{"emoji":"🇧🇬","aliases":["bulgaria"]},{"emoji":"🚅","aliases":["bullettrain_front"]},{"emoji":"🚄","aliases":["bullettrain_side"]},{"emoji":"🇧🇫","aliases":["burkina_faso"]},{"emoji":"🌯","aliases":["burrito"]},{"emoji":"🇧🇮","aliases":["burundi"]},{"emoji":"🚌","aliases":["bus"]},{"emoji":"🕴️","aliases":["business_suit_levitating"]},{"emoji":"🚏","aliases":["busstop"]},{"emoji":"👤","aliases":["bust_in_silhouette"]},{"emoji":"👥","aliases":["busts_in_silhouette"]},{"emoji":"🧈","aliases":["butter"]},{"emoji":"🦋","aliases":["butterfly"]},{"emoji":"🌵","aliases":["cactus"]},{"emoji":"🍰","aliases":["cake"]},{"emoji":"📆","aliases":["calendar"]},{"emoji":"🤙","aliases":["call_me_hand"]},{"emoji":"📲","aliases":["calling"]},{"emoji":"🇰🇭","aliases":["cambodia"]},{"emoji":"🐫","aliases":["camel"]},{"emoji":"📷","aliases":["camera"]},{"emoji":"📸","aliases":["camera_flash"]},{"emoji":"🇨🇲","aliases":["cameroon"]},{"emoji":"🏕️","aliases":["camping"]},{"emoji":"🇨🇦","aliases":["canada"]},{"emoji":"🇮🇨","aliases":["canary_islands"]},{"emoji":"♋","aliases":["cancer"]},{"emoji":"🕯️","aliases":["candle"]},{"emoji":"🍬","aliases":["candy"]},{"emoji":"🥫","aliases":["canned_food"]},{"emoji":"🛶","aliases":["canoe"]},{"emoji":"🇨🇻","aliases":["cape_verde"]},{"emoji":"🔠","aliases":["capital_abcd"]},{"emoji":"♑","aliases":["capricorn"]},{"emoji":"🚗","aliases":["car","red_car"]},{"emoji":"🗃️","aliases":["card_file_box"]},{"emoji":"📇","aliases":["card_index"]},{"emoji":"🗂️","aliases":["card_index_dividers"]},{"emoji":"🇧🇶","aliases":["caribbean_netherlands"]},{"emoji":"🎠","aliases":["carousel_horse"]},{"emoji":"🪚","aliases":["carpentry_saw"]},{"emoji":"🥕","aliases":["carrot"]},{"emoji":"🤸","aliases":["cartwheeling"]},{"emoji":"🐱","aliases":["cat"]},{"emoji":"🐈","aliases":["cat2"]},{"emoji":"🇰🇾","aliases":["cayman_islands"]},{"emoji":"💿","aliases":["cd"]},{"emoji":"🇨🇫","aliases":["central_african_republic"]},{"emoji":"🇪🇦","aliases":["ceuta_melilla"]},{"emoji":"🇹🇩","aliases":["chad"]},{"emoji":"⛓️","aliases":["chains"]},{"emoji":"🪑","aliases":["chair"]},{"emoji":"🍾","aliases":["champagne"]},{"emoji":"💹","aliases":["chart"]},{"emoji":"📉","aliases":["chart_with_downwards_trend"]},{"emoji":"📈","aliases":["chart_with_upwards_trend"]},{"emoji":"🏁","aliases":["checkered_flag"]},{"emoji":"🧀","aliases":["cheese"]},{"emoji":"🍒","aliases":["cherries"]},{"emoji":"🌸","aliases":["cherry_blossom"]},{"emoji":"♟️","aliases":["chess_pawn"]},{"emoji":"🌰","aliases":["chestnut"]},{"emoji":"🐔","aliases":["chicken"]},{"emoji":"🧒","aliases":["child"]},{"emoji":"🚸","aliases":["children_crossing"]},{"emoji":"🇨🇱","aliases":["chile"]},{"emoji":"🐿️","aliases":["chipmunk"]},{"emoji":"🍫","aliases":["chocolate_bar"]},{"emoji":"🥢","aliases":["chopsticks"]},{"emoji":"🇨🇽","aliases":["christmas_island"]},{"emoji":"🎄","aliases":["christmas_tree"]},{"emoji":"⛪","aliases":["church"]},{"emoji":"🎦","aliases":["cinema"]},{"emoji":"🎪","aliases":["circus_tent"]},{"emoji":"🌇","aliases":["city_sunrise"]},{"emoji":"🌆","aliases":["city_sunset"]},{"emoji":"🏙️","aliases":["cityscape"]},{"emoji":"🆑","aliases":["cl"]},{"emoji":"🗜️","aliases":["clamp"]},{"emoji":"👏","aliases":["clap"]},{"emoji":"🎬","aliases":["clapper"]},{"emoji":"🏛️","aliases":["classical_building"]},{"emoji":"🧗","aliases":["climbing"]},{"emoji":"🧗‍♂️","aliases":["climbing_man"]},{"emoji":"🧗‍♀️","aliases":["climbing_woman"]},{"emoji":"🥂","aliases":["clinking_glasses"]},{"emoji":"📋","aliases":["clipboard"]},{"emoji":"🇨🇵","aliases":["clipperton_island"]},{"emoji":"🕐","aliases":["clock1"]},{"emoji":"🕙","aliases":["clock10"]},{"emoji":"🕥","aliases":["clock1030"]},{"emoji":"🕚","aliases":["clock11"]},{"emoji":"🕦","aliases":["clock1130"]},{"emoji":"🕛","aliases":["clock12"]},{"emoji":"🕧","aliases":["clock1230"]},{"emoji":"🕜","aliases":["clock130"]},{"emoji":"🕑","aliases":["clock2"]},{"emoji":"🕝","aliases":["clock230"]},{"emoji":"🕒","aliases":["clock3"]},{"emoji":"🕞","aliases":["clock330"]},{"emoji":"🕓","aliases":["clock4"]},{"emoji":"🕟","aliases":["clock430"]},{"emoji":"🕔","aliases":["clock5"]},{"emoji":"🕠","aliases":["clock530"]},{"emoji":"🕕","aliases":["clock6"]},{"emoji":"🕡","aliases":["clock630"]},{"emoji":"🕖","aliases":["clock7"]},{"emoji":"🕢","aliases":["clock730"]},{"emoji":"🕗","aliases":["clock8"]},{"emoji":"🕣","aliases":["clock830"]},{"emoji":"🕘","aliases":["clock9"]},{"emoji":"🕤","aliases":["clock930"]},{"emoji":"📕","aliases":["closed_book"]},{"emoji":"🔐","aliases":["closed_lock_with_key"]},{"emoji":"🌂","aliases":["closed_umbrella"]},{"emoji":"☁️","aliases":["cloud"]},{"emoji":"🌩️","aliases":["cloud_with_lightning"]},{"emoji":"⛈️","aliases":["cloud_with_lightning_and_rain"]},{"emoji":"🌧️","aliases":["cloud_with_rain"]},{"emoji":"🌨️","aliases":["cloud_with_snow"]},{"emoji":"🤡","aliases":["clown_face"]},{"emoji":"♣️","aliases":["clubs"]},{"emoji":"🇨🇳","aliases":["cn"]},{"emoji":"🧥","aliases":["coat"]},{"emoji":"🪳","aliases":["cockroach"]},{"emoji":"🍸","aliases":["cocktail"]},{"emoji":"🥥","aliases":["coconut"]},{"emoji":"🇨🇨","aliases":["cocos_islands"]},{"emoji":"☕","aliases":["coffee"]},{"emoji":"⚰️","aliases":["coffin"]},{"emoji":"🪙","aliases":["coin"]},{"emoji":"🥶","aliases":["cold_face"]},{"emoji":"😰","aliases":["cold_sweat"]},{"emoji":"🇨🇴","aliases":["colombia"]},{"emoji":"☄️","aliases":["comet"]},{"emoji":"🇰🇲","aliases":["comoros"]},{"emoji":"🧭","aliases":["compass"]},{"emoji":"💻","aliases":["computer"]},{"emoji":"🖱️","aliases":["computer_mouse"]},{"emoji":"🎊","aliases":["confetti_ball"]},{"emoji":"😖","aliases":["confounded"]},{"emoji":"😕","aliases":["confused"]},{"emoji":"🇨🇬","aliases":["congo_brazzaville"]},{"emoji":"🇨🇩","aliases":["congo_kinshasa"]},{"emoji":"㊗️","aliases":["congratulations"]},{"emoji":"🚧","aliases":["construction"]},{"emoji":"👷","aliases":["construction_worker"]},{"emoji":"👷‍♂️","aliases":["construction_worker_man"]},{"emoji":"👷‍♀️","aliases":["construction_worker_woman"]},{"emoji":"🎛️","aliases":["control_knobs"]},{"emoji":"🏪","aliases":["convenience_store"]},{"emoji":"🧑‍🍳","aliases":["cook"]},{"emoji":"🇨🇰","aliases":["cook_islands"]},{"emoji":"🍪","aliases":["cookie"]},{"emoji":"🆒","aliases":["cool"]},{"emoji":"©️","aliases":["copyright"]},{"emoji":"🪸","aliases":["coral"]},{"emoji":"🌽","aliases":["corn"]},{"emoji":"🇨🇷","aliases":["costa_rica"]},{"emoji":"🇨🇮","aliases":["cote_divoire"]},{"emoji":"🛋️","aliases":["couch_and_lamp"]},{"emoji":"👫","aliases":["couple"]},{"emoji":"💑","aliases":["couple_with_heart"]},{"emoji":"👨‍❤️‍👨","aliases":["couple_with_heart_man_man"]},{"emoji":"👩‍❤️‍👨","aliases":["couple_with_heart_woman_man"]},{"emoji":"👩‍❤️‍👩","aliases":["couple_with_heart_woman_woman"]},{"emoji":"💏","aliases":["couplekiss"]},{"emoji":"👨‍❤️‍💋‍👨","aliases":["couplekiss_man_man"]},{"emoji":"👩‍❤️‍💋‍👨","aliases":["couplekiss_man_woman"]},{"emoji":"👩‍❤️‍💋‍👩","aliases":["couplekiss_woman_woman"]},{"emoji":"🐮","aliases":["cow"]},{"emoji":"🐄","aliases":["cow2"]},{"emoji":"🤠","aliases":["cowboy_hat_face"]},{"emoji":"🦀","aliases":["crab"]},{"emoji":"🖍️","aliases":["crayon"]},{"emoji":"💳","aliases":["credit_card"]},{"emoji":"🌙","aliases":["crescent_moon"]},{"emoji":"🦗","aliases":["cricket"]},{"emoji":"🏏","aliases":["cricket_game"]},{"emoji":"🇭🇷","aliases":["croatia"]},{"emoji":"🐊","aliases":["crocodile"]},{"emoji":"🥐","aliases":["croissant"]},{"emoji":"🤞","aliases":["crossed_fingers"]},{"emoji":"🎌","aliases":["crossed_flags"]},{"emoji":"⚔️","aliases":["crossed_swords"]},{"emoji":"👑","aliases":["crown"]},{"emoji":"🩼","aliases":["crutch"]},{"emoji":"😢","aliases":["cry"]},{"emoji":"😿","aliases":["crying_cat_face"]},{"emoji":"🔮","aliases":["crystal_ball"]},{"emoji":"🇨🇺","aliases":["cuba"]},{"emoji":"🥒","aliases":["cucumber"]},{"emoji":"🥤","aliases":["cup_with_straw"]},{"emoji":"🧁","aliases":["cupcake"]},{"emoji":"💘","aliases":["cupid"]},{"emoji":"🇨🇼","aliases":["curacao"]},{"emoji":"🥌","aliases":["curling_stone"]},{"emoji":"👨‍🦱","aliases":["curly_haired_man"]},{"emoji":"👩‍🦱","aliases":["curly_haired_woman"]},{"emoji":"➰","aliases":["curly_loop"]},{"emoji":"💱","aliases":["currency_exchange"]},{"emoji":"🍛","aliases":["curry"]},{"emoji":"🤬","aliases":["cursing_face"]},{"emoji":"🍮","aliases":["custard"]},{"emoji":"🛃","aliases":["customs"]},{"emoji":"🥩","aliases":["cut_of_meat"]},{"emoji":"🌀","aliases":["cyclone"]},{"emoji":"🇨🇾","aliases":["cyprus"]},{"emoji":"🇨🇿","aliases":["czech_republic"]},{"emoji":"🗡️","aliases":["dagger"]},{"emoji":"👯","aliases":["dancers"]},{"emoji":"👯‍♂️","aliases":["dancing_men"]},{"emoji":"👯‍♀️","aliases":["dancing_women"]},{"emoji":"🍡","aliases":["dango"]},{"emoji":"🕶️","aliases":["dark_sunglasses"]},{"emoji":"🎯","aliases":["dart"]},{"emoji":"💨","aliases":["dash"]},{"emoji":"📅","aliases":["date"]},{"emoji":"🇩🇪","aliases":["de"]},{"emoji":"🧏‍♂️","aliases":["deaf_man"]},{"emoji":"🧏","aliases":["deaf_person"]},{"emoji":"🧏‍♀️","aliases":["deaf_woman"]},{"emoji":"🌳","aliases":["deciduous_tree"]},{"emoji":"🦌","aliases":["deer"]},{"emoji":"🇩🇰","aliases":["denmark"]},{"emoji":"🏬","aliases":["department_store"]},{"emoji":"🏚️","aliases":["derelict_house"]},{"emoji":"🏜️","aliases":["desert"]},{"emoji":"🏝️","aliases":["desert_island"]},{"emoji":"🖥️","aliases":["desktop_computer"]},{"emoji":"🕵️","aliases":["detective"]},{"emoji":"💠","aliases":["diamond_shape_with_a_dot_inside"]},{"emoji":"♦️","aliases":["diamonds"]},{"emoji":"🇩🇬","aliases":["diego_garcia"]},{"emoji":"😞","aliases":["disappointed"]},{"emoji":"😥","aliases":["disappointed_relieved"]},{"emoji":"🥸","aliases":["disguised_face"]},{"emoji":"🤿","aliases":["diving_mask"]},{"emoji":"🪔","aliases":["diya_lamp"]},{"emoji":"💫","aliases":["dizzy"]},{"emoji":"😵","aliases":["dizzy_face"]},{"emoji":"🇩🇯","aliases":["djibouti"]},{"emoji":"🧬","aliases":["dna"]},{"emoji":"🚯","aliases":["do_not_litter"]},{"emoji":"🦤","aliases":["dodo"]},{"emoji":"🐶","aliases":["dog"]},{"emoji":"🐕","aliases":["dog2"]},{"emoji":"💵","aliases":["dollar"]},{"emoji":"🎎","aliases":["dolls"]},{"emoji":"🐬","aliases":["dolphin","flipper"]},{"emoji":"🇩🇲","aliases":["dominica"]},{"emoji":"🇩🇴","aliases":["dominican_republic"]},{"emoji":"🚪","aliases":["door"]},{"emoji":"🫥","aliases":["dotted_line_face"]},{"emoji":"🍩","aliases":["doughnut"]},{"emoji":"🕊️","aliases":["dove"]},{"emoji":"🐉","aliases":["dragon"]},{"emoji":"🐲","aliases":["dragon_face"]},{"emoji":"👗","aliases":["dress"]},{"emoji":"🐪","aliases":["dromedary_camel"]},{"emoji":"🤤","aliases":["drooling_face"]},{"emoji":"🩸","aliases":["drop_of_blood"]},{"emoji":"💧","aliases":["droplet"]},{"emoji":"🥁","aliases":["drum"]},{"emoji":"🦆","aliases":["duck"]},{"emoji":"🥟","aliases":["dumpling"]},{"emoji":"📀","aliases":["dvd"]},{"emoji":"🦅","aliases":["eagle"]},{"emoji":"👂","aliases":["ear"]},{"emoji":"🌾","aliases":["ear_of_rice"]},{"emoji":"🦻","aliases":["ear_with_hearing_aid"]},{"emoji":"🌍","aliases":["earth_africa"]},{"emoji":"🌎","aliases":["earth_americas"]},{"emoji":"🌏","aliases":["earth_asia"]},{"emoji":"🇪🇨","aliases":["ecuador"]},{"emoji":"🥚","aliases":["egg"]},{"emoji":"🍆","aliases":["eggplant"]},{"emoji":"🇪🇬","aliases":["egypt"]},{"emoji":"8️⃣","aliases":["eight"]},{"emoji":"✴️","aliases":["eight_pointed_black_star"]},{"emoji":"✳️","aliases":["eight_spoked_asterisk"]},{"emoji":"⏏️","aliases":["eject_button"]},{"emoji":"🇸🇻","aliases":["el_salvador"]},{"emoji":"🔌","aliases":["electric_plug"]},{"emoji":"🐘","aliases":["elephant"]},{"emoji":"🛗","aliases":["elevator"]},{"emoji":"🧝","aliases":["elf"]},{"emoji":"🧝‍♂️","aliases":["elf_man"]},{"emoji":"🧝‍♀️","aliases":["elf_woman"]},{"emoji":"📧","aliases":["email","e-mail"]},{"emoji":"🪹","aliases":["empty_nest"]},{"emoji":"🔚","aliases":["end"]},{"emoji":"🏴󠁧󠁢󠁥󠁮󠁧󠁿","aliases":["england"]},{"emoji":"✉️","aliases":["envelope"]},{"emoji":"📩","aliases":["envelope_with_arrow"]},{"emoji":"🇬🇶","aliases":["equatorial_guinea"]},{"emoji":"🇪🇷","aliases":["eritrea"]},{"emoji":"🇪🇸","aliases":["es"]},{"emoji":"🇪🇪","aliases":["estonia"]},{"emoji":"🇪🇹","aliases":["ethiopia"]},{"emoji":"🇪🇺","aliases":["eu","european_union"]},{"emoji":"💶","aliases":["euro"]},{"emoji":"🏰","aliases":["european_castle"]},{"emoji":"🏤","aliases":["european_post_office"]},{"emoji":"🌲","aliases":["evergreen_tree"]},{"emoji":"❗","aliases":["exclamation","heavy_exclamation_mark"]},{"emoji":"🤯","aliases":["exploding_head"]},{"emoji":"😑","aliases":["expressionless"]},{"emoji":"👁️","aliases":["eye"]},{"emoji":"👁️‍🗨️","aliases":["eye_speech_bubble"]},{"emoji":"👓","aliases":["eyeglasses"]},{"emoji":"👀","aliases":["eyes"]},{"emoji":"😮‍💨","aliases":["face_exhaling"]},{"emoji":"🥹","aliases":["face_holding_back_tears"]},{"emoji":"😶‍🌫️","aliases":["face_in_clouds"]},{"emoji":"🫤","aliases":["face_with_diagonal_mouth"]},{"emoji":"🤕","aliases":["face_with_head_bandage"]},{"emoji":"🫢","aliases":["face_with_open_eyes_and_hand_over_mouth"]},{"emoji":"🫣","aliases":["face_with_peeking_eye"]},{"emoji":"😵‍💫","aliases":["face_with_spiral_eyes"]},{"emoji":"🤒","aliases":["face_with_thermometer"]},{"emoji":"🤦","aliases":["facepalm"]},{"emoji":"🏭","aliases":["factory"]},{"emoji":"🧑‍🏭","aliases":["factory_worker"]},{"emoji":"🧚","aliases":["fairy"]},{"emoji":"🧚‍♂️","aliases":["fairy_man"]},{"emoji":"🧚‍♀️","aliases":["fairy_woman"]},{"emoji":"🧆","aliases":["falafel"]},{"emoji":"🇫🇰","aliases":["falkland_islands"]},{"emoji":"🍂","aliases":["fallen_leaf"]},{"emoji":"👪","aliases":["family"]},{"emoji":"👨‍👦","aliases":["family_man_boy"]},{"emoji":"👨‍👦‍👦","aliases":["family_man_boy_boy"]},{"emoji":"👨‍👧","aliases":["family_man_girl"]},{"emoji":"👨‍👧‍👦","aliases":["family_man_girl_boy"]},{"emoji":"👨‍👧‍👧","aliases":["family_man_girl_girl"]},{"emoji":"👨‍👨‍👦","aliases":["family_man_man_boy"]},{"emoji":"👨‍👨‍👦‍👦","aliases":["family_man_man_boy_boy"]},{"emoji":"👨‍👨‍👧","aliases":["family_man_man_girl"]},{"emoji":"👨‍👨‍👧‍👦","aliases":["family_man_man_girl_boy"]},{"emoji":"👨‍👨‍👧‍👧","aliases":["family_man_man_girl_girl"]},{"emoji":"👨‍👩‍👦","aliases":["family_man_woman_boy"]},{"emoji":"👨‍👩‍👦‍👦","aliases":["family_man_woman_boy_boy"]},{"emoji":"👨‍👩‍👧","aliases":["family_man_woman_girl"]},{"emoji":"👨‍👩‍👧‍👦","aliases":["family_man_woman_girl_boy"]},{"emoji":"👨‍👩‍👧‍👧","aliases":["family_man_woman_girl_girl"]},{"emoji":"👩‍👦","aliases":["family_woman_boy"]},{"emoji":"👩‍👦‍👦","aliases":["family_woman_boy_boy"]},{"emoji":"👩‍👧","aliases":["family_woman_girl"]},{"emoji":"👩‍👧‍👦","aliases":["family_woman_girl_boy"]},{"emoji":"👩‍👧‍👧","aliases":["family_woman_girl_girl"]},{"emoji":"👩‍👩‍👦","aliases":["family_woman_woman_boy"]},{"emoji":"👩‍👩‍👦‍👦","aliases":["family_woman_woman_boy_boy"]},{"emoji":"👩‍👩‍👧","aliases":["family_woman_woman_girl"]},{"emoji":"👩‍👩‍👧‍👦","aliases":["family_woman_woman_girl_boy"]},{"emoji":"👩‍👩‍👧‍👧","aliases":["family_woman_woman_girl_girl"]},{"emoji":"🧑‍🌾","aliases":["farmer"]},{"emoji":"🇫🇴","aliases":["faroe_islands"]},{"emoji":"⏩","aliases":["fast_forward"]},{"emoji":"📠","aliases":["fax"]},{"emoji":"😨","aliases":["fearful"]},{"emoji":"🪶","aliases":["feather"]},{"emoji":"🐾","aliases":["feet","paw_prints"]},{"emoji":"🕵️‍♀️","aliases":["female_detective"]},{"emoji":"♀️","aliases":["female_sign"]},{"emoji":"🎡","aliases":["ferris_wheel"]},{"emoji":"⛴️","aliases":["ferry"]},{"emoji":"🏑","aliases":["field_hockey"]},{"emoji":"🇫🇯","aliases":["fiji"]},{"emoji":"🗄️","aliases":["file_cabinet"]},{"emoji":"📁","aliases":["file_folder"]},{"emoji":"📽️","aliases":["film_projector"]},{"emoji":"🎞️","aliases":["film_strip"]},{"emoji":"🇫🇮","aliases":["finland"]},{"emoji":"🔥","aliases":["fire"]},{"emoji":"🚒","aliases":["fire_engine"]},{"emoji":"🧯","aliases":["fire_extinguisher"]},{"emoji":"🧨","aliases":["firecracker"]},{"emoji":"🧑‍🚒","aliases":["firefighter"]},{"emoji":"🎆","aliases":["fireworks"]},{"emoji":"🌓","aliases":["first_quarter_moon"]},{"emoji":"🌛","aliases":["first_quarter_moon_with_face"]},{"emoji":"🐟","aliases":["fish"]},{"emoji":"🍥","aliases":["fish_cake"]},{"emoji":"🎣","aliases":["fishing_pole_and_fish"]},{"emoji":"🤛","aliases":["fist_left"]},{"emoji":"👊","aliases":["fist_oncoming","facepunch","punch"]},{"emoji":"✊","aliases":["fist_raised","fist"]},{"emoji":"🤜","aliases":["fist_right"]},{"emoji":"5️⃣","aliases":["five"]},{"emoji":"🎏","aliases":["flags"]},{"emoji":"🦩","aliases":["flamingo"]},{"emoji":"🔦","aliases":["flashlight"]},{"emoji":"🥿","aliases":["flat_shoe"]},{"emoji":"🫓","aliases":["flatbread"]},{"emoji":"⚜️","aliases":["fleur_de_lis"]},{"emoji":"🛬","aliases":["flight_arrival"]},{"emoji":"🛫","aliases":["flight_departure"]},{"emoji":"💾","aliases":["floppy_disk"]},{"emoji":"🎴","aliases":["flower_playing_cards"]},{"emoji":"😳","aliases":["flushed"]},{"emoji":"🪰","aliases":["fly"]},{"emoji":"🥏","aliases":["flying_disc"]},{"emoji":"🛸","aliases":["flying_saucer"]},{"emoji":"🌫️","aliases":["fog"]},{"emoji":"🌁","aliases":["foggy"]},{"emoji":"🫕","aliases":["fondue"]},{"emoji":"🦶","aliases":["foot"]},{"emoji":"🏈","aliases":["football"]},{"emoji":"👣","aliases":["footprints"]},{"emoji":"🍴","aliases":["fork_and_knife"]},{"emoji":"🥠","aliases":["fortune_cookie"]},{"emoji":"⛲","aliases":["fountain"]},{"emoji":"🖋️","aliases":["fountain_pen"]},{"emoji":"4️⃣","aliases":["four"]},{"emoji":"🍀","aliases":["four_leaf_clover"]},{"emoji":"🦊","aliases":["fox_face"]},{"emoji":"🇫🇷","aliases":["fr"]},{"emoji":"🖼️","aliases":["framed_picture"]},{"emoji":"🆓","aliases":["free"]},{"emoji":"🇬🇫","aliases":["french_guiana"]},{"emoji":"🇵🇫","aliases":["french_polynesia"]},{"emoji":"🇹🇫","aliases":["french_southern_territories"]},{"emoji":"🍳","aliases":["fried_egg"]},{"emoji":"🍤","aliases":["fried_shrimp"]},{"emoji":"🍟","aliases":["fries"]},{"emoji":"🐸","aliases":["frog"]},{"emoji":"😦","aliases":["frowning"]},{"emoji":"☹️","aliases":["frowning_face"]},{"emoji":"🙍‍♂️","aliases":["frowning_man"]},{"emoji":"🙍","aliases":["frowning_person"]},{"emoji":"🙍‍♀️","aliases":["frowning_woman"]},{"emoji":"⛽","aliases":["fuelpump"]},{"emoji":"🌕","aliases":["full_moon"]},{"emoji":"🌝","aliases":["full_moon_with_face"]},{"emoji":"⚱️","aliases":["funeral_urn"]},{"emoji":"🇬🇦","aliases":["gabon"]},{"emoji":"🇬🇲","aliases":["gambia"]},{"emoji":"🎲","aliases":["game_die"]},{"emoji":"🧄","aliases":["garlic"]},{"emoji":"🇬🇧","aliases":["gb","uk"]},{"emoji":"⚙️","aliases":["gear"]},{"emoji":"💎","aliases":["gem"]},{"emoji":"♊","aliases":["gemini"]},{"emoji":"🧞","aliases":["genie"]},{"emoji":"🧞‍♂️","aliases":["genie_man"]},{"emoji":"🧞‍♀️","aliases":["genie_woman"]},{"emoji":"🇬🇪","aliases":["georgia"]},{"emoji":"🇬🇭","aliases":["ghana"]},{"emoji":"👻","aliases":["ghost"]},{"emoji":"🇬🇮","aliases":["gibraltar"]},{"emoji":"🎁","aliases":["gift"]},{"emoji":"💝","aliases":["gift_heart"]},{"emoji":"🦒","aliases":["giraffe"]},{"emoji":"👧","aliases":["girl"]},{"emoji":"🌐","aliases":["globe_with_meridians"]},{"emoji":"🧤","aliases":["gloves"]},{"emoji":"🥅","aliases":["goal_net"]},{"emoji":"🐐","aliases":["goat"]},{"emoji":"🥽","aliases":["goggles"]},{"emoji":"⛳","aliases":["golf"]},{"emoji":"🏌️","aliases":["golfing"]},{"emoji":"🏌️‍♂️","aliases":["golfing_man"]},{"emoji":"🏌️‍♀️","aliases":["golfing_woman"]},{"emoji":"🦍","aliases":["gorilla"]},{"emoji":"🍇","aliases":["grapes"]},{"emoji":"🇬🇷","aliases":["greece"]},{"emoji":"🍏","aliases":["green_apple"]},{"emoji":"📗","aliases":["green_book"]},{"emoji":"🟢","aliases":["green_circle"]},{"emoji":"💚","aliases":["green_heart"]},{"emoji":"🥗","aliases":["green_salad"]},{"emoji":"🟩","aliases":["green_square"]},{"emoji":"🇬🇱","aliases":["greenland"]},{"emoji":"🇬🇩","aliases":["grenada"]},{"emoji":"❕","aliases":["grey_exclamation"]},{"emoji":"❔","aliases":["grey_question"]},{"emoji":"😬","aliases":["grimacing"]},{"emoji":"😁","aliases":["grin"]},{"emoji":"😀","aliases":["grinning"]},{"emoji":"🇬🇵","aliases":["guadeloupe"]},{"emoji":"🇬🇺","aliases":["guam"]},{"emoji":"💂","aliases":["guard"]},{"emoji":"💂‍♂️","aliases":["guardsman"]},{"emoji":"💂‍♀️","aliases":["guardswoman"]},{"emoji":"🇬🇹","aliases":["guatemala"]},{"emoji":"🇬🇬","aliases":["guernsey"]},{"emoji":"🦮","aliases":["guide_dog"]},{"emoji":"🇬🇳","aliases":["guinea"]},{"emoji":"🇬🇼","aliases":["guinea_bissau"]},{"emoji":"🎸","aliases":["guitar"]},{"emoji":"🔫","aliases":["gun"]},{"emoji":"🇬🇾","aliases":["guyana"]},{"emoji":"💇","aliases":["haircut"]},{"emoji":"💇‍♂️","aliases":["haircut_man"]},{"emoji":"💇‍♀️","aliases":["haircut_woman"]},{"emoji":"🇭🇹","aliases":["haiti"]},{"emoji":"🍔","aliases":["hamburger"]},{"emoji":"🔨","aliases":["hammer"]},{"emoji":"⚒️","aliases":["hammer_and_pick"]},{"emoji":"🛠️","aliases":["hammer_and_wrench"]},{"emoji":"🪬","aliases":["hamsa"]},{"emoji":"🐹","aliases":["hamster"]},{"emoji":"✋","aliases":["hand","raised_hand"]},{"emoji":"🤭","aliases":["hand_over_mouth"]},{"emoji":"🫰","aliases":["hand_with_index_finger_and_thumb_crossed"]},{"emoji":"👜","aliases":["handbag"]},{"emoji":"🤾","aliases":["handball_person"]},{"emoji":"🤝","aliases":["handshake"]},{"emoji":"💩","aliases":["hankey","poop","shit"]},{"emoji":"#️⃣","aliases":["hash"]},{"emoji":"🐥","aliases":["hatched_chick"]},{"emoji":"🐣","aliases":["hatching_chick"]},{"emoji":"🎧","aliases":["headphones"]},{"emoji":"🪦","aliases":["headstone"]},{"emoji":"🧑‍⚕️","aliases":["health_worker"]},{"emoji":"🙉","aliases":["hear_no_evil"]},{"emoji":"🇭🇲","aliases":["heard_mcdonald_islands"]},{"emoji":"❤️","aliases":["heart"]},{"emoji":"💟","aliases":["heart_decoration"]},{"emoji":"😍","aliases":["heart_eyes"]},{"emoji":"😻","aliases":["heart_eyes_cat"]},{"emoji":"🫶","aliases":["heart_hands"]},{"emoji":"❤️‍🔥","aliases":["heart_on_fire"]},{"emoji":"💓","aliases":["heartbeat"]},{"emoji":"💗","aliases":["heartpulse"]},{"emoji":"♥️","aliases":["hearts"]},{"emoji":"✔️","aliases":["heavy_check_mark"]},{"emoji":"➗","aliases":["heavy_division_sign"]},{"emoji":"💲","aliases":["heavy_dollar_sign"]},{"emoji":"🟰","aliases":["heavy_equals_sign"]},{"emoji":"❣️","aliases":["heavy_heart_exclamation"]},{"emoji":"➖","aliases":["heavy_minus_sign"]},{"emoji":"✖️","aliases":["heavy_multiplication_x"]},{"emoji":"➕","aliases":["heavy_plus_sign"]},{"emoji":"🦔","aliases":["hedgehog"]},{"emoji":"🚁","aliases":["helicopter"]},{"emoji":"🌿","aliases":["herb"]},{"emoji":"🌺","aliases":["hibiscus"]},{"emoji":"🔆","aliases":["high_brightness"]},{"emoji":"👠","aliases":["high_heel"]},{"emoji":"🥾","aliases":["hiking_boot"]},{"emoji":"🛕","aliases":["hindu_temple"]},{"emoji":"🦛","aliases":["hippopotamus"]},{"emoji":"🔪","aliases":["hocho","knife"]},{"emoji":"🕳️","aliases":["hole"]},{"emoji":"🇭🇳","aliases":["honduras"]},{"emoji":"🍯","aliases":["honey_pot"]},{"emoji":"🇭🇰","aliases":["hong_kong"]},{"emoji":"🪝","aliases":["hook"]},{"emoji":"🐴","aliases":["horse"]},{"emoji":"🏇","aliases":["horse_racing"]},{"emoji":"🏥","aliases":["hospital"]},{"emoji":"🥵","aliases":["hot_face"]},{"emoji":"🌶️","aliases":["hot_pepper"]},{"emoji":"🌭","aliases":["hotdog"]},{"emoji":"🏨","aliases":["hotel"]},{"emoji":"♨️","aliases":["hotsprings"]},{"emoji":"⌛","aliases":["hourglass"]},{"emoji":"⏳","aliases":["hourglass_flowing_sand"]},{"emoji":"🏠","aliases":["house"]},{"emoji":"🏡","aliases":["house_with_garden"]},{"emoji":"🏘️","aliases":["houses"]},{"emoji":"🤗","aliases":["hugs"]},{"emoji":"🇭🇺","aliases":["hungary"]},{"emoji":"😯","aliases":["hushed"]},{"emoji":"🛖","aliases":["hut"]},{"emoji":"🍨","aliases":["ice_cream"]},{"emoji":"🧊","aliases":["ice_cube"]},{"emoji":"🏒","aliases":["ice_hockey"]},{"emoji":"⛸️","aliases":["ice_skate"]},{"emoji":"🍦","aliases":["icecream"]},{"emoji":"🇮🇸","aliases":["iceland"]},{"emoji":"🆔","aliases":["id"]},{"emoji":"🪪","aliases":["identification_card"]},{"emoji":"🉐","aliases":["ideograph_advantage"]},{"emoji":"👿","aliases":["imp"]},{"emoji":"📥","aliases":["inbox_tray"]},{"emoji":"📨","aliases":["incoming_envelope"]},{"emoji":"🫵","aliases":["index_pointing_at_the_viewer"]},{"emoji":"🇮🇳","aliases":["india"]},{"emoji":"🇮🇩","aliases":["indonesia"]},{"emoji":"♾️","aliases":["infinity"]},{"emoji":"ℹ️","aliases":["information_source"]},{"emoji":"😇","aliases":["innocent"]},{"emoji":"⁉️","aliases":["interrobang"]},{"emoji":"📱","aliases":["iphone"]},{"emoji":"🇮🇷","aliases":["iran"]},{"emoji":"🇮🇶","aliases":["iraq"]},{"emoji":"🇮🇪","aliases":["ireland"]},{"emoji":"🇮🇲","aliases":["isle_of_man"]},{"emoji":"🇮🇱","aliases":["israel"]},{"emoji":"🇮🇹","aliases":["it"]},{"emoji":"🏮","aliases":["izakaya_lantern","lantern"]},{"emoji":"🎃","aliases":["jack_o_lantern"]},{"emoji":"🇯🇲","aliases":["jamaica"]},{"emoji":"🗾","aliases":["japan"]},{"emoji":"🏯","aliases":["japanese_castle"]},{"emoji":"👺","aliases":["japanese_goblin"]},{"emoji":"👹","aliases":["japanese_ogre"]},{"emoji":"🫙","aliases":["jar"]},{"emoji":"👖","aliases":["jeans"]},{"emoji":"🇯🇪","aliases":["jersey"]},{"emoji":"🧩","aliases":["jigsaw"]},{"emoji":"🇯🇴","aliases":["jordan"]},{"emoji":"😂","aliases":["joy"]},{"emoji":"😹","aliases":["joy_cat"]},{"emoji":"🕹️","aliases":["joystick"]},{"emoji":"🇯🇵","aliases":["jp"]},{"emoji":"🧑‍⚖️","aliases":["judge"]},{"emoji":"🤹","aliases":["juggling_person"]},{"emoji":"🕋","aliases":["kaaba"]},{"emoji":"🦘","aliases":["kangaroo"]},{"emoji":"🇰🇿","aliases":["kazakhstan"]},{"emoji":"🇰🇪","aliases":["kenya"]},{"emoji":"🔑","aliases":["key"]},{"emoji":"⌨️","aliases":["keyboard"]},{"emoji":"🔟","aliases":["keycap_ten"]},{"emoji":"🛴","aliases":["kick_scooter"]},{"emoji":"👘","aliases":["kimono"]},{"emoji":"🇰🇮","aliases":["kiribati"]},{"emoji":"💋","aliases":["kiss"]},{"emoji":"😗","aliases":["kissing"]},{"emoji":"😽","aliases":["kissing_cat"]},{"emoji":"😚","aliases":["kissing_closed_eyes"]},{"emoji":"😘","aliases":["kissing_heart"]},{"emoji":"😙","aliases":["kissing_smiling_eyes"]},{"emoji":"🪁","aliases":["kite"]},{"emoji":"🥝","aliases":["kiwi_fruit"]},{"emoji":"🧎‍♂️","aliases":["kneeling_man"]},{"emoji":"🧎","aliases":["kneeling_person"]},{"emoji":"🧎‍♀️","aliases":["kneeling_woman"]},{"emoji":"🪢","aliases":["knot"]},{"emoji":"🐨","aliases":["koala"]},{"emoji":"🈁","aliases":["koko"]},{"emoji":"🇽🇰","aliases":["kosovo"]},{"emoji":"🇰🇷","aliases":["kr"]},{"emoji":"🇰🇼","aliases":["kuwait"]},{"emoji":"🇰🇬","aliases":["kyrgyzstan"]},{"emoji":"🥼","aliases":["lab_coat"]},{"emoji":"🏷️","aliases":["label"]},{"emoji":"🥍","aliases":["lacrosse"]},{"emoji":"🪜","aliases":["ladder"]},{"emoji":"🐞","aliases":["lady_beetle"]},{"emoji":"🇱🇦","aliases":["laos"]},{"emoji":"🔵","aliases":["large_blue_circle"]},{"emoji":"🔷","aliases":["large_blue_diamond"]},{"emoji":"🔶","aliases":["large_orange_diamond"]},{"emoji":"🌗","aliases":["last_quarter_moon"]},{"emoji":"🌜","aliases":["last_quarter_moon_with_face"]},{"emoji":"✝️","aliases":["latin_cross"]},{"emoji":"🇱🇻","aliases":["latvia"]},{"emoji":"😆","aliases":["laughing","satisfied","laugh"]},{"emoji":"🥬","aliases":["leafy_green"]},{"emoji":"🍃","aliases":["leaves"]},{"emoji":"🇱🇧","aliases":["lebanon"]},{"emoji":"📒","aliases":["ledger"]},{"emoji":"🛅","aliases":["left_luggage"]},{"emoji":"↔️","aliases":["left_right_arrow"]},{"emoji":"🗨️","aliases":["left_speech_bubble"]},{"emoji":"↩️","aliases":["leftwards_arrow_with_hook"]},{"emoji":"🫲","aliases":["leftwards_hand"]},{"emoji":"🦵","aliases":["leg"]},{"emoji":"🍋","aliases":["lemon"]},{"emoji":"♌","aliases":["leo"]},{"emoji":"🐆","aliases":["leopard"]},{"emoji":"🇱🇸","aliases":["lesotho"]},{"emoji":"🎚️","aliases":["level_slider"]},{"emoji":"🇱🇷","aliases":["liberia"]},{"emoji":"♎","aliases":["libra"]},{"emoji":"🇱🇾","aliases":["libya"]},{"emoji":"🇱🇮","aliases":["liechtenstein"]},{"emoji":"🚈","aliases":["light_rail"]},{"emoji":"🔗","aliases":["link"]},{"emoji":"🦁","aliases":["lion"]},{"emoji":"👄","aliases":["lips"]},{"emoji":"💄","aliases":["lipstick"]},{"emoji":"🇱🇹","aliases":["lithuania"]},{"emoji":"🦎","aliases":["lizard"]},{"emoji":"🦙","aliases":["llama"]},{"emoji":"🦞","aliases":["lobster"]},{"emoji":"🔒","aliases":["lock"]},{"emoji":"🔏","aliases":["lock_with_ink_pen"]},{"emoji":"🍭","aliases":["lollipop"]},{"emoji":"🪘","aliases":["long_drum"]},{"emoji":"➿","aliases":["loop"]},{"emoji":"🧴","aliases":["lotion_bottle"]},{"emoji":"🪷","aliases":["lotus"]},{"emoji":"🧘","aliases":["lotus_position"]},{"emoji":"🧘‍♂️","aliases":["lotus_position_man"]},{"emoji":"🧘‍♀️","aliases":["lotus_position_woman"]},{"emoji":"🔊","aliases":["loud_sound"]},{"emoji":"📢","aliases":["loudspeaker"]},{"emoji":"🏩","aliases":["love_hotel"]},{"emoji":"💌","aliases":["love_letter"]},{"emoji":"🤟","aliases":["love_you_gesture"]},{"emoji":"🪫","aliases":["low_battery"]},{"emoji":"🔅","aliases":["low_brightness"]},{"emoji":"🧳","aliases":["luggage"]},{"emoji":"🫁","aliases":["lungs"]},{"emoji":"🇱🇺","aliases":["luxembourg"]},{"emoji":"🤥","aliases":["lying_face"]},{"emoji":"Ⓜ️","aliases":["m"]},{"emoji":"🇲🇴","aliases":["macau"]},{"emoji":"🇲🇰","aliases":["macedonia"]},{"emoji":"🇲🇬","aliases":["madagascar"]},{"emoji":"🔍","aliases":["mag"]},{"emoji":"🔎","aliases":["mag_right"]},{"emoji":"🧙","aliases":["mage"]},{"emoji":"🧙‍♂️","aliases":["mage_man"]},{"emoji":"🧙‍♀️","aliases":["mage_woman"]},{"emoji":"🪄","aliases":["magic_wand"]},{"emoji":"🧲","aliases":["magnet"]},{"emoji":"🀄","aliases":["mahjong"]},{"emoji":"📫","aliases":["mailbox"]},{"emoji":"📪","aliases":["mailbox_closed"]},{"emoji":"📬","aliases":["mailbox_with_mail"]},{"emoji":"📭","aliases":["mailbox_with_no_mail"]},{"emoji":"🇲🇼","aliases":["malawi"]},{"emoji":"🇲🇾","aliases":["malaysia"]},{"emoji":"🇲🇻","aliases":["maldives"]},{"emoji":"🕵️‍♂️","aliases":["male_detective"]},{"emoji":"♂️","aliases":["male_sign"]},{"emoji":"🇲🇱","aliases":["mali"]},{"emoji":"🇲🇹","aliases":["malta"]},{"emoji":"🦣","aliases":["mammoth"]},{"emoji":"👨","aliases":["man"]},{"emoji":"👨‍🎨","aliases":["man_artist"]},{"emoji":"👨‍🚀","aliases":["man_astronaut"]},{"emoji":"🧔‍♂️","aliases":["man_beard"]},{"emoji":"🤸‍♂️","aliases":["man_cartwheeling"]},{"emoji":"👨‍🍳","aliases":["man_cook"]},{"emoji":"🕺","aliases":["man_dancing"]},{"emoji":"🤦‍♂️","aliases":["man_facepalming"]},{"emoji":"👨‍🏭","aliases":["man_factory_worker"]},{"emoji":"👨‍🌾","aliases":["man_farmer"]},{"emoji":"👨‍🍼","aliases":["man_feeding_baby"]},{"emoji":"👨‍🚒","aliases":["man_firefighter"]},{"emoji":"👨‍⚕️","aliases":["man_health_worker"]},{"emoji":"👨‍🦽","aliases":["man_in_manual_wheelchair"]},{"emoji":"👨‍🦼","aliases":["man_in_motorized_wheelchair"]},{"emoji":"🤵‍♂️","aliases":["man_in_tuxedo"]},{"emoji":"👨‍⚖️","aliases":["man_judge"]},{"emoji":"🤹‍♂️","aliases":["man_juggling"]},{"emoji":"👨‍🔧","aliases":["man_mechanic"]},{"emoji":"👨‍💼","aliases":["man_office_worker"]},{"emoji":"👨‍✈️","aliases":["man_pilot"]},{"emoji":"🤾‍♂️","aliases":["man_playing_handball"]},{"emoji":"🤽‍♂️","aliases":["man_playing_water_polo"]},{"emoji":"👨‍🔬","aliases":["man_scientist"]},{"emoji":"🤷‍♂️","aliases":["man_shrugging"]},{"emoji":"👨‍🎤","aliases":["man_singer"]},{"emoji":"👨‍🎓","aliases":["man_student"]},{"emoji":"👨‍🏫","aliases":["man_teacher"]},{"emoji":"👨‍💻","aliases":["man_technologist"]},{"emoji":"👲","aliases":["man_with_gua_pi_mao"]},{"emoji":"👨‍🦯","aliases":["man_with_probing_cane"]},{"emoji":"👳‍♂️","aliases":["man_with_turban"]},{"emoji":"👰‍♂️","aliases":["man_with_veil"]},{"emoji":"🥭","aliases":["mango"]},{"emoji":"👞","aliases":["mans_shoe","shoe"]},{"emoji":"🕰️","aliases":["mantelpiece_clock"]},{"emoji":"🦽","aliases":["manual_wheelchair"]},{"emoji":"🍁","aliases":["maple_leaf"]},{"emoji":"🇲🇭","aliases":["marshall_islands"]},{"emoji":"🥋","aliases":["martial_arts_uniform"]},{"emoji":"🇲🇶","aliases":["martinique"]},{"emoji":"😷","aliases":["mask"]},{"emoji":"💆","aliases":["massage"]},{"emoji":"💆‍♂️","aliases":["massage_man"]},{"emoji":"💆‍♀️","aliases":["massage_woman"]},{"emoji":"🧉","aliases":["mate"]},{"emoji":"🇲🇷","aliases":["mauritania"]},{"emoji":"🇲🇺","aliases":["mauritius"]},{"emoji":"🇾🇹","aliases":["mayotte"]},{"emoji":"🍖","aliases":["meat_on_bone"]},{"emoji":"🧑‍🔧","aliases":["mechanic"]},{"emoji":"🦾","aliases":["mechanical_arm"]},{"emoji":"🦿","aliases":["mechanical_leg"]},{"emoji":"🎖️","aliases":["medal_military"]},{"emoji":"🏅","aliases":["medal_sports"]},{"emoji":"⚕️","aliases":["medical_symbol"]},{"emoji":"📣","aliases":["mega"]},{"emoji":"🍈","aliases":["melon"]},{"emoji":"🫠","aliases":["melting_face"]},{"emoji":"📝","aliases":["memo","pencil"]},{"emoji":"🤼‍♂️","aliases":["men_wrestling"]},{"emoji":"❤️‍🩹","aliases":["mending_heart"]},{"emoji":"🕎","aliases":["menorah"]},{"emoji":"🚹","aliases":["mens"]},{"emoji":"🧜‍♀️","aliases":["mermaid"]},{"emoji":"🧜‍♂️","aliases":["merman"]},{"emoji":"🧜","aliases":["merperson"]},{"emoji":"🤘","aliases":["metal"]},{"emoji":"🚇","aliases":["metro"]},{"emoji":"🇲🇽","aliases":["mexico"]},{"emoji":"🦠","aliases":["microbe"]},{"emoji":"🇫🇲","aliases":["micronesia"]},{"emoji":"🎤","aliases":["microphone"]},{"emoji":"🔬","aliases":["microscope"]},{"emoji":"🖕","aliases":["middle_finger","fu"]},{"emoji":"🪖","aliases":["military_helmet"]},{"emoji":"🥛","aliases":["milk_glass"]},{"emoji":"🌌","aliases":["milky_way"]},{"emoji":"🚐","aliases":["minibus"]},{"emoji":"💽","aliases":["minidisc"]},{"emoji":"🪞","aliases":["mirror"]},{"emoji":"🪩","aliases":["mirror_ball"]},{"emoji":"📴","aliases":["mobile_phone_off"]},{"emoji":"🇲🇩","aliases":["moldova"]},{"emoji":"🇲🇨","aliases":["monaco"]},{"emoji":"🤑","aliases":["money_mouth_face"]},{"emoji":"💸","aliases":["money_with_wings"]},{"emoji":"💰","aliases":["moneybag"]},{"emoji":"🇲🇳","aliases":["mongolia"]},{"emoji":"🐒","aliases":["monkey"]},{"emoji":"🐵","aliases":["monkey_face"]},{"emoji":"🧐","aliases":["monocle_face"]},{"emoji":"🚝","aliases":["monorail"]},{"emoji":"🇲🇪","aliases":["montenegro"]},{"emoji":"🇲🇸","aliases":["montserrat"]},{"emoji":"🌔","aliases":["moon","waxing_gibbous_moon"]},{"emoji":"🥮","aliases":["moon_cake"]},{"emoji":"🇲🇦","aliases":["morocco"]},{"emoji":"🎓","aliases":["mortar_board"]},{"emoji":"🕌","aliases":["mosque"]},{"emoji":"🦟","aliases":["mosquito"]},{"emoji":"🛥️","aliases":["motor_boat"]},{"emoji":"🛵","aliases":["motor_scooter"]},{"emoji":"🏍️","aliases":["motorcycle"]},{"emoji":"🦼","aliases":["motorized_wheelchair"]},{"emoji":"🛣️","aliases":["motorway"]},{"emoji":"🗻","aliases":["mount_fuji"]},{"emoji":"⛰️","aliases":["mountain"]},{"emoji":"🚵","aliases":["mountain_bicyclist"]},{"emoji":"🚵‍♂️","aliases":["mountain_biking_man"]},{"emoji":"🚵‍♀️","aliases":["mountain_biking_woman"]},{"emoji":"🚠","aliases":["mountain_cableway"]},{"emoji":"🚞","aliases":["mountain_railway"]},{"emoji":"🏔️","aliases":["mountain_snow"]},{"emoji":"🐭","aliases":["mouse"]},{"emoji":"🐁","aliases":["mouse2"]},{"emoji":"🪤","aliases":["mouse_trap"]},{"emoji":"🎥","aliases":["movie_camera"]},{"emoji":"🗿","aliases":["moyai"]},{"emoji":"🇲🇿","aliases":["mozambique"]},{"emoji":"🤶","aliases":["mrs_claus"]},{"emoji":"💪","aliases":["muscle"]},{"emoji":"🍄","aliases":["mushroom"]},{"emoji":"🎹","aliases":["musical_keyboard"]},{"emoji":"🎵","aliases":["musical_note"]},{"emoji":"🎼","aliases":["musical_score"]},{"emoji":"🔇","aliases":["mute"]},{"emoji":"🧑‍🎄","aliases":["mx_claus"]},{"emoji":"🇲🇲","aliases":["myanmar"]},{"emoji":"💅","aliases":["nail_care"]},{"emoji":"📛","aliases":["name_badge"]},{"emoji":"🇳🇦","aliases":["namibia"]},{"emoji":"🏞️","aliases":["national_park"]},{"emoji":"🇳🇷","aliases":["nauru"]},{"emoji":"🤢","aliases":["nauseated_face"]},{"emoji":"🧿","aliases":["nazar_amulet"]},{"emoji":"👔","aliases":["necktie"]},{"emoji":"❎","aliases":["negative_squared_cross_mark"]},{"emoji":"🇳🇵","aliases":["nepal"]},{"emoji":"🤓","aliases":["nerd_face"]},{"emoji":"🪺","aliases":["nest_with_eggs"]},{"emoji":"🪆","aliases":["nesting_dolls"]},{"emoji":"🇳🇱","aliases":["netherlands"]},{"emoji":"😐","aliases":["neutral_face"]},{"emoji":"🆕","aliases":["new"]},{"emoji":"🇳🇨","aliases":["new_caledonia"]},{"emoji":"🌑","aliases":["new_moon"]},{"emoji":"🌚","aliases":["new_moon_with_face"]},{"emoji":"🇳🇿","aliases":["new_zealand"]},{"emoji":"📰","aliases":["newspaper"]},{"emoji":"🗞️","aliases":["newspaper_roll"]},{"emoji":"⏭️","aliases":["next_track_button"]},{"emoji":"🆖","aliases":["ng"]},{"emoji":"🇳🇮","aliases":["nicaragua"]},{"emoji":"🇳🇪","aliases":["niger"]},{"emoji":"🇳🇬","aliases":["nigeria"]},{"emoji":"🌃","aliases":["night_with_stars"]},{"emoji":"9️⃣","aliases":["nine"]},{"emoji":"🥷","aliases":["ninja"]},{"emoji":"🇳🇺","aliases":["niue"]},{"emoji":"🔕","aliases":["no_bell"]},{"emoji":"🚳","aliases":["no_bicycles"]},{"emoji":"⛔","aliases":["no_entry"]},{"emoji":"🚫","aliases":["no_entry_sign"]},{"emoji":"🙅","aliases":["no_good"]},{"emoji":"🙅‍♂️","aliases":["no_good_man","ng_man"]},{"emoji":"🙅‍♀️","aliases":["no_good_woman","ng_woman"]},{"emoji":"📵","aliases":["no_mobile_phones"]},{"emoji":"😶","aliases":["no_mouth"]},{"emoji":"🚷","aliases":["no_pedestrians"]},{"emoji":"🚭","aliases":["no_smoking"]},{"emoji":"🚱","aliases":["non-potable_water"]},{"emoji":"🇳🇫","aliases":["norfolk_island"]},{"emoji":"🇰🇵","aliases":["north_korea"]},{"emoji":"🇲🇵","aliases":["northern_mariana_islands"]},{"emoji":"🇳🇴","aliases":["norway"]},{"emoji":"👃","aliases":["nose"]},{"emoji":"📓","aliases":["notebook"]},{"emoji":"📔","aliases":["notebook_with_decorative_cover"]},{"emoji":"🎶","aliases":["notes"]},{"emoji":"🔩","aliases":["nut_and_bolt"]},{"emoji":"⭕","aliases":["o"]},{"emoji":"🅾️","aliases":["o2"]},{"emoji":"🌊","aliases":["ocean"]},{"emoji":"🐙","aliases":["octopus"]},{"emoji":"🍢","aliases":["oden"]},{"emoji":"🏢","aliases":["office"]},{"emoji":"🧑‍💼","aliases":["office_worker"]},{"emoji":"🛢️","aliases":["oil_drum"]},{"emoji":"🆗","aliases":["ok"]},{"emoji":"👌","aliases":["ok_hand"]},{"emoji":"🙆‍♂️","aliases":["ok_man"]},{"emoji":"🙆","aliases":["ok_person"]},{"emoji":"🙆‍♀️","aliases":["ok_woman"]},{"emoji":"🗝️","aliases":["old_key"]},{"emoji":"🧓","aliases":["older_adult"]},{"emoji":"👴","aliases":["older_man"]},{"emoji":"👵","aliases":["older_woman"]},{"emoji":"🫒","aliases":["olive"]},{"emoji":"🕉️","aliases":["om"]},{"emoji":"🇴🇲","aliases":["oman"]},{"emoji":"🔛","aliases":["on"]},{"emoji":"🚘","aliases":["oncoming_automobile"]},{"emoji":"🚍","aliases":["oncoming_bus"]},{"emoji":"🚔","aliases":["oncoming_police_car"]},{"emoji":"🚖","aliases":["oncoming_taxi"]},{"emoji":"1️⃣","aliases":["one"]},{"emoji":"🩱","aliases":["one_piece_swimsuit"]},{"emoji":"🧅","aliases":["onion"]},{"emoji":"📂","aliases":["open_file_folder"]},{"emoji":"👐","aliases":["open_hands"]},{"emoji":"😮","aliases":["open_mouth"]},{"emoji":"☂️","aliases":["open_umbrella"]},{"emoji":"⛎","aliases":["ophiuchus"]},{"emoji":"📙","aliases":["orange_book"]},{"emoji":"🟠","aliases":["orange_circle"]},{"emoji":"🧡","aliases":["orange_heart"]},{"emoji":"🟧","aliases":["orange_square"]},{"emoji":"🦧","aliases":["orangutan"]},{"emoji":"☦️","aliases":["orthodox_cross"]},{"emoji":"🦦","aliases":["otter"]},{"emoji":"📤","aliases":["outbox_tray"]},{"emoji":"🦉","aliases":["owl"]},{"emoji":"🐂","aliases":["ox"]},{"emoji":"🦪","aliases":["oyster"]},{"emoji":"📦","aliases":["package"]},{"emoji":"📄","aliases":["page_facing_up"]},{"emoji":"📃","aliases":["page_with_curl"]},{"emoji":"📟","aliases":["pager"]},{"emoji":"🖌️","aliases":["paintbrush"]},{"emoji":"🇵🇰","aliases":["pakistan"]},{"emoji":"🇵🇼","aliases":["palau"]},{"emoji":"🇵🇸","aliases":["palestinian_territories"]},{"emoji":"🫳","aliases":["palm_down_hand"]},{"emoji":"🌴","aliases":["palm_tree"]},{"emoji":"🫴","aliases":["palm_up_hand"]},{"emoji":"🤲","aliases":["palms_up_together"]},{"emoji":"🇵🇦","aliases":["panama"]},{"emoji":"🥞","aliases":["pancakes"]},{"emoji":"🐼","aliases":["panda_face"]},{"emoji":"📎","aliases":["paperclip"]},{"emoji":"🖇️","aliases":["paperclips"]},{"emoji":"🇵🇬","aliases":["papua_new_guinea"]},{"emoji":"🪂","aliases":["parachute"]},{"emoji":"🇵🇾","aliases":["paraguay"]},{"emoji":"⛱️","aliases":["parasol_on_ground"]},{"emoji":"🅿️","aliases":["parking"]},{"emoji":"🦜","aliases":["parrot"]},{"emoji":"〽️","aliases":["part_alternation_mark"]},{"emoji":"⛅","aliases":["partly_sunny"]},{"emoji":"🥳","aliases":["partying_face"]},{"emoji":"🛳️","aliases":["passenger_ship"]},{"emoji":"🛂","aliases":["passport_control"]},{"emoji":"⏸️","aliases":["pause_button"]},{"emoji":"☮️","aliases":["peace_symbol"]},{"emoji":"🍑","aliases":["peach"]},{"emoji":"🦚","aliases":["peacock"]},{"emoji":"🥜","aliases":["peanuts"]},{"emoji":"🍐","aliases":["pear"]},{"emoji":"🖊️","aliases":["pen"]},{"emoji":"✏️","aliases":["pencil2"]},{"emoji":"🐧","aliases":["penguin"]},{"emoji":"😔","aliases":["pensive"]},{"emoji":"🧑‍🤝‍🧑","aliases":["people_holding_hands"]},{"emoji":"🫂","aliases":["people_hugging"]},{"emoji":"🎭","aliases":["performing_arts"]},{"emoji":"😣","aliases":["persevere"]},{"emoji":"🧑‍🦲","aliases":["person_bald"]},{"emoji":"🧑‍🦱","aliases":["person_curly_hair"]},{"emoji":"🧑‍🍼","aliases":["person_feeding_baby"]},{"emoji":"🤺","aliases":["person_fencing"]},{"emoji":"🧑‍🦽","aliases":["person_in_manual_wheelchair"]},{"emoji":"🧑‍🦼","aliases":["person_in_motorized_wheelchair"]},{"emoji":"🤵","aliases":["person_in_tuxedo"]},{"emoji":"🧑‍🦰","aliases":["person_red_hair"]},{"emoji":"🧑‍🦳","aliases":["person_white_hair"]},{"emoji":"🫅","aliases":["person_with_crown"]},{"emoji":"🧑‍🦯","aliases":["person_with_probing_cane"]},{"emoji":"👳","aliases":["person_with_turban"]},{"emoji":"👰","aliases":["person_with_veil"]},{"emoji":"🇵🇪","aliases":["peru"]},{"emoji":"🧫","aliases":["petri_dish"]},{"emoji":"🇵🇭","aliases":["philippines"]},{"emoji":"☎️","aliases":["phone","telephone"]},{"emoji":"⛏️","aliases":["pick"]},{"emoji":"🛻","aliases":["pickup_truck"]},{"emoji":"🥧","aliases":["pie"]},{"emoji":"🐷","aliases":["pig"]},{"emoji":"🐖","aliases":["pig2"]},{"emoji":"🐽","aliases":["pig_nose"]},{"emoji":"💊","aliases":["pill"]},{"emoji":"🧑‍✈️","aliases":["pilot"]},{"emoji":"🪅","aliases":["pinata"]},{"emoji":"🤌","aliases":["pinched_fingers"]},{"emoji":"🤏","aliases":["pinching_hand"]},{"emoji":"🍍","aliases":["pineapple"]},{"emoji":"🏓","aliases":["ping_pong"]},{"emoji":"🏴‍☠️","aliases":["pirate_flag"]},{"emoji":"♓","aliases":["pisces"]},{"emoji":"🇵🇳","aliases":["pitcairn_islands"]},{"emoji":"🍕","aliases":["pizza"]},{"emoji":"🪧","aliases":["placard"]},{"emoji":"🛐","aliases":["place_of_worship"]},{"emoji":"🍽️","aliases":["plate_with_cutlery"]},{"emoji":"⏯️","aliases":["play_or_pause_button"]},{"emoji":"🛝","aliases":["playground_slide"]},{"emoji":"🥺","aliases":["pleading_face"]},{"emoji":"🪠","aliases":["plunger"]},{"emoji":"👇","aliases":["point_down"]},{"emoji":"👈","aliases":["point_left"]},{"emoji":"👉","aliases":["point_right"]},{"emoji":"☝️","aliases":["point_up"]},{"emoji":"👆","aliases":["point_up_2"]},{"emoji":"🇵🇱","aliases":["poland"]},{"emoji":"🐻‍❄️","aliases":["polar_bear"]},{"emoji":"🚓","aliases":["police_car"]},{"emoji":"👮","aliases":["police_officer","cop"]},{"emoji":"👮‍♂️","aliases":["policeman"]},{"emoji":"👮‍♀️","aliases":["policewoman"]},{"emoji":"🐩","aliases":["poodle"]},{"emoji":"🍿","aliases":["popcorn"]},{"emoji":"🇵🇹","aliases":["portugal"]},{"emoji":"🏣","aliases":["post_office"]},{"emoji":"📯","aliases":["postal_horn"]},{"emoji":"📮","aliases":["postbox"]},{"emoji":"🚰","aliases":["potable_water"]},{"emoji":"🥔","aliases":["potato"]},{"emoji":"🪴","aliases":["potted_plant"]},{"emoji":"👝","aliases":["pouch"]},{"emoji":"🍗","aliases":["poultry_leg"]},{"emoji":"💷","aliases":["pound"]},{"emoji":"🫗","aliases":["pouring_liquid"]},{"emoji":"😾","aliases":["pouting_cat"]},{"emoji":"🙎","aliases":["pouting_face"]},{"emoji":"🙎‍♂️","aliases":["pouting_man"]},{"emoji":"🙎‍♀️","aliases":["pouting_woman"]},{"emoji":"🙏","aliases":["pray"]},{"emoji":"📿","aliases":["prayer_beads"]},{"emoji":"🫃","aliases":["pregnant_man"]},{"emoji":"🫄","aliases":["pregnant_person"]},{"emoji":"🤰","aliases":["pregnant_woman"]},{"emoji":"🥨","aliases":["pretzel"]},{"emoji":"⏮️","aliases":["previous_track_button"]},{"emoji":"🤴","aliases":["prince"]},{"emoji":"👸","aliases":["princess"]},{"emoji":"🖨️","aliases":["printer"]},{"emoji":"🦯","aliases":["probing_cane"]},{"emoji":"🇵🇷","aliases":["puerto_rico"]},{"emoji":"🟣","aliases":["purple_circle"]},{"emoji":"💜","aliases":["purple_heart"]},{"emoji":"🟪","aliases":["purple_square"]},{"emoji":"👛","aliases":["purse"]},{"emoji":"📌","aliases":["pushpin"]},{"emoji":"🚮","aliases":["put_litter_in_its_place"]},{"emoji":"🇶🇦","aliases":["qatar"]},{"emoji":"❓","aliases":["question"]},{"emoji":"🐰","aliases":["rabbit"]},{"emoji":"🐇","aliases":["rabbit2"]},{"emoji":"🦝","aliases":["raccoon"]},{"emoji":"🐎","aliases":["racehorse"]},{"emoji":"🏎️","aliases":["racing_car"]},{"emoji":"📻","aliases":["radio"]},{"emoji":"🔘","aliases":["radio_button"]},{"emoji":"☢️","aliases":["radioactive"]},{"emoji":"😡","aliases":["rage","pout"]},{"emoji":"🚃","aliases":["railway_car"]},{"emoji":"🛤️","aliases":["railway_track"]},{"emoji":"🌈","aliases":["rainbow"]},{"emoji":"🏳️‍🌈","aliases":["rainbow_flag"]},{"emoji":"🤚","aliases":["raised_back_of_hand"]},{"emoji":"🤨","aliases":["raised_eyebrow"]},{"emoji":"🖐️","aliases":["raised_hand_with_fingers_splayed"]},{"emoji":"🙌","aliases":["raised_hands"]},{"emoji":"🙋","aliases":["raising_hand"]},{"emoji":"🙋‍♂️","aliases":["raising_hand_man"]},{"emoji":"🙋‍♀️","aliases":["raising_hand_woman"]},{"emoji":"🐏","aliases":["ram"]},{"emoji":"🍜","aliases":["ramen"]},{"emoji":"🐀","aliases":["rat"]},{"emoji":"🪒","aliases":["razor"]},{"emoji":"🧾","aliases":["receipt"]},{"emoji":"⏺️","aliases":["record_button"]},{"emoji":"♻️","aliases":["recycle"]},{"emoji":"🔴","aliases":["red_circle"]},{"emoji":"🧧","aliases":["red_envelope"]},{"emoji":"👨‍🦰","aliases":["red_haired_man"]},{"emoji":"👩‍🦰","aliases":["red_haired_woman"]},{"emoji":"🟥","aliases":["red_square"]},{"emoji":"®️","aliases":["registered"]},{"emoji":"☺️","aliases":["relaxed"]},{"emoji":"😌","aliases":["relieved"]},{"emoji":"🎗️","aliases":["reminder_ribbon"]},{"emoji":"🔁","aliases":["repeat"]},{"emoji":"🔂","aliases":["repeat_one"]},{"emoji":"⛑️","aliases":["rescue_worker_helmet"]},{"emoji":"🚻","aliases":["restroom"]},{"emoji":"🇷🇪","aliases":["reunion"]},{"emoji":"💞","aliases":["revolving_hearts"]},{"emoji":"⏪","aliases":["rewind"]},{"emoji":"🦏","aliases":["rhinoceros"]},{"emoji":"🎀","aliases":["ribbon"]},{"emoji":"🍚","aliases":["rice"]},{"emoji":"🍙","aliases":["rice_ball"]},{"emoji":"🍘","aliases":["rice_cracker"]},{"emoji":"🎑","aliases":["rice_scene"]},{"emoji":"🗯️","aliases":["right_anger_bubble"]},{"emoji":"🫱","aliases":["rightwards_hand"]},{"emoji":"💍","aliases":["ring"]},{"emoji":"🛟","aliases":["ring_buoy"]},{"emoji":"🪐","aliases":["ringed_planet"]},{"emoji":"🤖","aliases":["robot"]},{"emoji":"🪨","aliases":["rock"]},{"emoji":"🚀","aliases":["rocket"]},{"emoji":"🤣","aliases":["rofl"]},{"emoji":"🙄","aliases":["roll_eyes"]},{"emoji":"🧻","aliases":["roll_of_paper"]},{"emoji":"🎢","aliases":["roller_coaster"]},{"emoji":"🛼","aliases":["roller_skate"]},{"emoji":"🇷🇴","aliases":["romania"]},{"emoji":"🐓","aliases":["rooster"]},{"emoji":"🌹","aliases":["rose"]},{"emoji":"🏵️","aliases":["rosette"]},{"emoji":"🚨","aliases":["rotating_light"]},{"emoji":"📍","aliases":["round_pushpin"]},{"emoji":"🚣","aliases":["rowboat"]},{"emoji":"🚣‍♂️","aliases":["rowing_man"]},{"emoji":"🚣‍♀️","aliases":["rowing_woman"]},{"emoji":"🇷🇺","aliases":["ru"]},{"emoji":"🏉","aliases":["rugby_football"]},{"emoji":"🏃","aliases":["runner","running"]},{"emoji":"🏃‍♂️","aliases":["running_man"]},{"emoji":"🎽","aliases":["running_shirt_with_sash"]},{"emoji":"🏃‍♀️","aliases":["running_woman"]},{"emoji":"🇷🇼","aliases":["rwanda"]},{"emoji":"🈂️","aliases":["sa"]},{"emoji":"🧷","aliases":["safety_pin"]},{"emoji":"🦺","aliases":["safety_vest"]},{"emoji":"♐","aliases":["sagittarius"]},{"emoji":"🍶","aliases":["sake"]},{"emoji":"🧂","aliases":["salt"]},{"emoji":"🫡","aliases":["saluting_face"]},{"emoji":"🇼🇸","aliases":["samoa"]},{"emoji":"🇸🇲","aliases":["san_marino"]},{"emoji":"👡","aliases":["sandal"]},{"emoji":"🥪","aliases":["sandwich"]},{"emoji":"🎅","aliases":["santa"]},{"emoji":"🇸🇹","aliases":["sao_tome_principe"]},{"emoji":"🥻","aliases":["sari"]},{"emoji":"📡","aliases":["satellite"]},{"emoji":"🇸🇦","aliases":["saudi_arabia"]},{"emoji":"🧖‍♂️","aliases":["sauna_man"]},{"emoji":"🧖","aliases":["sauna_person"]},{"emoji":"🧖‍♀️","aliases":["sauna_woman"]},{"emoji":"🦕","aliases":["sauropod"]},{"emoji":"🎷","aliases":["saxophone"]},{"emoji":"🧣","aliases":["scarf"]},{"emoji":"🏫","aliases":["school"]},{"emoji":"🎒","aliases":["school_satchel"]},{"emoji":"🧑‍🔬","aliases":["scientist"]},{"emoji":"✂️","aliases":["scissors"]},{"emoji":"🦂","aliases":["scorpion"]},{"emoji":"♏","aliases":["scorpius"]},{"emoji":"🏴󠁧󠁢󠁳󠁣󠁴󠁿","aliases":["scotland"]},{"emoji":"😱","aliases":["scream"]},{"emoji":"🙀","aliases":["scream_cat"]},{"emoji":"🪛","aliases":["screwdriver"]},{"emoji":"📜","aliases":["scroll"]},{"emoji":"🦭","aliases":["seal"]},{"emoji":"💺","aliases":["seat"]},{"emoji":"㊙️","aliases":["secret"]},{"emoji":"🙈","aliases":["see_no_evil"]},{"emoji":"🌱","aliases":["seedling"]},{"emoji":"🤳","aliases":["selfie"]},{"emoji":"🇸🇳","aliases":["senegal"]},{"emoji":"🇷🇸","aliases":["serbia"]},{"emoji":"🐕‍🦺","aliases":["service_dog"]},{"emoji":"7️⃣","aliases":["seven"]},{"emoji":"🪡","aliases":["sewing_needle"]},{"emoji":"🇸🇨","aliases":["seychelles"]},{"emoji":"🥘","aliases":["shallow_pan_of_food"]},{"emoji":"☘️","aliases":["shamrock"]},{"emoji":"🦈","aliases":["shark"]},{"emoji":"🍧","aliases":["shaved_ice"]},{"emoji":"🐑","aliases":["sheep"]},{"emoji":"🐚","aliases":["shell"]},{"emoji":"🛡️","aliases":["shield"]},{"emoji":"⛩️","aliases":["shinto_shrine"]},{"emoji":"🚢","aliases":["ship"]},{"emoji":"👕","aliases":["shirt","tshirt"]},{"emoji":"🛍️","aliases":["shopping"]},{"emoji":"🛒","aliases":["shopping_cart"]},{"emoji":"🩳","aliases":["shorts"]},{"emoji":"🚿","aliases":["shower"]},{"emoji":"🦐","aliases":["shrimp"]},{"emoji":"🤷","aliases":["shrug"]},{"emoji":"🤫","aliases":["shushing_face"]},{"emoji":"🇸🇱","aliases":["sierra_leone"]},{"emoji":"📶","aliases":["signal_strength"]},{"emoji":"🇸🇬","aliases":["singapore"]},{"emoji":"🧑‍🎤","aliases":["singer"]},{"emoji":"🇸🇽","aliases":["sint_maarten"]},{"emoji":"6️⃣","aliases":["six"]},{"emoji":"🔯","aliases":["six_pointed_star"]},{"emoji":"🛹","aliases":["skateboard"]},{"emoji":"🎿","aliases":["ski"]},{"emoji":"⛷️","aliases":["skier"]},{"emoji":"💀","aliases":["skull"]},{"emoji":"☠️","aliases":["skull_and_crossbones"]},{"emoji":"🦨","aliases":["skunk"]},{"emoji":"🛷","aliases":["sled"]},{"emoji":"😴","aliases":["sleeping"]},{"emoji":"🛌","aliases":["sleeping_bed"]},{"emoji":"😪","aliases":["sleepy"]},{"emoji":"🙁","aliases":["slightly_frowning_face"]},{"emoji":"🙂","aliases":["slightly_smiling_face"]},{"emoji":"🎰","aliases":["slot_machine"]},{"emoji":"🦥","aliases":["sloth"]},{"emoji":"🇸🇰","aliases":["slovakia"]},{"emoji":"🇸🇮","aliases":["slovenia"]},{"emoji":"🛩️","aliases":["small_airplane"]},{"emoji":"🔹","aliases":["small_blue_diamond"]},{"emoji":"🔸","aliases":["small_orange_diamond"]},{"emoji":"🔺","aliases":["small_red_triangle"]},{"emoji":"🔻","aliases":["small_red_triangle_down"]},{"emoji":"😄","aliases":["smile"]},{"emoji":"😸","aliases":["smile_cat"]},{"emoji":"😃","aliases":["smiley"]},{"emoji":"😺","aliases":["smiley_cat"]},{"emoji":"🥲","aliases":["smiling_face_with_tear"]},{"emoji":"🥰","aliases":["smiling_face_with_three_hearts"]},{"emoji":"😈","aliases":["smiling_imp"]},{"emoji":"😏","aliases":["smirk"]},{"emoji":"😼","aliases":["smirk_cat"]},{"emoji":"🚬","aliases":["smoking"]},{"emoji":"🐌","aliases":["snail"]},{"emoji":"🐍","aliases":["snake"]},{"emoji":"🤧","aliases":["sneezing_face"]},{"emoji":"🏂","aliases":["snowboarder"]},{"emoji":"❄️","aliases":["snowflake"]},{"emoji":"⛄","aliases":["snowman"]},{"emoji":"☃️","aliases":["snowman_with_snow"]},{"emoji":"🧼","aliases":["soap"]},{"emoji":"😭","aliases":["sob"]},{"emoji":"⚽","aliases":["soccer"]},{"emoji":"🧦","aliases":["socks"]},{"emoji":"🥎","aliases":["softball"]},{"emoji":"🇸🇧","aliases":["solomon_islands"]},{"emoji":"🇸🇴","aliases":["somalia"]},{"emoji":"🔜","aliases":["soon"]},{"emoji":"🆘","aliases":["sos"]},{"emoji":"🔉","aliases":["sound"]},{"emoji":"🇿🇦","aliases":["south_africa"]},{"emoji":"🇬🇸","aliases":["south_georgia_south_sandwich_islands"]},{"emoji":"🇸🇸","aliases":["south_sudan"]},{"emoji":"👾","aliases":["space_invader"]},{"emoji":"♠️","aliases":["spades"]},{"emoji":"🍝","aliases":["spaghetti"]},{"emoji":"❇️","aliases":["sparkle"]},{"emoji":"🎇","aliases":["sparkler"]},{"emoji":"✨","aliases":["sparkles"]},{"emoji":"💖","aliases":["sparkling_heart"]},{"emoji":"🙊","aliases":["speak_no_evil"]},{"emoji":"🔈","aliases":["speaker"]},{"emoji":"🗣️","aliases":["speaking_head"]},{"emoji":"💬","aliases":["speech_balloon"]},{"emoji":"🚤","aliases":["speedboat"]},{"emoji":"🕷️","aliases":["spider"]},{"emoji":"🕸️","aliases":["spider_web"]},{"emoji":"🗓️","aliases":["spiral_calendar"]},{"emoji":"🗒️","aliases":["spiral_notepad"]},{"emoji":"🧽","aliases":["sponge"]},{"emoji":"🥄","aliases":["spoon"]},{"emoji":"🦑","aliases":["squid"]},{"emoji":"🇱🇰","aliases":["sri_lanka"]},{"emoji":"🇧🇱","aliases":["st_barthelemy"]},{"emoji":"🇸🇭","aliases":["st_helena"]},{"emoji":"🇰🇳","aliases":["st_kitts_nevis"]},{"emoji":"🇱🇨","aliases":["st_lucia"]},{"emoji":"🇲🇫","aliases":["st_martin"]},{"emoji":"🇵🇲","aliases":["st_pierre_miquelon"]},{"emoji":"🇻🇨","aliases":["st_vincent_grenadines"]},{"emoji":"🏟️","aliases":["stadium"]},{"emoji":"🧍‍♂️","aliases":["standing_man"]},{"emoji":"🧍","aliases":["standing_person"]},{"emoji":"🧍‍♀️","aliases":["standing_woman"]},{"emoji":"⭐","aliases":["star"]},{"emoji":"🌟","aliases":["star2"]},{"emoji":"☪️","aliases":["star_and_crescent"]},{"emoji":"✡️","aliases":["star_of_david"]},{"emoji":"🤩","aliases":["star_struck"]},{"emoji":"🌠","aliases":["stars"]},{"emoji":"🚉","aliases":["station"]},{"emoji":"🗽","aliases":["statue_of_liberty"]},{"emoji":"🚂","aliases":["steam_locomotive"]},{"emoji":"🩺","aliases":["stethoscope"]},{"emoji":"🍲","aliases":["stew"]},{"emoji":"⏹️","aliases":["stop_button"]},{"emoji":"🛑","aliases":["stop_sign"]},{"emoji":"⏱️","aliases":["stopwatch"]},{"emoji":"📏","aliases":["straight_ruler"]},{"emoji":"🍓","aliases":["strawberry"]},{"emoji":"😛","aliases":["stuck_out_tongue"]},{"emoji":"😝","aliases":["stuck_out_tongue_closed_eyes"]},{"emoji":"😜","aliases":["stuck_out_tongue_winking_eye"]},{"emoji":"🧑‍🎓","aliases":["student"]},{"emoji":"🎙️","aliases":["studio_microphone"]},{"emoji":"🥙","aliases":["stuffed_flatbread"]},{"emoji":"🇸🇩","aliases":["sudan"]},{"emoji":"🌥️","aliases":["sun_behind_large_cloud"]},{"emoji":"🌦️","aliases":["sun_behind_rain_cloud"]},{"emoji":"🌤️","aliases":["sun_behind_small_cloud"]},{"emoji":"🌞","aliases":["sun_with_face"]},{"emoji":"🌻","aliases":["sunflower"]},{"emoji":"😎","aliases":["sunglasses"]},{"emoji":"☀️","aliases":["sunny"]},{"emoji":"🌅","aliases":["sunrise"]},{"emoji":"🌄","aliases":["sunrise_over_mountains"]},{"emoji":"🦸","aliases":["superhero"]},{"emoji":"🦸‍♂️","aliases":["superhero_man"]},{"emoji":"🦸‍♀️","aliases":["superhero_woman"]},{"emoji":"🦹","aliases":["supervillain"]},{"emoji":"🦹‍♂️","aliases":["supervillain_man"]},{"emoji":"🦹‍♀️","aliases":["supervillain_woman"]},{"emoji":"🏄","aliases":["surfer"]},{"emoji":"🏄‍♂️","aliases":["surfing_man"]},{"emoji":"🏄‍♀️","aliases":["surfing_woman"]},{"emoji":"🇸🇷","aliases":["suriname"]},{"emoji":"🍣","aliases":["sushi"]},{"emoji":"🚟","aliases":["suspension_railway"]},{"emoji":"🇸🇯","aliases":["svalbard_jan_mayen"]},{"emoji":"🦢","aliases":["swan"]},{"emoji":"🇸🇿","aliases":["swaziland"]},{"emoji":"😓","aliases":["sweat"]},{"emoji":"💦","aliases":["sweat_drops"]},{"emoji":"😅","aliases":["sweat_smile"]},{"emoji":"🇸🇪","aliases":["sweden"]},{"emoji":"🍠","aliases":["sweet_potato"]},{"emoji":"🩲","aliases":["swim_brief"]},{"emoji":"🏊","aliases":["swimmer"]},{"emoji":"🏊‍♂️","aliases":["swimming_man"]},{"emoji":"🏊‍♀️","aliases":["swimming_woman"]},{"emoji":"🇨🇭","aliases":["switzerland"]},{"emoji":"🔣","aliases":["symbols"]},{"emoji":"🕍","aliases":["synagogue"]},{"emoji":"🇸🇾","aliases":["syria"]},{"emoji":"💉","aliases":["syringe"]},{"emoji":"🦖","aliases":["t-rex"]},{"emoji":"🌮","aliases":["taco"]},{"emoji":"🎉","aliases":["tada","hooray"]},{"emoji":"🇹🇼","aliases":["taiwan"]},{"emoji":"🇹🇯","aliases":["tajikistan"]},{"emoji":"🥡","aliases":["takeout_box"]},{"emoji":"🫔","aliases":["tamale"]},{"emoji":"🎋","aliases":["tanabata_tree"]},{"emoji":"🍊","aliases":["tangerine","orange","mandarin"]},{"emoji":"🇹🇿","aliases":["tanzania"]},{"emoji":"♉","aliases":["taurus"]},{"emoji":"🚕","aliases":["taxi"]},{"emoji":"🍵","aliases":["tea"]},{"emoji":"🧑‍🏫","aliases":["teacher"]},{"emoji":"🫖","aliases":["teapot"]},{"emoji":"🧑‍💻","aliases":["technologist"]},{"emoji":"🧸","aliases":["teddy_bear"]},{"emoji":"📞","aliases":["telephone_receiver"]},{"emoji":"🔭","aliases":["telescope"]},{"emoji":"🎾","aliases":["tennis"]},{"emoji":"⛺","aliases":["tent"]},{"emoji":"🧪","aliases":["test_tube"]},{"emoji":"🇹🇭","aliases":["thailand"]},{"emoji":"🌡️","aliases":["thermometer"]},{"emoji":"🤔","aliases":["thinking"]},{"emoji":"🩴","aliases":["thong_sandal"]},{"emoji":"💭","aliases":["thought_balloon"]},{"emoji":"🧵","aliases":["thread"]},{"emoji":"3️⃣","aliases":["three"]},{"emoji":"🎫","aliases":["ticket"]},{"emoji":"🎟️","aliases":["tickets"]},{"emoji":"🐯","aliases":["tiger"]},{"emoji":"🐅","aliases":["tiger2"]},{"emoji":"⏲️","aliases":["timer_clock"]},{"emoji":"🇹🇱","aliases":["timor_leste"]},{"emoji":"💁‍♂️","aliases":["tipping_hand_man","sassy_man"]},{"emoji":"💁","aliases":["tipping_hand_person","information_desk_person"]},{"emoji":"💁‍♀️","aliases":["tipping_hand_woman","sassy_woman"]},{"emoji":"😫","aliases":["tired_face"]},{"emoji":"™️","aliases":["tm"]},{"emoji":"🇹🇬","aliases":["togo"]},{"emoji":"🚽","aliases":["toilet"]},{"emoji":"🇹🇰","aliases":["tokelau"]},{"emoji":"🗼","aliases":["tokyo_tower"]},{"emoji":"🍅","aliases":["tomato"]},{"emoji":"🇹🇴","aliases":["tonga"]},{"emoji":"👅","aliases":["tongue"]},{"emoji":"🧰","aliases":["toolbox"]},{"emoji":"🦷","aliases":["tooth"]},{"emoji":"🪥","aliases":["toothbrush"]},{"emoji":"🔝","aliases":["top"]},{"emoji":"🎩","aliases":["tophat"]},{"emoji":"🌪️","aliases":["tornado"]},{"emoji":"🇹🇷","aliases":["tr"]},{"emoji":"🖲️","aliases":["trackball"]},{"emoji":"🚜","aliases":["tractor"]},{"emoji":"🚥","aliases":["traffic_light"]},{"emoji":"🚋","aliases":["train"]},{"emoji":"🚆","aliases":["train2"]},{"emoji":"🚊","aliases":["tram"]},{"emoji":"🏳️‍⚧️","aliases":["transgender_flag"]},{"emoji":"⚧️","aliases":["transgender_symbol"]},{"emoji":"🚩","aliases":["triangular_flag_on_post"]},{"emoji":"📐","aliases":["triangular_ruler"]},{"emoji":"🔱","aliases":["trident"]},{"emoji":"🇹🇹","aliases":["trinidad_tobago"]},{"emoji":"🇹🇦","aliases":["tristan_da_cunha"]},{"emoji":"😤","aliases":["triumph"]},{"emoji":"🧌","aliases":["troll"]},{"emoji":"🚎","aliases":["trolleybus"]},{"emoji":"🏆","aliases":["trophy"]},{"emoji":"🍹","aliases":["tropical_drink"]},{"emoji":"🐠","aliases":["tropical_fish"]},{"emoji":"🚚","aliases":["truck"]},{"emoji":"🎺","aliases":["trumpet"]},{"emoji":"🌷","aliases":["tulip"]},{"emoji":"🥃","aliases":["tumbler_glass"]},{"emoji":"🇹🇳","aliases":["tunisia"]},{"emoji":"🦃","aliases":["turkey"]},{"emoji":"🇹🇲","aliases":["turkmenistan"]},{"emoji":"🇹🇨","aliases":["turks_caicos_islands"]},{"emoji":"🐢","aliases":["turtle"]},{"emoji":"🇹🇻","aliases":["tuvalu"]},{"emoji":"📺","aliases":["tv"]},{"emoji":"🔀","aliases":["twisted_rightwards_arrows"]},{"emoji":"2️⃣","aliases":["two"]},{"emoji":"💕","aliases":["two_hearts"]},{"emoji":"👬","aliases":["two_men_holding_hands"]},{"emoji":"👭","aliases":["two_women_holding_hands"]},{"emoji":"🈹","aliases":["u5272"]},{"emoji":"🈴","aliases":["u5408"]},{"emoji":"🈺","aliases":["u55b6"]},{"emoji":"🈯","aliases":["u6307"]},{"emoji":"🈷️","aliases":["u6708"]},{"emoji":"🈶","aliases":["u6709"]},{"emoji":"🈵","aliases":["u6e80"]},{"emoji":"🈚","aliases":["u7121"]},{"emoji":"🈸","aliases":["u7533"]},{"emoji":"🈲","aliases":["u7981"]},{"emoji":"🈳","aliases":["u7a7a"]},{"emoji":"🇺🇬","aliases":["uganda"]},{"emoji":"🇺🇦","aliases":["ukraine"]},{"emoji":"☔","aliases":["umbrella"]},{"emoji":"😒","aliases":["unamused"]},{"emoji":"🔞","aliases":["underage"]},{"emoji":"🦄","aliases":["unicorn"]},{"emoji":"🇦🇪","aliases":["united_arab_emirates"]},{"emoji":"🇺🇳","aliases":["united_nations"]},{"emoji":"🔓","aliases":["unlock"]},{"emoji":"🆙","aliases":["up"]},{"emoji":"🙃","aliases":["upside_down_face"]},{"emoji":"🇺🇾","aliases":["uruguay"]},{"emoji":"🇺🇸","aliases":["us"]},{"emoji":"🇺🇲","aliases":["us_outlying_islands"]},{"emoji":"🇻🇮","aliases":["us_virgin_islands"]},{"emoji":"🇺🇿","aliases":["uzbekistan"]},{"emoji":"✌️","aliases":["v"]},{"emoji":"🧛","aliases":["vampire"]},{"emoji":"🧛‍♂️","aliases":["vampire_man"]},{"emoji":"🧛‍♀️","aliases":["vampire_woman"]},{"emoji":"🇻🇺","aliases":["vanuatu"]},{"emoji":"🇻🇦","aliases":["vatican_city"]},{"emoji":"🇻🇪","aliases":["venezuela"]},{"emoji":"🚦","aliases":["vertical_traffic_light"]},{"emoji":"📼","aliases":["vhs"]},{"emoji":"📳","aliases":["vibration_mode"]},{"emoji":"📹","aliases":["video_camera"]},{"emoji":"🎮","aliases":["video_game"]},{"emoji":"🇻🇳","aliases":["vietnam"]},{"emoji":"🎻","aliases":["violin"]},{"emoji":"♍","aliases":["virgo"]},{"emoji":"🌋","aliases":["volcano"]},{"emoji":"🏐","aliases":["volleyball"]},{"emoji":"🤮","aliases":["vomiting_face"]},{"emoji":"🆚","aliases":["vs"]},{"emoji":"🖖","aliases":["vulcan_salute"]},{"emoji":"🧇","aliases":["waffle"]},{"emoji":"🏴󠁧󠁢󠁷󠁬󠁳󠁿","aliases":["wales"]},{"emoji":"🚶","aliases":["walking"]},{"emoji":"🚶‍♂️","aliases":["walking_man"]},{"emoji":"🚶‍♀️","aliases":["walking_woman"]},{"emoji":"🇼🇫","aliases":["wallis_futuna"]},{"emoji":"🌘","aliases":["waning_crescent_moon"]},{"emoji":"🌖","aliases":["waning_gibbous_moon"]},{"emoji":"⚠️","aliases":["warning"]},{"emoji":"🗑️","aliases":["wastebasket"]},{"emoji":"⌚","aliases":["watch"]},{"emoji":"🐃","aliases":["water_buffalo"]},{"emoji":"🤽","aliases":["water_polo"]},{"emoji":"🍉","aliases":["watermelon"]},{"emoji":"👋","aliases":["wave"]},{"emoji":"〰️","aliases":["wavy_dash"]},{"emoji":"🌒","aliases":["waxing_crescent_moon"]},{"emoji":"🚾","aliases":["wc"]},{"emoji":"😩","aliases":["weary"]},{"emoji":"💒","aliases":["wedding"]},{"emoji":"🏋️","aliases":["weight_lifting"]},{"emoji":"🏋️‍♂️","aliases":["weight_lifting_man"]},{"emoji":"🏋️‍♀️","aliases":["weight_lifting_woman"]},{"emoji":"🇪🇭","aliases":["western_sahara"]},{"emoji":"🐳","aliases":["whale"]},{"emoji":"🐋","aliases":["whale2"]},{"emoji":"🛞","aliases":["wheel"]},{"emoji":"☸️","aliases":["wheel_of_dharma"]},{"emoji":"♿","aliases":["wheelchair"]},{"emoji":"✅","aliases":["white_check_mark"]},{"emoji":"⚪","aliases":["white_circle"]},{"emoji":"🏳️","aliases":["white_flag"]},{"emoji":"💮","aliases":["white_flower"]},{"emoji":"👨‍🦳","aliases":["white_haired_man"]},{"emoji":"👩‍🦳","aliases":["white_haired_woman"]},{"emoji":"🤍","aliases":["white_heart"]},{"emoji":"⬜","aliases":["white_large_square"]},{"emoji":"◽","aliases":["white_medium_small_square"]},{"emoji":"◻️","aliases":["white_medium_square"]},{"emoji":"▫️","aliases":["white_small_square"]},{"emoji":"🔳","aliases":["white_square_button"]},{"emoji":"🥀","aliases":["wilted_flower"]},{"emoji":"🎐","aliases":["wind_chime"]},{"emoji":"🌬️","aliases":["wind_face"]},{"emoji":"🪟","aliases":["window"]},{"emoji":"🍷","aliases":["wine_glass"]},{"emoji":"😉","aliases":["wink"]},{"emoji":"🐺","aliases":["wolf"]},{"emoji":"👩","aliases":["woman"]},{"emoji":"👩‍🎨","aliases":["woman_artist"]},{"emoji":"👩‍🚀","aliases":["woman_astronaut"]},{"emoji":"🧔‍♀️","aliases":["woman_beard"]},{"emoji":"🤸‍♀️","aliases":["woman_cartwheeling"]},{"emoji":"👩‍🍳","aliases":["woman_cook"]},{"emoji":"💃","aliases":["woman_dancing","dancer"]},{"emoji":"🤦‍♀️","aliases":["woman_facepalming"]},{"emoji":"👩‍🏭","aliases":["woman_factory_worker"]},{"emoji":"👩‍🌾","aliases":["woman_farmer"]},{"emoji":"👩‍🍼","aliases":["woman_feeding_baby"]},{"emoji":"👩‍🚒","aliases":["woman_firefighter"]},{"emoji":"👩‍⚕️","aliases":["woman_health_worker"]},{"emoji":"👩‍🦽","aliases":["woman_in_manual_wheelchair"]},{"emoji":"👩‍🦼","aliases":["woman_in_motorized_wheelchair"]},{"emoji":"🤵‍♀️","aliases":["woman_in_tuxedo"]},{"emoji":"👩‍⚖️","aliases":["woman_judge"]},{"emoji":"🤹‍♀️","aliases":["woman_juggling"]},{"emoji":"👩‍🔧","aliases":["woman_mechanic"]},{"emoji":"👩‍💼","aliases":["woman_office_worker"]},{"emoji":"👩‍✈️","aliases":["woman_pilot"]},{"emoji":"🤾‍♀️","aliases":["woman_playing_handball"]},{"emoji":"🤽‍♀️","aliases":["woman_playing_water_polo"]},{"emoji":"👩‍🔬","aliases":["woman_scientist"]},{"emoji":"🤷‍♀️","aliases":["woman_shrugging"]},{"emoji":"👩‍🎤","aliases":["woman_singer"]},{"emoji":"👩‍🎓","aliases":["woman_student"]},{"emoji":"👩‍🏫","aliases":["woman_teacher"]},{"emoji":"👩‍💻","aliases":["woman_technologist"]},{"emoji":"🧕","aliases":["woman_with_headscarf"]},{"emoji":"👩‍🦯","aliases":["woman_with_probing_cane"]},{"emoji":"👳‍♀️","aliases":["woman_with_turban"]},{"emoji":"👰‍♀️","aliases":["woman_with_veil","bride_with_veil"]},{"emoji":"👚","aliases":["womans_clothes"]},{"emoji":"👒","aliases":["womans_hat"]},{"emoji":"🤼‍♀️","aliases":["women_wrestling"]},{"emoji":"🚺","aliases":["womens"]},{"emoji":"🪵","aliases":["wood"]},{"emoji":"🥴","aliases":["woozy_face"]},{"emoji":"🗺️","aliases":["world_map"]},{"emoji":"🪱","aliases":["worm"]},{"emoji":"😟","aliases":["worried"]},{"emoji":"🔧","aliases":["wrench"]},{"emoji":"🤼","aliases":["wrestling"]},{"emoji":"✍️","aliases":["writing_hand"]},{"emoji":"❌","aliases":["x"]},{"emoji":"🩻","aliases":["x_ray"]},{"emoji":"🧶","aliases":["yarn"]},{"emoji":"🥱","aliases":["yawning_face"]},{"emoji":"🟡","aliases":["yellow_circle"]},{"emoji":"💛","aliases":["yellow_heart"]},{"emoji":"🟨","aliases":["yellow_square"]},{"emoji":"🇾🇪","aliases":["yemen"]},{"emoji":"💴","aliases":["yen"]},{"emoji":"☯️","aliases":["yin_yang"]},{"emoji":"🪀","aliases":["yo_yo"]},{"emoji":"😋","aliases":["yum"]},{"emoji":"🇿🇲","aliases":["zambia"]},{"emoji":"🤪","aliases":["zany_face"]},{"emoji":"⚡","aliases":["zap"]},{"emoji":"🦓","aliases":["zebra"]},{"emoji":"0️⃣","aliases":["zero"]},{"emoji":"🇿🇼","aliases":["zimbabwe"]},{"emoji":"🤐","aliases":["zipper_mouth_face"]},{"emoji":"🧟","aliases":["zombie"]},{"emoji":"🧟‍♂️","aliases":["zombie_man"]},{"emoji":"🧟‍♀️","aliases":["zombie_woman"]},{"emoji":"💤","aliases":["zzz"]}] \ No newline at end of file +[{"emoji":"👍","aliases":["+1","thumbsup"]},{"emoji":"👎","aliases":["-1","thumbsdown"]},{"emoji":"💯","aliases":["100"]},{"emoji":"🔢","aliases":["1234"]},{"emoji":"🥇","aliases":["1st_place_medal"]},{"emoji":"🥈","aliases":["2nd_place_medal"]},{"emoji":"🥉","aliases":["3rd_place_medal"]},{"emoji":"🎱","aliases":["8ball"]},{"emoji":"🅰️","aliases":["a"]},{"emoji":"🆎","aliases":["ab"]},{"emoji":"🧮","aliases":["abacus"]},{"emoji":"🔤","aliases":["abc"]},{"emoji":"🔡","aliases":["abcd"]},{"emoji":"🉑","aliases":["accept"]},{"emoji":"🪗","aliases":["accordion"]},{"emoji":"🩹","aliases":["adhesive_bandage"]},{"emoji":"🧑","aliases":["adult"]},{"emoji":"🚡","aliases":["aerial_tramway"]},{"emoji":"🇦🇫","aliases":["afghanistan"]},{"emoji":"✈️","aliases":["airplane"]},{"emoji":"🇦🇽","aliases":["aland_islands"]},{"emoji":"⏰","aliases":["alarm_clock"]},{"emoji":"🇦🇱","aliases":["albania"]},{"emoji":"⚗️","aliases":["alembic"]},{"emoji":"🇩🇿","aliases":["algeria"]},{"emoji":"👽","aliases":["alien"]},{"emoji":"🚑","aliases":["ambulance"]},{"emoji":"🇦🇸","aliases":["american_samoa"]},{"emoji":"🏺","aliases":["amphora"]},{"emoji":"🫀","aliases":["anatomical_heart"]},{"emoji":"⚓","aliases":["anchor"]},{"emoji":"🇦🇩","aliases":["andorra"]},{"emoji":"👼","aliases":["angel"]},{"emoji":"💢","aliases":["anger"]},{"emoji":"🇦🇴","aliases":["angola"]},{"emoji":"😠","aliases":["angry"]},{"emoji":"🇦🇮","aliases":["anguilla"]},{"emoji":"😧","aliases":["anguished"]},{"emoji":"🐜","aliases":["ant"]},{"emoji":"🇦🇶","aliases":["antarctica"]},{"emoji":"🇦🇬","aliases":["antigua_barbuda"]},{"emoji":"🍎","aliases":["apple"]},{"emoji":"♒","aliases":["aquarius"]},{"emoji":"🇦🇷","aliases":["argentina"]},{"emoji":"♈","aliases":["aries"]},{"emoji":"🇦🇲","aliases":["armenia"]},{"emoji":"◀️","aliases":["arrow_backward"]},{"emoji":"⏬","aliases":["arrow_double_down"]},{"emoji":"⏫","aliases":["arrow_double_up"]},{"emoji":"⬇️","aliases":["arrow_down"]},{"emoji":"🔽","aliases":["arrow_down_small"]},{"emoji":"▶️","aliases":["arrow_forward"]},{"emoji":"⤵️","aliases":["arrow_heading_down"]},{"emoji":"⤴️","aliases":["arrow_heading_up"]},{"emoji":"⬅️","aliases":["arrow_left"]},{"emoji":"↙️","aliases":["arrow_lower_left"]},{"emoji":"↘️","aliases":["arrow_lower_right"]},{"emoji":"➡️","aliases":["arrow_right"]},{"emoji":"↪️","aliases":["arrow_right_hook"]},{"emoji":"⬆️","aliases":["arrow_up"]},{"emoji":"↕️","aliases":["arrow_up_down"]},{"emoji":"🔼","aliases":["arrow_up_small"]},{"emoji":"↖️","aliases":["arrow_upper_left"]},{"emoji":"↗️","aliases":["arrow_upper_right"]},{"emoji":"🔃","aliases":["arrows_clockwise"]},{"emoji":"🔄","aliases":["arrows_counterclockwise"]},{"emoji":"🎨","aliases":["art"]},{"emoji":"🚛","aliases":["articulated_lorry"]},{"emoji":"🛰️","aliases":["artificial_satellite"]},{"emoji":"🧑‍🎨","aliases":["artist"]},{"emoji":"🇦🇼","aliases":["aruba"]},{"emoji":"🇦🇨","aliases":["ascension_island"]},{"emoji":"*️⃣","aliases":["asterisk"]},{"emoji":"😲","aliases":["astonished"]},{"emoji":"🧑‍🚀","aliases":["astronaut"]},{"emoji":"👟","aliases":["athletic_shoe"]},{"emoji":"🏧","aliases":["atm"]},{"emoji":"⚛️","aliases":["atom_symbol"]},{"emoji":"🇦🇺","aliases":["australia"]},{"emoji":"🇦🇹","aliases":["austria"]},{"emoji":"🛺","aliases":["auto_rickshaw"]},{"emoji":"🥑","aliases":["avocado"]},{"emoji":"🪓","aliases":["axe"]},{"emoji":"🇦🇿","aliases":["azerbaijan"]},{"emoji":"🅱️","aliases":["b"]},{"emoji":"👶","aliases":["baby"]},{"emoji":"🍼","aliases":["baby_bottle"]},{"emoji":"🐤","aliases":["baby_chick"]},{"emoji":"🚼","aliases":["baby_symbol"]},{"emoji":"🔙","aliases":["back"]},{"emoji":"🥓","aliases":["bacon"]},{"emoji":"🦡","aliases":["badger"]},{"emoji":"🏸","aliases":["badminton"]},{"emoji":"🥯","aliases":["bagel"]},{"emoji":"🛄","aliases":["baggage_claim"]},{"emoji":"🥖","aliases":["baguette_bread"]},{"emoji":"🇧🇸","aliases":["bahamas"]},{"emoji":"🇧🇭","aliases":["bahrain"]},{"emoji":"⚖️","aliases":["balance_scale"]},{"emoji":"👨‍🦲","aliases":["bald_man"]},{"emoji":"👩‍🦲","aliases":["bald_woman"]},{"emoji":"🩰","aliases":["ballet_shoes"]},{"emoji":"🎈","aliases":["balloon"]},{"emoji":"🗳️","aliases":["ballot_box"]},{"emoji":"☑️","aliases":["ballot_box_with_check"]},{"emoji":"🎍","aliases":["bamboo"]},{"emoji":"🍌","aliases":["banana"]},{"emoji":"‼️","aliases":["bangbang"]},{"emoji":"🇧🇩","aliases":["bangladesh"]},{"emoji":"🪕","aliases":["banjo"]},{"emoji":"🏦","aliases":["bank"]},{"emoji":"📊","aliases":["bar_chart"]},{"emoji":"🇧🇧","aliases":["barbados"]},{"emoji":"💈","aliases":["barber"]},{"emoji":"⚾","aliases":["baseball"]},{"emoji":"🧺","aliases":["basket"]},{"emoji":"🏀","aliases":["basketball"]},{"emoji":"🦇","aliases":["bat"]},{"emoji":"🛀","aliases":["bath"]},{"emoji":"🛁","aliases":["bathtub"]},{"emoji":"🔋","aliases":["battery"]},{"emoji":"🏖️","aliases":["beach_umbrella"]},{"emoji":"🫘","aliases":["beans"]},{"emoji":"🐻","aliases":["bear"]},{"emoji":"🧔","aliases":["bearded_person"]},{"emoji":"🦫","aliases":["beaver"]},{"emoji":"🛏️","aliases":["bed"]},{"emoji":"🐝","aliases":["bee","honeybee"]},{"emoji":"🍺","aliases":["beer"]},{"emoji":"🍻","aliases":["beers"]},{"emoji":"🪲","aliases":["beetle"]},{"emoji":"🔰","aliases":["beginner"]},{"emoji":"🇧🇾","aliases":["belarus"]},{"emoji":"🇧🇪","aliases":["belgium"]},{"emoji":"🇧🇿","aliases":["belize"]},{"emoji":"🔔","aliases":["bell"]},{"emoji":"🫑","aliases":["bell_pepper"]},{"emoji":"🛎️","aliases":["bellhop_bell"]},{"emoji":"🇧🇯","aliases":["benin"]},{"emoji":"🍱","aliases":["bento"]},{"emoji":"🇧🇲","aliases":["bermuda"]},{"emoji":"🧃","aliases":["beverage_box"]},{"emoji":"🇧🇹","aliases":["bhutan"]},{"emoji":"🚴","aliases":["bicyclist"]},{"emoji":"🚲","aliases":["bike"]},{"emoji":"🚴‍♂️","aliases":["biking_man"]},{"emoji":"🚴‍♀️","aliases":["biking_woman"]},{"emoji":"👙","aliases":["bikini"]},{"emoji":"🧢","aliases":["billed_cap"]},{"emoji":"☣️","aliases":["biohazard"]},{"emoji":"🐦","aliases":["bird"]},{"emoji":"🎂","aliases":["birthday"]},{"emoji":"🦬","aliases":["bison"]},{"emoji":"🫦","aliases":["biting_lip"]},{"emoji":"🐦‍⬛","aliases":["black_bird"]},{"emoji":"🐈‍⬛","aliases":["black_cat"]},{"emoji":"⚫","aliases":["black_circle"]},{"emoji":"🏴","aliases":["black_flag"]},{"emoji":"🖤","aliases":["black_heart"]},{"emoji":"🃏","aliases":["black_joker"]},{"emoji":"⬛","aliases":["black_large_square"]},{"emoji":"◾","aliases":["black_medium_small_square"]},{"emoji":"◼️","aliases":["black_medium_square"]},{"emoji":"✒️","aliases":["black_nib"]},{"emoji":"▪️","aliases":["black_small_square"]},{"emoji":"🔲","aliases":["black_square_button"]},{"emoji":"👱‍♂️","aliases":["blond_haired_man"]},{"emoji":"👱","aliases":["blond_haired_person"]},{"emoji":"👱‍♀️","aliases":["blond_haired_woman","blonde_woman"]},{"emoji":"🌼","aliases":["blossom"]},{"emoji":"🐡","aliases":["blowfish"]},{"emoji":"📘","aliases":["blue_book"]},{"emoji":"🚙","aliases":["blue_car"]},{"emoji":"💙","aliases":["blue_heart"]},{"emoji":"🟦","aliases":["blue_square"]},{"emoji":"🫐","aliases":["blueberries"]},{"emoji":"😊","aliases":["blush"]},{"emoji":"🐗","aliases":["boar"]},{"emoji":"⛵","aliases":["boat","sailboat"]},{"emoji":"🇧🇴","aliases":["bolivia"]},{"emoji":"💣","aliases":["bomb"]},{"emoji":"🦴","aliases":["bone"]},{"emoji":"📖","aliases":["book","open_book"]},{"emoji":"🔖","aliases":["bookmark"]},{"emoji":"📑","aliases":["bookmark_tabs"]},{"emoji":"📚","aliases":["books"]},{"emoji":"💥","aliases":["boom","collision"]},{"emoji":"🪃","aliases":["boomerang"]},{"emoji":"👢","aliases":["boot"]},{"emoji":"🇧🇦","aliases":["bosnia_herzegovina"]},{"emoji":"🇧🇼","aliases":["botswana"]},{"emoji":"⛹️‍♂️","aliases":["bouncing_ball_man","basketball_man"]},{"emoji":"⛹️","aliases":["bouncing_ball_person"]},{"emoji":"⛹️‍♀️","aliases":["bouncing_ball_woman","basketball_woman"]},{"emoji":"💐","aliases":["bouquet"]},{"emoji":"🇧🇻","aliases":["bouvet_island"]},{"emoji":"🙇","aliases":["bow"]},{"emoji":"🏹","aliases":["bow_and_arrow"]},{"emoji":"🙇‍♂️","aliases":["bowing_man"]},{"emoji":"🙇‍♀️","aliases":["bowing_woman"]},{"emoji":"🥣","aliases":["bowl_with_spoon"]},{"emoji":"🎳","aliases":["bowling"]},{"emoji":"🥊","aliases":["boxing_glove"]},{"emoji":"👦","aliases":["boy"]},{"emoji":"🧠","aliases":["brain"]},{"emoji":"🇧🇷","aliases":["brazil"]},{"emoji":"🍞","aliases":["bread"]},{"emoji":"🤱","aliases":["breast_feeding"]},{"emoji":"🧱","aliases":["bricks"]},{"emoji":"🌉","aliases":["bridge_at_night"]},{"emoji":"💼","aliases":["briefcase"]},{"emoji":"🇮🇴","aliases":["british_indian_ocean_territory"]},{"emoji":"🇻🇬","aliases":["british_virgin_islands"]},{"emoji":"🥦","aliases":["broccoli"]},{"emoji":"💔","aliases":["broken_heart"]},{"emoji":"🧹","aliases":["broom"]},{"emoji":"🟤","aliases":["brown_circle"]},{"emoji":"🤎","aliases":["brown_heart"]},{"emoji":"🟫","aliases":["brown_square"]},{"emoji":"🇧🇳","aliases":["brunei"]},{"emoji":"🧋","aliases":["bubble_tea"]},{"emoji":"🫧","aliases":["bubbles"]},{"emoji":"🪣","aliases":["bucket"]},{"emoji":"🐛","aliases":["bug"]},{"emoji":"🏗️","aliases":["building_construction"]},{"emoji":"💡","aliases":["bulb"]},{"emoji":"🇧🇬","aliases":["bulgaria"]},{"emoji":"🚅","aliases":["bullettrain_front"]},{"emoji":"🚄","aliases":["bullettrain_side"]},{"emoji":"🇧🇫","aliases":["burkina_faso"]},{"emoji":"🌯","aliases":["burrito"]},{"emoji":"🇧🇮","aliases":["burundi"]},{"emoji":"🚌","aliases":["bus"]},{"emoji":"🕴️","aliases":["business_suit_levitating"]},{"emoji":"🚏","aliases":["busstop"]},{"emoji":"👤","aliases":["bust_in_silhouette"]},{"emoji":"👥","aliases":["busts_in_silhouette"]},{"emoji":"🧈","aliases":["butter"]},{"emoji":"🦋","aliases":["butterfly"]},{"emoji":"🌵","aliases":["cactus"]},{"emoji":"🍰","aliases":["cake"]},{"emoji":"📆","aliases":["calendar"]},{"emoji":"🤙","aliases":["call_me_hand"]},{"emoji":"📲","aliases":["calling"]},{"emoji":"🇰🇭","aliases":["cambodia"]},{"emoji":"🐫","aliases":["camel"]},{"emoji":"📷","aliases":["camera"]},{"emoji":"📸","aliases":["camera_flash"]},{"emoji":"🇨🇲","aliases":["cameroon"]},{"emoji":"🏕️","aliases":["camping"]},{"emoji":"🇨🇦","aliases":["canada"]},{"emoji":"🇮🇨","aliases":["canary_islands"]},{"emoji":"♋","aliases":["cancer"]},{"emoji":"🕯️","aliases":["candle"]},{"emoji":"🍬","aliases":["candy"]},{"emoji":"🥫","aliases":["canned_food"]},{"emoji":"🛶","aliases":["canoe"]},{"emoji":"🇨🇻","aliases":["cape_verde"]},{"emoji":"🔠","aliases":["capital_abcd"]},{"emoji":"♑","aliases":["capricorn"]},{"emoji":"🚗","aliases":["car","red_car"]},{"emoji":"🗃️","aliases":["card_file_box"]},{"emoji":"📇","aliases":["card_index"]},{"emoji":"🗂️","aliases":["card_index_dividers"]},{"emoji":"🇧🇶","aliases":["caribbean_netherlands"]},{"emoji":"🎠","aliases":["carousel_horse"]},{"emoji":"🪚","aliases":["carpentry_saw"]},{"emoji":"🥕","aliases":["carrot"]},{"emoji":"🤸","aliases":["cartwheeling"]},{"emoji":"🐱","aliases":["cat"]},{"emoji":"🐈","aliases":["cat2"]},{"emoji":"🇰🇾","aliases":["cayman_islands"]},{"emoji":"💿","aliases":["cd"]},{"emoji":"🇨🇫","aliases":["central_african_republic"]},{"emoji":"🇪🇦","aliases":["ceuta_melilla"]},{"emoji":"🇹🇩","aliases":["chad"]},{"emoji":"⛓️","aliases":["chains"]},{"emoji":"🪑","aliases":["chair"]},{"emoji":"🍾","aliases":["champagne"]},{"emoji":"💹","aliases":["chart"]},{"emoji":"📉","aliases":["chart_with_downwards_trend"]},{"emoji":"📈","aliases":["chart_with_upwards_trend"]},{"emoji":"🏁","aliases":["checkered_flag"]},{"emoji":"🧀","aliases":["cheese"]},{"emoji":"🍒","aliases":["cherries"]},{"emoji":"🌸","aliases":["cherry_blossom"]},{"emoji":"♟️","aliases":["chess_pawn"]},{"emoji":"🌰","aliases":["chestnut"]},{"emoji":"🐔","aliases":["chicken"]},{"emoji":"🧒","aliases":["child"]},{"emoji":"🚸","aliases":["children_crossing"]},{"emoji":"🇨🇱","aliases":["chile"]},{"emoji":"🐿️","aliases":["chipmunk"]},{"emoji":"🍫","aliases":["chocolate_bar"]},{"emoji":"🥢","aliases":["chopsticks"]},{"emoji":"🇨🇽","aliases":["christmas_island"]},{"emoji":"🎄","aliases":["christmas_tree"]},{"emoji":"⛪","aliases":["church"]},{"emoji":"🎦","aliases":["cinema"]},{"emoji":"🎪","aliases":["circus_tent"]},{"emoji":"🌇","aliases":["city_sunrise"]},{"emoji":"🌆","aliases":["city_sunset"]},{"emoji":"🏙️","aliases":["cityscape"]},{"emoji":"🆑","aliases":["cl"]},{"emoji":"🗜️","aliases":["clamp"]},{"emoji":"👏","aliases":["clap"]},{"emoji":"🎬","aliases":["clapper"]},{"emoji":"🏛️","aliases":["classical_building"]},{"emoji":"🧗","aliases":["climbing"]},{"emoji":"🧗‍♂️","aliases":["climbing_man"]},{"emoji":"🧗‍♀️","aliases":["climbing_woman"]},{"emoji":"🥂","aliases":["clinking_glasses"]},{"emoji":"📋","aliases":["clipboard"]},{"emoji":"🇨🇵","aliases":["clipperton_island"]},{"emoji":"🕐","aliases":["clock1"]},{"emoji":"🕙","aliases":["clock10"]},{"emoji":"🕥","aliases":["clock1030"]},{"emoji":"🕚","aliases":["clock11"]},{"emoji":"🕦","aliases":["clock1130"]},{"emoji":"🕛","aliases":["clock12"]},{"emoji":"🕧","aliases":["clock1230"]},{"emoji":"🕜","aliases":["clock130"]},{"emoji":"🕑","aliases":["clock2"]},{"emoji":"🕝","aliases":["clock230"]},{"emoji":"🕒","aliases":["clock3"]},{"emoji":"🕞","aliases":["clock330"]},{"emoji":"🕓","aliases":["clock4"]},{"emoji":"🕟","aliases":["clock430"]},{"emoji":"🕔","aliases":["clock5"]},{"emoji":"🕠","aliases":["clock530"]},{"emoji":"🕕","aliases":["clock6"]},{"emoji":"🕡","aliases":["clock630"]},{"emoji":"🕖","aliases":["clock7"]},{"emoji":"🕢","aliases":["clock730"]},{"emoji":"🕗","aliases":["clock8"]},{"emoji":"🕣","aliases":["clock830"]},{"emoji":"🕘","aliases":["clock9"]},{"emoji":"🕤","aliases":["clock930"]},{"emoji":"📕","aliases":["closed_book"]},{"emoji":"🔐","aliases":["closed_lock_with_key"]},{"emoji":"🌂","aliases":["closed_umbrella"]},{"emoji":"☁️","aliases":["cloud"]},{"emoji":"🌩️","aliases":["cloud_with_lightning"]},{"emoji":"⛈️","aliases":["cloud_with_lightning_and_rain"]},{"emoji":"🌧️","aliases":["cloud_with_rain"]},{"emoji":"🌨️","aliases":["cloud_with_snow"]},{"emoji":"🤡","aliases":["clown_face"]},{"emoji":"♣️","aliases":["clubs"]},{"emoji":"🇨🇳","aliases":["cn"]},{"emoji":"🧥","aliases":["coat"]},{"emoji":"🪳","aliases":["cockroach"]},{"emoji":"🍸","aliases":["cocktail"]},{"emoji":"🥥","aliases":["coconut"]},{"emoji":"🇨🇨","aliases":["cocos_islands"]},{"emoji":"☕","aliases":["coffee"]},{"emoji":"⚰️","aliases":["coffin"]},{"emoji":"🪙","aliases":["coin"]},{"emoji":"🥶","aliases":["cold_face"]},{"emoji":"😰","aliases":["cold_sweat"]},{"emoji":"🇨🇴","aliases":["colombia"]},{"emoji":"☄️","aliases":["comet"]},{"emoji":"🇰🇲","aliases":["comoros"]},{"emoji":"🧭","aliases":["compass"]},{"emoji":"💻","aliases":["computer"]},{"emoji":"🖱️","aliases":["computer_mouse"]},{"emoji":"🎊","aliases":["confetti_ball"]},{"emoji":"😖","aliases":["confounded"]},{"emoji":"😕","aliases":["confused"]},{"emoji":"🇨🇬","aliases":["congo_brazzaville"]},{"emoji":"🇨🇩","aliases":["congo_kinshasa"]},{"emoji":"㊗️","aliases":["congratulations"]},{"emoji":"🚧","aliases":["construction"]},{"emoji":"👷","aliases":["construction_worker"]},{"emoji":"👷‍♂️","aliases":["construction_worker_man"]},{"emoji":"👷‍♀️","aliases":["construction_worker_woman"]},{"emoji":"🎛️","aliases":["control_knobs"]},{"emoji":"🏪","aliases":["convenience_store"]},{"emoji":"🧑‍🍳","aliases":["cook"]},{"emoji":"🇨🇰","aliases":["cook_islands"]},{"emoji":"🍪","aliases":["cookie"]},{"emoji":"🆒","aliases":["cool"]},{"emoji":"©️","aliases":["copyright"]},{"emoji":"🪸","aliases":["coral"]},{"emoji":"🌽","aliases":["corn"]},{"emoji":"🇨🇷","aliases":["costa_rica"]},{"emoji":"🇨🇮","aliases":["cote_divoire"]},{"emoji":"🛋️","aliases":["couch_and_lamp"]},{"emoji":"👫","aliases":["couple"]},{"emoji":"💑","aliases":["couple_with_heart"]},{"emoji":"👨‍❤️‍👨","aliases":["couple_with_heart_man_man"]},{"emoji":"👩‍❤️‍👨","aliases":["couple_with_heart_woman_man"]},{"emoji":"👩‍❤️‍👩","aliases":["couple_with_heart_woman_woman"]},{"emoji":"💏","aliases":["couplekiss"]},{"emoji":"👨‍❤️‍💋‍👨","aliases":["couplekiss_man_man"]},{"emoji":"👩‍❤️‍💋‍👨","aliases":["couplekiss_man_woman"]},{"emoji":"👩‍❤️‍💋‍👩","aliases":["couplekiss_woman_woman"]},{"emoji":"🐮","aliases":["cow"]},{"emoji":"🐄","aliases":["cow2"]},{"emoji":"🤠","aliases":["cowboy_hat_face"]},{"emoji":"🦀","aliases":["crab"]},{"emoji":"🖍️","aliases":["crayon"]},{"emoji":"💳","aliases":["credit_card"]},{"emoji":"🌙","aliases":["crescent_moon"]},{"emoji":"🦗","aliases":["cricket"]},{"emoji":"🏏","aliases":["cricket_game"]},{"emoji":"🇭🇷","aliases":["croatia"]},{"emoji":"🐊","aliases":["crocodile"]},{"emoji":"🥐","aliases":["croissant"]},{"emoji":"🤞","aliases":["crossed_fingers"]},{"emoji":"🎌","aliases":["crossed_flags"]},{"emoji":"⚔️","aliases":["crossed_swords"]},{"emoji":"👑","aliases":["crown"]},{"emoji":"🩼","aliases":["crutch"]},{"emoji":"😢","aliases":["cry"]},{"emoji":"😿","aliases":["crying_cat_face"]},{"emoji":"🔮","aliases":["crystal_ball"]},{"emoji":"🇨🇺","aliases":["cuba"]},{"emoji":"🥒","aliases":["cucumber"]},{"emoji":"🥤","aliases":["cup_with_straw"]},{"emoji":"🧁","aliases":["cupcake"]},{"emoji":"💘","aliases":["cupid"]},{"emoji":"🇨🇼","aliases":["curacao"]},{"emoji":"🥌","aliases":["curling_stone"]},{"emoji":"👨‍🦱","aliases":["curly_haired_man"]},{"emoji":"👩‍🦱","aliases":["curly_haired_woman"]},{"emoji":"➰","aliases":["curly_loop"]},{"emoji":"💱","aliases":["currency_exchange"]},{"emoji":"🍛","aliases":["curry"]},{"emoji":"🤬","aliases":["cursing_face"]},{"emoji":"🍮","aliases":["custard"]},{"emoji":"🛃","aliases":["customs"]},{"emoji":"🥩","aliases":["cut_of_meat"]},{"emoji":"🌀","aliases":["cyclone"]},{"emoji":"🇨🇾","aliases":["cyprus"]},{"emoji":"🇨🇿","aliases":["czech_republic"]},{"emoji":"🗡️","aliases":["dagger"]},{"emoji":"👯","aliases":["dancers"]},{"emoji":"👯‍♂️","aliases":["dancing_men"]},{"emoji":"👯‍♀️","aliases":["dancing_women"]},{"emoji":"🍡","aliases":["dango"]},{"emoji":"🕶️","aliases":["dark_sunglasses"]},{"emoji":"🎯","aliases":["dart"]},{"emoji":"💨","aliases":["dash"]},{"emoji":"📅","aliases":["date"]},{"emoji":"🇩🇪","aliases":["de"]},{"emoji":"🧏‍♂️","aliases":["deaf_man"]},{"emoji":"🧏","aliases":["deaf_person"]},{"emoji":"🧏‍♀️","aliases":["deaf_woman"]},{"emoji":"🌳","aliases":["deciduous_tree"]},{"emoji":"🦌","aliases":["deer"]},{"emoji":"🇩🇰","aliases":["denmark"]},{"emoji":"🏬","aliases":["department_store"]},{"emoji":"🏚️","aliases":["derelict_house"]},{"emoji":"🏜️","aliases":["desert"]},{"emoji":"🏝️","aliases":["desert_island"]},{"emoji":"🖥️","aliases":["desktop_computer"]},{"emoji":"🕵️","aliases":["detective"]},{"emoji":"💠","aliases":["diamond_shape_with_a_dot_inside"]},{"emoji":"♦️","aliases":["diamonds"]},{"emoji":"🇩🇬","aliases":["diego_garcia"]},{"emoji":"😞","aliases":["disappointed"]},{"emoji":"😥","aliases":["disappointed_relieved"]},{"emoji":"🥸","aliases":["disguised_face"]},{"emoji":"🤿","aliases":["diving_mask"]},{"emoji":"🪔","aliases":["diya_lamp"]},{"emoji":"💫","aliases":["dizzy"]},{"emoji":"😵","aliases":["dizzy_face"]},{"emoji":"🇩🇯","aliases":["djibouti"]},{"emoji":"🧬","aliases":["dna"]},{"emoji":"🚯","aliases":["do_not_litter"]},{"emoji":"🦤","aliases":["dodo"]},{"emoji":"🐶","aliases":["dog"]},{"emoji":"🐕","aliases":["dog2"]},{"emoji":"💵","aliases":["dollar"]},{"emoji":"🎎","aliases":["dolls"]},{"emoji":"🐬","aliases":["dolphin","flipper"]},{"emoji":"🇩🇲","aliases":["dominica"]},{"emoji":"🇩🇴","aliases":["dominican_republic"]},{"emoji":"🫏","aliases":["donkey"]},{"emoji":"🚪","aliases":["door"]},{"emoji":"🫥","aliases":["dotted_line_face"]},{"emoji":"🍩","aliases":["doughnut"]},{"emoji":"🕊️","aliases":["dove"]},{"emoji":"🐉","aliases":["dragon"]},{"emoji":"🐲","aliases":["dragon_face"]},{"emoji":"👗","aliases":["dress"]},{"emoji":"🐪","aliases":["dromedary_camel"]},{"emoji":"🤤","aliases":["drooling_face"]},{"emoji":"🩸","aliases":["drop_of_blood"]},{"emoji":"💧","aliases":["droplet"]},{"emoji":"🥁","aliases":["drum"]},{"emoji":"🦆","aliases":["duck"]},{"emoji":"🥟","aliases":["dumpling"]},{"emoji":"📀","aliases":["dvd"]},{"emoji":"🦅","aliases":["eagle"]},{"emoji":"👂","aliases":["ear"]},{"emoji":"🌾","aliases":["ear_of_rice"]},{"emoji":"🦻","aliases":["ear_with_hearing_aid"]},{"emoji":"🌍","aliases":["earth_africa"]},{"emoji":"🌎","aliases":["earth_americas"]},{"emoji":"🌏","aliases":["earth_asia"]},{"emoji":"🇪🇨","aliases":["ecuador"]},{"emoji":"🥚","aliases":["egg"]},{"emoji":"🍆","aliases":["eggplant"]},{"emoji":"🇪🇬","aliases":["egypt"]},{"emoji":"8️⃣","aliases":["eight"]},{"emoji":"✴️","aliases":["eight_pointed_black_star"]},{"emoji":"✳️","aliases":["eight_spoked_asterisk"]},{"emoji":"⏏️","aliases":["eject_button"]},{"emoji":"🇸🇻","aliases":["el_salvador"]},{"emoji":"🔌","aliases":["electric_plug"]},{"emoji":"🐘","aliases":["elephant"]},{"emoji":"🛗","aliases":["elevator"]},{"emoji":"🧝","aliases":["elf"]},{"emoji":"🧝‍♂️","aliases":["elf_man"]},{"emoji":"🧝‍♀️","aliases":["elf_woman"]},{"emoji":"📧","aliases":["email","e-mail"]},{"emoji":"🪹","aliases":["empty_nest"]},{"emoji":"🔚","aliases":["end"]},{"emoji":"🏴󠁧󠁢󠁥󠁮󠁧󠁿","aliases":["england"]},{"emoji":"✉️","aliases":["envelope"]},{"emoji":"📩","aliases":["envelope_with_arrow"]},{"emoji":"🇬🇶","aliases":["equatorial_guinea"]},{"emoji":"🇪🇷","aliases":["eritrea"]},{"emoji":"🇪🇸","aliases":["es"]},{"emoji":"🇪🇪","aliases":["estonia"]},{"emoji":"🇪🇹","aliases":["ethiopia"]},{"emoji":"🇪🇺","aliases":["eu","european_union"]},{"emoji":"💶","aliases":["euro"]},{"emoji":"🏰","aliases":["european_castle"]},{"emoji":"🏤","aliases":["european_post_office"]},{"emoji":"🌲","aliases":["evergreen_tree"]},{"emoji":"❗","aliases":["exclamation","heavy_exclamation_mark"]},{"emoji":"🤯","aliases":["exploding_head"]},{"emoji":"😑","aliases":["expressionless"]},{"emoji":"👁️","aliases":["eye"]},{"emoji":"👁️‍🗨️","aliases":["eye_speech_bubble"]},{"emoji":"👓","aliases":["eyeglasses"]},{"emoji":"👀","aliases":["eyes"]},{"emoji":"😮‍💨","aliases":["face_exhaling"]},{"emoji":"🥹","aliases":["face_holding_back_tears"]},{"emoji":"😶‍🌫️","aliases":["face_in_clouds"]},{"emoji":"🫤","aliases":["face_with_diagonal_mouth"]},{"emoji":"🤕","aliases":["face_with_head_bandage"]},{"emoji":"🫢","aliases":["face_with_open_eyes_and_hand_over_mouth"]},{"emoji":"🫣","aliases":["face_with_peeking_eye"]},{"emoji":"😵‍💫","aliases":["face_with_spiral_eyes"]},{"emoji":"🤒","aliases":["face_with_thermometer"]},{"emoji":"🤦","aliases":["facepalm"]},{"emoji":"🏭","aliases":["factory"]},{"emoji":"🧑‍🏭","aliases":["factory_worker"]},{"emoji":"🧚","aliases":["fairy"]},{"emoji":"🧚‍♂️","aliases":["fairy_man"]},{"emoji":"🧚‍♀️","aliases":["fairy_woman"]},{"emoji":"🧆","aliases":["falafel"]},{"emoji":"🇫🇰","aliases":["falkland_islands"]},{"emoji":"🍂","aliases":["fallen_leaf"]},{"emoji":"👪","aliases":["family"]},{"emoji":"👨‍👦","aliases":["family_man_boy"]},{"emoji":"👨‍👦‍👦","aliases":["family_man_boy_boy"]},{"emoji":"👨‍👧","aliases":["family_man_girl"]},{"emoji":"👨‍👧‍👦","aliases":["family_man_girl_boy"]},{"emoji":"👨‍👧‍👧","aliases":["family_man_girl_girl"]},{"emoji":"👨‍👨‍👦","aliases":["family_man_man_boy"]},{"emoji":"👨‍👨‍👦‍👦","aliases":["family_man_man_boy_boy"]},{"emoji":"👨‍👨‍👧","aliases":["family_man_man_girl"]},{"emoji":"👨‍👨‍👧‍👦","aliases":["family_man_man_girl_boy"]},{"emoji":"👨‍👨‍👧‍👧","aliases":["family_man_man_girl_girl"]},{"emoji":"👨‍👩‍👦","aliases":["family_man_woman_boy"]},{"emoji":"👨‍👩‍👦‍👦","aliases":["family_man_woman_boy_boy"]},{"emoji":"👨‍👩‍👧","aliases":["family_man_woman_girl"]},{"emoji":"👨‍👩‍👧‍👦","aliases":["family_man_woman_girl_boy"]},{"emoji":"👨‍👩‍👧‍👧","aliases":["family_man_woman_girl_girl"]},{"emoji":"👩‍👦","aliases":["family_woman_boy"]},{"emoji":"👩‍👦‍👦","aliases":["family_woman_boy_boy"]},{"emoji":"👩‍👧","aliases":["family_woman_girl"]},{"emoji":"👩‍👧‍👦","aliases":["family_woman_girl_boy"]},{"emoji":"👩‍👧‍👧","aliases":["family_woman_girl_girl"]},{"emoji":"👩‍👩‍👦","aliases":["family_woman_woman_boy"]},{"emoji":"👩‍👩‍👦‍👦","aliases":["family_woman_woman_boy_boy"]},{"emoji":"👩‍👩‍👧","aliases":["family_woman_woman_girl"]},{"emoji":"👩‍👩‍👧‍👦","aliases":["family_woman_woman_girl_boy"]},{"emoji":"👩‍👩‍👧‍👧","aliases":["family_woman_woman_girl_girl"]},{"emoji":"🧑‍🌾","aliases":["farmer"]},{"emoji":"🇫🇴","aliases":["faroe_islands"]},{"emoji":"⏩","aliases":["fast_forward"]},{"emoji":"📠","aliases":["fax"]},{"emoji":"😨","aliases":["fearful"]},{"emoji":"🪶","aliases":["feather"]},{"emoji":"🐾","aliases":["feet","paw_prints"]},{"emoji":"🕵️‍♀️","aliases":["female_detective"]},{"emoji":"♀️","aliases":["female_sign"]},{"emoji":"🎡","aliases":["ferris_wheel"]},{"emoji":"⛴️","aliases":["ferry"]},{"emoji":"🏑","aliases":["field_hockey"]},{"emoji":"🇫🇯","aliases":["fiji"]},{"emoji":"🗄️","aliases":["file_cabinet"]},{"emoji":"📁","aliases":["file_folder"]},{"emoji":"📽️","aliases":["film_projector"]},{"emoji":"🎞️","aliases":["film_strip"]},{"emoji":"🇫🇮","aliases":["finland"]},{"emoji":"🔥","aliases":["fire"]},{"emoji":"🚒","aliases":["fire_engine"]},{"emoji":"🧯","aliases":["fire_extinguisher"]},{"emoji":"🧨","aliases":["firecracker"]},{"emoji":"🧑‍🚒","aliases":["firefighter"]},{"emoji":"🎆","aliases":["fireworks"]},{"emoji":"🌓","aliases":["first_quarter_moon"]},{"emoji":"🌛","aliases":["first_quarter_moon_with_face"]},{"emoji":"🐟","aliases":["fish"]},{"emoji":"🍥","aliases":["fish_cake"]},{"emoji":"🎣","aliases":["fishing_pole_and_fish"]},{"emoji":"🤛","aliases":["fist_left"]},{"emoji":"👊","aliases":["fist_oncoming","facepunch","punch"]},{"emoji":"✊","aliases":["fist_raised","fist"]},{"emoji":"🤜","aliases":["fist_right"]},{"emoji":"5️⃣","aliases":["five"]},{"emoji":"🎏","aliases":["flags"]},{"emoji":"🦩","aliases":["flamingo"]},{"emoji":"🔦","aliases":["flashlight"]},{"emoji":"🥿","aliases":["flat_shoe"]},{"emoji":"🫓","aliases":["flatbread"]},{"emoji":"⚜️","aliases":["fleur_de_lis"]},{"emoji":"🛬","aliases":["flight_arrival"]},{"emoji":"🛫","aliases":["flight_departure"]},{"emoji":"💾","aliases":["floppy_disk"]},{"emoji":"🎴","aliases":["flower_playing_cards"]},{"emoji":"😳","aliases":["flushed"]},{"emoji":"🪈","aliases":["flute"]},{"emoji":"🪰","aliases":["fly"]},{"emoji":"🥏","aliases":["flying_disc"]},{"emoji":"🛸","aliases":["flying_saucer"]},{"emoji":"🌫️","aliases":["fog"]},{"emoji":"🌁","aliases":["foggy"]},{"emoji":"🪭","aliases":["folding_hand_fan"]},{"emoji":"🫕","aliases":["fondue"]},{"emoji":"🦶","aliases":["foot"]},{"emoji":"🏈","aliases":["football"]},{"emoji":"👣","aliases":["footprints"]},{"emoji":"🍴","aliases":["fork_and_knife"]},{"emoji":"🥠","aliases":["fortune_cookie"]},{"emoji":"⛲","aliases":["fountain"]},{"emoji":"🖋️","aliases":["fountain_pen"]},{"emoji":"4️⃣","aliases":["four"]},{"emoji":"🍀","aliases":["four_leaf_clover"]},{"emoji":"🦊","aliases":["fox_face"]},{"emoji":"🇫🇷","aliases":["fr"]},{"emoji":"🖼️","aliases":["framed_picture"]},{"emoji":"🆓","aliases":["free"]},{"emoji":"🇬🇫","aliases":["french_guiana"]},{"emoji":"🇵🇫","aliases":["french_polynesia"]},{"emoji":"🇹🇫","aliases":["french_southern_territories"]},{"emoji":"🍳","aliases":["fried_egg"]},{"emoji":"🍤","aliases":["fried_shrimp"]},{"emoji":"🍟","aliases":["fries"]},{"emoji":"🐸","aliases":["frog"]},{"emoji":"😦","aliases":["frowning"]},{"emoji":"☹️","aliases":["frowning_face"]},{"emoji":"🙍‍♂️","aliases":["frowning_man"]},{"emoji":"🙍","aliases":["frowning_person"]},{"emoji":"🙍‍♀️","aliases":["frowning_woman"]},{"emoji":"⛽","aliases":["fuelpump"]},{"emoji":"🌕","aliases":["full_moon"]},{"emoji":"🌝","aliases":["full_moon_with_face"]},{"emoji":"⚱️","aliases":["funeral_urn"]},{"emoji":"🇬🇦","aliases":["gabon"]},{"emoji":"🇬🇲","aliases":["gambia"]},{"emoji":"🎲","aliases":["game_die"]},{"emoji":"🧄","aliases":["garlic"]},{"emoji":"🇬🇧","aliases":["gb","uk"]},{"emoji":"⚙️","aliases":["gear"]},{"emoji":"💎","aliases":["gem"]},{"emoji":"♊","aliases":["gemini"]},{"emoji":"🧞","aliases":["genie"]},{"emoji":"🧞‍♂️","aliases":["genie_man"]},{"emoji":"🧞‍♀️","aliases":["genie_woman"]},{"emoji":"🇬🇪","aliases":["georgia"]},{"emoji":"🇬🇭","aliases":["ghana"]},{"emoji":"👻","aliases":["ghost"]},{"emoji":"🇬🇮","aliases":["gibraltar"]},{"emoji":"🎁","aliases":["gift"]},{"emoji":"💝","aliases":["gift_heart"]},{"emoji":"🫚","aliases":["ginger_root"]},{"emoji":"🦒","aliases":["giraffe"]},{"emoji":"👧","aliases":["girl"]},{"emoji":"🌐","aliases":["globe_with_meridians"]},{"emoji":"🧤","aliases":["gloves"]},{"emoji":"🥅","aliases":["goal_net"]},{"emoji":"🐐","aliases":["goat"]},{"emoji":"🥽","aliases":["goggles"]},{"emoji":"⛳","aliases":["golf"]},{"emoji":"🏌️","aliases":["golfing"]},{"emoji":"🏌️‍♂️","aliases":["golfing_man"]},{"emoji":"🏌️‍♀️","aliases":["golfing_woman"]},{"emoji":"🪿","aliases":["goose"]},{"emoji":"🦍","aliases":["gorilla"]},{"emoji":"🍇","aliases":["grapes"]},{"emoji":"🇬🇷","aliases":["greece"]},{"emoji":"🍏","aliases":["green_apple"]},{"emoji":"📗","aliases":["green_book"]},{"emoji":"🟢","aliases":["green_circle"]},{"emoji":"💚","aliases":["green_heart"]},{"emoji":"🥗","aliases":["green_salad"]},{"emoji":"🟩","aliases":["green_square"]},{"emoji":"🇬🇱","aliases":["greenland"]},{"emoji":"🇬🇩","aliases":["grenada"]},{"emoji":"❕","aliases":["grey_exclamation"]},{"emoji":"🩶","aliases":["grey_heart"]},{"emoji":"❔","aliases":["grey_question"]},{"emoji":"😬","aliases":["grimacing"]},{"emoji":"😁","aliases":["grin"]},{"emoji":"😀","aliases":["grinning"]},{"emoji":"🇬🇵","aliases":["guadeloupe"]},{"emoji":"🇬🇺","aliases":["guam"]},{"emoji":"💂","aliases":["guard"]},{"emoji":"💂‍♂️","aliases":["guardsman"]},{"emoji":"💂‍♀️","aliases":["guardswoman"]},{"emoji":"🇬🇹","aliases":["guatemala"]},{"emoji":"🇬🇬","aliases":["guernsey"]},{"emoji":"🦮","aliases":["guide_dog"]},{"emoji":"🇬🇳","aliases":["guinea"]},{"emoji":"🇬🇼","aliases":["guinea_bissau"]},{"emoji":"🎸","aliases":["guitar"]},{"emoji":"🔫","aliases":["gun"]},{"emoji":"🇬🇾","aliases":["guyana"]},{"emoji":"🪮","aliases":["hair_pick"]},{"emoji":"💇","aliases":["haircut"]},{"emoji":"💇‍♂️","aliases":["haircut_man"]},{"emoji":"💇‍♀️","aliases":["haircut_woman"]},{"emoji":"🇭🇹","aliases":["haiti"]},{"emoji":"🍔","aliases":["hamburger"]},{"emoji":"🔨","aliases":["hammer"]},{"emoji":"⚒️","aliases":["hammer_and_pick"]},{"emoji":"🛠️","aliases":["hammer_and_wrench"]},{"emoji":"🪬","aliases":["hamsa"]},{"emoji":"🐹","aliases":["hamster"]},{"emoji":"✋","aliases":["hand","raised_hand"]},{"emoji":"🤭","aliases":["hand_over_mouth"]},{"emoji":"🫰","aliases":["hand_with_index_finger_and_thumb_crossed"]},{"emoji":"👜","aliases":["handbag"]},{"emoji":"🤾","aliases":["handball_person"]},{"emoji":"🤝","aliases":["handshake"]},{"emoji":"💩","aliases":["hankey","poop","shit"]},{"emoji":"#️⃣","aliases":["hash"]},{"emoji":"🐥","aliases":["hatched_chick"]},{"emoji":"🐣","aliases":["hatching_chick"]},{"emoji":"🎧","aliases":["headphones"]},{"emoji":"🪦","aliases":["headstone"]},{"emoji":"🧑‍⚕️","aliases":["health_worker"]},{"emoji":"🙉","aliases":["hear_no_evil"]},{"emoji":"🇭🇲","aliases":["heard_mcdonald_islands"]},{"emoji":"❤️","aliases":["heart"]},{"emoji":"💟","aliases":["heart_decoration"]},{"emoji":"😍","aliases":["heart_eyes"]},{"emoji":"😻","aliases":["heart_eyes_cat"]},{"emoji":"🫶","aliases":["heart_hands"]},{"emoji":"❤️‍🔥","aliases":["heart_on_fire"]},{"emoji":"💓","aliases":["heartbeat"]},{"emoji":"💗","aliases":["heartpulse"]},{"emoji":"♥️","aliases":["hearts"]},{"emoji":"✔️","aliases":["heavy_check_mark"]},{"emoji":"➗","aliases":["heavy_division_sign"]},{"emoji":"💲","aliases":["heavy_dollar_sign"]},{"emoji":"🟰","aliases":["heavy_equals_sign"]},{"emoji":"❣️","aliases":["heavy_heart_exclamation"]},{"emoji":"➖","aliases":["heavy_minus_sign"]},{"emoji":"✖️","aliases":["heavy_multiplication_x"]},{"emoji":"➕","aliases":["heavy_plus_sign"]},{"emoji":"🦔","aliases":["hedgehog"]},{"emoji":"🚁","aliases":["helicopter"]},{"emoji":"🌿","aliases":["herb"]},{"emoji":"🌺","aliases":["hibiscus"]},{"emoji":"🔆","aliases":["high_brightness"]},{"emoji":"👠","aliases":["high_heel"]},{"emoji":"🥾","aliases":["hiking_boot"]},{"emoji":"🛕","aliases":["hindu_temple"]},{"emoji":"🦛","aliases":["hippopotamus"]},{"emoji":"🔪","aliases":["hocho","knife"]},{"emoji":"🕳️","aliases":["hole"]},{"emoji":"🇭🇳","aliases":["honduras"]},{"emoji":"🍯","aliases":["honey_pot"]},{"emoji":"🇭🇰","aliases":["hong_kong"]},{"emoji":"🪝","aliases":["hook"]},{"emoji":"🐴","aliases":["horse"]},{"emoji":"🏇","aliases":["horse_racing"]},{"emoji":"🏥","aliases":["hospital"]},{"emoji":"🥵","aliases":["hot_face"]},{"emoji":"🌶️","aliases":["hot_pepper"]},{"emoji":"🌭","aliases":["hotdog"]},{"emoji":"🏨","aliases":["hotel"]},{"emoji":"♨️","aliases":["hotsprings"]},{"emoji":"⌛","aliases":["hourglass"]},{"emoji":"⏳","aliases":["hourglass_flowing_sand"]},{"emoji":"🏠","aliases":["house"]},{"emoji":"🏡","aliases":["house_with_garden"]},{"emoji":"🏘️","aliases":["houses"]},{"emoji":"🤗","aliases":["hugs"]},{"emoji":"🇭🇺","aliases":["hungary"]},{"emoji":"😯","aliases":["hushed"]},{"emoji":"🛖","aliases":["hut"]},{"emoji":"🪻","aliases":["hyacinth"]},{"emoji":"🍨","aliases":["ice_cream"]},{"emoji":"🧊","aliases":["ice_cube"]},{"emoji":"🏒","aliases":["ice_hockey"]},{"emoji":"⛸️","aliases":["ice_skate"]},{"emoji":"🍦","aliases":["icecream"]},{"emoji":"🇮🇸","aliases":["iceland"]},{"emoji":"🆔","aliases":["id"]},{"emoji":"🪪","aliases":["identification_card"]},{"emoji":"🉐","aliases":["ideograph_advantage"]},{"emoji":"👿","aliases":["imp"]},{"emoji":"📥","aliases":["inbox_tray"]},{"emoji":"📨","aliases":["incoming_envelope"]},{"emoji":"🫵","aliases":["index_pointing_at_the_viewer"]},{"emoji":"🇮🇳","aliases":["india"]},{"emoji":"🇮🇩","aliases":["indonesia"]},{"emoji":"♾️","aliases":["infinity"]},{"emoji":"ℹ️","aliases":["information_source"]},{"emoji":"😇","aliases":["innocent"]},{"emoji":"⁉️","aliases":["interrobang"]},{"emoji":"📱","aliases":["iphone"]},{"emoji":"🇮🇷","aliases":["iran"]},{"emoji":"🇮🇶","aliases":["iraq"]},{"emoji":"🇮🇪","aliases":["ireland"]},{"emoji":"🇮🇲","aliases":["isle_of_man"]},{"emoji":"🇮🇱","aliases":["israel"]},{"emoji":"🇮🇹","aliases":["it"]},{"emoji":"🏮","aliases":["izakaya_lantern","lantern"]},{"emoji":"🎃","aliases":["jack_o_lantern"]},{"emoji":"🇯🇲","aliases":["jamaica"]},{"emoji":"🗾","aliases":["japan"]},{"emoji":"🏯","aliases":["japanese_castle"]},{"emoji":"👺","aliases":["japanese_goblin"]},{"emoji":"👹","aliases":["japanese_ogre"]},{"emoji":"🫙","aliases":["jar"]},{"emoji":"👖","aliases":["jeans"]},{"emoji":"🪼","aliases":["jellyfish"]},{"emoji":"🇯🇪","aliases":["jersey"]},{"emoji":"🧩","aliases":["jigsaw"]},{"emoji":"🇯🇴","aliases":["jordan"]},{"emoji":"😂","aliases":["joy"]},{"emoji":"😹","aliases":["joy_cat"]},{"emoji":"🕹️","aliases":["joystick"]},{"emoji":"🇯🇵","aliases":["jp"]},{"emoji":"🧑‍⚖️","aliases":["judge"]},{"emoji":"🤹","aliases":["juggling_person"]},{"emoji":"🕋","aliases":["kaaba"]},{"emoji":"🦘","aliases":["kangaroo"]},{"emoji":"🇰🇿","aliases":["kazakhstan"]},{"emoji":"🇰🇪","aliases":["kenya"]},{"emoji":"🔑","aliases":["key"]},{"emoji":"⌨️","aliases":["keyboard"]},{"emoji":"🔟","aliases":["keycap_ten"]},{"emoji":"🪯","aliases":["khanda"]},{"emoji":"🛴","aliases":["kick_scooter"]},{"emoji":"👘","aliases":["kimono"]},{"emoji":"🇰🇮","aliases":["kiribati"]},{"emoji":"💋","aliases":["kiss"]},{"emoji":"😗","aliases":["kissing"]},{"emoji":"😽","aliases":["kissing_cat"]},{"emoji":"😚","aliases":["kissing_closed_eyes"]},{"emoji":"😘","aliases":["kissing_heart"]},{"emoji":"😙","aliases":["kissing_smiling_eyes"]},{"emoji":"🪁","aliases":["kite"]},{"emoji":"🥝","aliases":["kiwi_fruit"]},{"emoji":"🧎‍♂️","aliases":["kneeling_man"]},{"emoji":"🧎","aliases":["kneeling_person"]},{"emoji":"🧎‍♀️","aliases":["kneeling_woman"]},{"emoji":"🪢","aliases":["knot"]},{"emoji":"🐨","aliases":["koala"]},{"emoji":"🈁","aliases":["koko"]},{"emoji":"🇽🇰","aliases":["kosovo"]},{"emoji":"🇰🇷","aliases":["kr"]},{"emoji":"🇰🇼","aliases":["kuwait"]},{"emoji":"🇰🇬","aliases":["kyrgyzstan"]},{"emoji":"🥼","aliases":["lab_coat"]},{"emoji":"🏷️","aliases":["label"]},{"emoji":"🥍","aliases":["lacrosse"]},{"emoji":"🪜","aliases":["ladder"]},{"emoji":"🐞","aliases":["lady_beetle"]},{"emoji":"🇱🇦","aliases":["laos"]},{"emoji":"🔵","aliases":["large_blue_circle"]},{"emoji":"🔷","aliases":["large_blue_diamond"]},{"emoji":"🔶","aliases":["large_orange_diamond"]},{"emoji":"🌗","aliases":["last_quarter_moon"]},{"emoji":"🌜","aliases":["last_quarter_moon_with_face"]},{"emoji":"✝️","aliases":["latin_cross"]},{"emoji":"🇱🇻","aliases":["latvia"]},{"emoji":"😆","aliases":["laughing","satisfied","laugh"]},{"emoji":"🥬","aliases":["leafy_green"]},{"emoji":"🍃","aliases":["leaves"]},{"emoji":"🇱🇧","aliases":["lebanon"]},{"emoji":"📒","aliases":["ledger"]},{"emoji":"🛅","aliases":["left_luggage"]},{"emoji":"↔️","aliases":["left_right_arrow"]},{"emoji":"🗨️","aliases":["left_speech_bubble"]},{"emoji":"↩️","aliases":["leftwards_arrow_with_hook"]},{"emoji":"🫲","aliases":["leftwards_hand"]},{"emoji":"🫷","aliases":["leftwards_pushing_hand"]},{"emoji":"🦵","aliases":["leg"]},{"emoji":"🍋","aliases":["lemon"]},{"emoji":"♌","aliases":["leo"]},{"emoji":"🐆","aliases":["leopard"]},{"emoji":"🇱🇸","aliases":["lesotho"]},{"emoji":"🎚️","aliases":["level_slider"]},{"emoji":"🇱🇷","aliases":["liberia"]},{"emoji":"♎","aliases":["libra"]},{"emoji":"🇱🇾","aliases":["libya"]},{"emoji":"🇱🇮","aliases":["liechtenstein"]},{"emoji":"🩵","aliases":["light_blue_heart"]},{"emoji":"🚈","aliases":["light_rail"]},{"emoji":"🔗","aliases":["link"]},{"emoji":"🦁","aliases":["lion"]},{"emoji":"👄","aliases":["lips"]},{"emoji":"💄","aliases":["lipstick"]},{"emoji":"🇱🇹","aliases":["lithuania"]},{"emoji":"🦎","aliases":["lizard"]},{"emoji":"🦙","aliases":["llama"]},{"emoji":"🦞","aliases":["lobster"]},{"emoji":"🔒","aliases":["lock"]},{"emoji":"🔏","aliases":["lock_with_ink_pen"]},{"emoji":"🍭","aliases":["lollipop"]},{"emoji":"🪘","aliases":["long_drum"]},{"emoji":"➿","aliases":["loop"]},{"emoji":"🧴","aliases":["lotion_bottle"]},{"emoji":"🪷","aliases":["lotus"]},{"emoji":"🧘","aliases":["lotus_position"]},{"emoji":"🧘‍♂️","aliases":["lotus_position_man"]},{"emoji":"🧘‍♀️","aliases":["lotus_position_woman"]},{"emoji":"🔊","aliases":["loud_sound"]},{"emoji":"📢","aliases":["loudspeaker"]},{"emoji":"🏩","aliases":["love_hotel"]},{"emoji":"💌","aliases":["love_letter"]},{"emoji":"🤟","aliases":["love_you_gesture"]},{"emoji":"🪫","aliases":["low_battery"]},{"emoji":"🔅","aliases":["low_brightness"]},{"emoji":"🧳","aliases":["luggage"]},{"emoji":"🫁","aliases":["lungs"]},{"emoji":"🇱🇺","aliases":["luxembourg"]},{"emoji":"🤥","aliases":["lying_face"]},{"emoji":"Ⓜ️","aliases":["m"]},{"emoji":"🇲🇴","aliases":["macau"]},{"emoji":"🇲🇰","aliases":["macedonia"]},{"emoji":"🇲🇬","aliases":["madagascar"]},{"emoji":"🔍","aliases":["mag"]},{"emoji":"🔎","aliases":["mag_right"]},{"emoji":"🧙","aliases":["mage"]},{"emoji":"🧙‍♂️","aliases":["mage_man"]},{"emoji":"🧙‍♀️","aliases":["mage_woman"]},{"emoji":"🪄","aliases":["magic_wand"]},{"emoji":"🧲","aliases":["magnet"]},{"emoji":"🀄","aliases":["mahjong"]},{"emoji":"📫","aliases":["mailbox"]},{"emoji":"📪","aliases":["mailbox_closed"]},{"emoji":"📬","aliases":["mailbox_with_mail"]},{"emoji":"📭","aliases":["mailbox_with_no_mail"]},{"emoji":"🇲🇼","aliases":["malawi"]},{"emoji":"🇲🇾","aliases":["malaysia"]},{"emoji":"🇲🇻","aliases":["maldives"]},{"emoji":"🕵️‍♂️","aliases":["male_detective"]},{"emoji":"♂️","aliases":["male_sign"]},{"emoji":"🇲🇱","aliases":["mali"]},{"emoji":"🇲🇹","aliases":["malta"]},{"emoji":"🦣","aliases":["mammoth"]},{"emoji":"👨","aliases":["man"]},{"emoji":"👨‍🎨","aliases":["man_artist"]},{"emoji":"👨‍🚀","aliases":["man_astronaut"]},{"emoji":"🧔‍♂️","aliases":["man_beard"]},{"emoji":"🤸‍♂️","aliases":["man_cartwheeling"]},{"emoji":"👨‍🍳","aliases":["man_cook"]},{"emoji":"🕺","aliases":["man_dancing"]},{"emoji":"🤦‍♂️","aliases":["man_facepalming"]},{"emoji":"👨‍🏭","aliases":["man_factory_worker"]},{"emoji":"👨‍🌾","aliases":["man_farmer"]},{"emoji":"👨‍🍼","aliases":["man_feeding_baby"]},{"emoji":"👨‍🚒","aliases":["man_firefighter"]},{"emoji":"👨‍⚕️","aliases":["man_health_worker"]},{"emoji":"👨‍🦽","aliases":["man_in_manual_wheelchair"]},{"emoji":"👨‍🦼","aliases":["man_in_motorized_wheelchair"]},{"emoji":"🤵‍♂️","aliases":["man_in_tuxedo"]},{"emoji":"👨‍⚖️","aliases":["man_judge"]},{"emoji":"🤹‍♂️","aliases":["man_juggling"]},{"emoji":"👨‍🔧","aliases":["man_mechanic"]},{"emoji":"👨‍💼","aliases":["man_office_worker"]},{"emoji":"👨‍✈️","aliases":["man_pilot"]},{"emoji":"🤾‍♂️","aliases":["man_playing_handball"]},{"emoji":"🤽‍♂️","aliases":["man_playing_water_polo"]},{"emoji":"👨‍🔬","aliases":["man_scientist"]},{"emoji":"🤷‍♂️","aliases":["man_shrugging"]},{"emoji":"👨‍🎤","aliases":["man_singer"]},{"emoji":"👨‍🎓","aliases":["man_student"]},{"emoji":"👨‍🏫","aliases":["man_teacher"]},{"emoji":"👨‍💻","aliases":["man_technologist"]},{"emoji":"👲","aliases":["man_with_gua_pi_mao"]},{"emoji":"👨‍🦯","aliases":["man_with_probing_cane"]},{"emoji":"👳‍♂️","aliases":["man_with_turban"]},{"emoji":"👰‍♂️","aliases":["man_with_veil"]},{"emoji":"🥭","aliases":["mango"]},{"emoji":"👞","aliases":["mans_shoe","shoe"]},{"emoji":"🕰️","aliases":["mantelpiece_clock"]},{"emoji":"🦽","aliases":["manual_wheelchair"]},{"emoji":"🍁","aliases":["maple_leaf"]},{"emoji":"🪇","aliases":["maracas"]},{"emoji":"🇲🇭","aliases":["marshall_islands"]},{"emoji":"🥋","aliases":["martial_arts_uniform"]},{"emoji":"🇲🇶","aliases":["martinique"]},{"emoji":"😷","aliases":["mask"]},{"emoji":"💆","aliases":["massage"]},{"emoji":"💆‍♂️","aliases":["massage_man"]},{"emoji":"💆‍♀️","aliases":["massage_woman"]},{"emoji":"🧉","aliases":["mate"]},{"emoji":"🇲🇷","aliases":["mauritania"]},{"emoji":"🇲🇺","aliases":["mauritius"]},{"emoji":"🇾🇹","aliases":["mayotte"]},{"emoji":"🍖","aliases":["meat_on_bone"]},{"emoji":"🧑‍🔧","aliases":["mechanic"]},{"emoji":"🦾","aliases":["mechanical_arm"]},{"emoji":"🦿","aliases":["mechanical_leg"]},{"emoji":"🎖️","aliases":["medal_military"]},{"emoji":"🏅","aliases":["medal_sports"]},{"emoji":"⚕️","aliases":["medical_symbol"]},{"emoji":"📣","aliases":["mega"]},{"emoji":"🍈","aliases":["melon"]},{"emoji":"🫠","aliases":["melting_face"]},{"emoji":"📝","aliases":["memo","pencil"]},{"emoji":"🤼‍♂️","aliases":["men_wrestling"]},{"emoji":"❤️‍🩹","aliases":["mending_heart"]},{"emoji":"🕎","aliases":["menorah"]},{"emoji":"🚹","aliases":["mens"]},{"emoji":"🧜‍♀️","aliases":["mermaid"]},{"emoji":"🧜‍♂️","aliases":["merman"]},{"emoji":"🧜","aliases":["merperson"]},{"emoji":"🤘","aliases":["metal"]},{"emoji":"🚇","aliases":["metro"]},{"emoji":"🇲🇽","aliases":["mexico"]},{"emoji":"🦠","aliases":["microbe"]},{"emoji":"🇫🇲","aliases":["micronesia"]},{"emoji":"🎤","aliases":["microphone"]},{"emoji":"🔬","aliases":["microscope"]},{"emoji":"🖕","aliases":["middle_finger","fu"]},{"emoji":"🪖","aliases":["military_helmet"]},{"emoji":"🥛","aliases":["milk_glass"]},{"emoji":"🌌","aliases":["milky_way"]},{"emoji":"🚐","aliases":["minibus"]},{"emoji":"💽","aliases":["minidisc"]},{"emoji":"🪞","aliases":["mirror"]},{"emoji":"🪩","aliases":["mirror_ball"]},{"emoji":"📴","aliases":["mobile_phone_off"]},{"emoji":"🇲🇩","aliases":["moldova"]},{"emoji":"🇲🇨","aliases":["monaco"]},{"emoji":"🤑","aliases":["money_mouth_face"]},{"emoji":"💸","aliases":["money_with_wings"]},{"emoji":"💰","aliases":["moneybag"]},{"emoji":"🇲🇳","aliases":["mongolia"]},{"emoji":"🐒","aliases":["monkey"]},{"emoji":"🐵","aliases":["monkey_face"]},{"emoji":"🧐","aliases":["monocle_face"]},{"emoji":"🚝","aliases":["monorail"]},{"emoji":"🇲🇪","aliases":["montenegro"]},{"emoji":"🇲🇸","aliases":["montserrat"]},{"emoji":"🌔","aliases":["moon","waxing_gibbous_moon"]},{"emoji":"🥮","aliases":["moon_cake"]},{"emoji":"🫎","aliases":["moose"]},{"emoji":"🇲🇦","aliases":["morocco"]},{"emoji":"🎓","aliases":["mortar_board"]},{"emoji":"🕌","aliases":["mosque"]},{"emoji":"🦟","aliases":["mosquito"]},{"emoji":"🛥️","aliases":["motor_boat"]},{"emoji":"🛵","aliases":["motor_scooter"]},{"emoji":"🏍️","aliases":["motorcycle"]},{"emoji":"🦼","aliases":["motorized_wheelchair"]},{"emoji":"🛣️","aliases":["motorway"]},{"emoji":"🗻","aliases":["mount_fuji"]},{"emoji":"⛰️","aliases":["mountain"]},{"emoji":"🚵","aliases":["mountain_bicyclist"]},{"emoji":"🚵‍♂️","aliases":["mountain_biking_man"]},{"emoji":"🚵‍♀️","aliases":["mountain_biking_woman"]},{"emoji":"🚠","aliases":["mountain_cableway"]},{"emoji":"🚞","aliases":["mountain_railway"]},{"emoji":"🏔️","aliases":["mountain_snow"]},{"emoji":"🐭","aliases":["mouse"]},{"emoji":"🐁","aliases":["mouse2"]},{"emoji":"🪤","aliases":["mouse_trap"]},{"emoji":"🎥","aliases":["movie_camera"]},{"emoji":"🗿","aliases":["moyai"]},{"emoji":"🇲🇿","aliases":["mozambique"]},{"emoji":"🤶","aliases":["mrs_claus"]},{"emoji":"💪","aliases":["muscle"]},{"emoji":"🍄","aliases":["mushroom"]},{"emoji":"🎹","aliases":["musical_keyboard"]},{"emoji":"🎵","aliases":["musical_note"]},{"emoji":"🎼","aliases":["musical_score"]},{"emoji":"🔇","aliases":["mute"]},{"emoji":"🧑‍🎄","aliases":["mx_claus"]},{"emoji":"🇲🇲","aliases":["myanmar"]},{"emoji":"💅","aliases":["nail_care"]},{"emoji":"📛","aliases":["name_badge"]},{"emoji":"🇳🇦","aliases":["namibia"]},{"emoji":"🏞️","aliases":["national_park"]},{"emoji":"🇳🇷","aliases":["nauru"]},{"emoji":"🤢","aliases":["nauseated_face"]},{"emoji":"🧿","aliases":["nazar_amulet"]},{"emoji":"👔","aliases":["necktie"]},{"emoji":"❎","aliases":["negative_squared_cross_mark"]},{"emoji":"🇳🇵","aliases":["nepal"]},{"emoji":"🤓","aliases":["nerd_face"]},{"emoji":"🪺","aliases":["nest_with_eggs"]},{"emoji":"🪆","aliases":["nesting_dolls"]},{"emoji":"🇳🇱","aliases":["netherlands"]},{"emoji":"😐","aliases":["neutral_face"]},{"emoji":"🆕","aliases":["new"]},{"emoji":"🇳🇨","aliases":["new_caledonia"]},{"emoji":"🌑","aliases":["new_moon"]},{"emoji":"🌚","aliases":["new_moon_with_face"]},{"emoji":"🇳🇿","aliases":["new_zealand"]},{"emoji":"📰","aliases":["newspaper"]},{"emoji":"🗞️","aliases":["newspaper_roll"]},{"emoji":"⏭️","aliases":["next_track_button"]},{"emoji":"🆖","aliases":["ng"]},{"emoji":"🇳🇮","aliases":["nicaragua"]},{"emoji":"🇳🇪","aliases":["niger"]},{"emoji":"🇳🇬","aliases":["nigeria"]},{"emoji":"🌃","aliases":["night_with_stars"]},{"emoji":"9️⃣","aliases":["nine"]},{"emoji":"🥷","aliases":["ninja"]},{"emoji":"🇳🇺","aliases":["niue"]},{"emoji":"🔕","aliases":["no_bell"]},{"emoji":"🚳","aliases":["no_bicycles"]},{"emoji":"⛔","aliases":["no_entry"]},{"emoji":"🚫","aliases":["no_entry_sign"]},{"emoji":"🙅","aliases":["no_good"]},{"emoji":"🙅‍♂️","aliases":["no_good_man","ng_man"]},{"emoji":"🙅‍♀️","aliases":["no_good_woman","ng_woman"]},{"emoji":"📵","aliases":["no_mobile_phones"]},{"emoji":"😶","aliases":["no_mouth"]},{"emoji":"🚷","aliases":["no_pedestrians"]},{"emoji":"🚭","aliases":["no_smoking"]},{"emoji":"🚱","aliases":["non-potable_water"]},{"emoji":"🇳🇫","aliases":["norfolk_island"]},{"emoji":"🇰🇵","aliases":["north_korea"]},{"emoji":"🇲🇵","aliases":["northern_mariana_islands"]},{"emoji":"🇳🇴","aliases":["norway"]},{"emoji":"👃","aliases":["nose"]},{"emoji":"📓","aliases":["notebook"]},{"emoji":"📔","aliases":["notebook_with_decorative_cover"]},{"emoji":"🎶","aliases":["notes"]},{"emoji":"🔩","aliases":["nut_and_bolt"]},{"emoji":"⭕","aliases":["o"]},{"emoji":"🅾️","aliases":["o2"]},{"emoji":"🌊","aliases":["ocean"]},{"emoji":"🐙","aliases":["octopus"]},{"emoji":"🍢","aliases":["oden"]},{"emoji":"🏢","aliases":["office"]},{"emoji":"🧑‍💼","aliases":["office_worker"]},{"emoji":"🛢️","aliases":["oil_drum"]},{"emoji":"🆗","aliases":["ok"]},{"emoji":"👌","aliases":["ok_hand"]},{"emoji":"🙆‍♂️","aliases":["ok_man"]},{"emoji":"🙆","aliases":["ok_person"]},{"emoji":"🙆‍♀️","aliases":["ok_woman"]},{"emoji":"🗝️","aliases":["old_key"]},{"emoji":"🧓","aliases":["older_adult"]},{"emoji":"👴","aliases":["older_man"]},{"emoji":"👵","aliases":["older_woman"]},{"emoji":"🫒","aliases":["olive"]},{"emoji":"🕉️","aliases":["om"]},{"emoji":"🇴🇲","aliases":["oman"]},{"emoji":"🔛","aliases":["on"]},{"emoji":"🚘","aliases":["oncoming_automobile"]},{"emoji":"🚍","aliases":["oncoming_bus"]},{"emoji":"🚔","aliases":["oncoming_police_car"]},{"emoji":"🚖","aliases":["oncoming_taxi"]},{"emoji":"1️⃣","aliases":["one"]},{"emoji":"🩱","aliases":["one_piece_swimsuit"]},{"emoji":"🧅","aliases":["onion"]},{"emoji":"📂","aliases":["open_file_folder"]},{"emoji":"👐","aliases":["open_hands"]},{"emoji":"😮","aliases":["open_mouth"]},{"emoji":"☂️","aliases":["open_umbrella"]},{"emoji":"⛎","aliases":["ophiuchus"]},{"emoji":"📙","aliases":["orange_book"]},{"emoji":"🟠","aliases":["orange_circle"]},{"emoji":"🧡","aliases":["orange_heart"]},{"emoji":"🟧","aliases":["orange_square"]},{"emoji":"🦧","aliases":["orangutan"]},{"emoji":"☦️","aliases":["orthodox_cross"]},{"emoji":"🦦","aliases":["otter"]},{"emoji":"📤","aliases":["outbox_tray"]},{"emoji":"🦉","aliases":["owl"]},{"emoji":"🐂","aliases":["ox"]},{"emoji":"🦪","aliases":["oyster"]},{"emoji":"📦","aliases":["package"]},{"emoji":"📄","aliases":["page_facing_up"]},{"emoji":"📃","aliases":["page_with_curl"]},{"emoji":"📟","aliases":["pager"]},{"emoji":"🖌️","aliases":["paintbrush"]},{"emoji":"🇵🇰","aliases":["pakistan"]},{"emoji":"🇵🇼","aliases":["palau"]},{"emoji":"🇵🇸","aliases":["palestinian_territories"]},{"emoji":"🫳","aliases":["palm_down_hand"]},{"emoji":"🌴","aliases":["palm_tree"]},{"emoji":"🫴","aliases":["palm_up_hand"]},{"emoji":"🤲","aliases":["palms_up_together"]},{"emoji":"🇵🇦","aliases":["panama"]},{"emoji":"🥞","aliases":["pancakes"]},{"emoji":"🐼","aliases":["panda_face"]},{"emoji":"📎","aliases":["paperclip"]},{"emoji":"🖇️","aliases":["paperclips"]},{"emoji":"🇵🇬","aliases":["papua_new_guinea"]},{"emoji":"🪂","aliases":["parachute"]},{"emoji":"🇵🇾","aliases":["paraguay"]},{"emoji":"⛱️","aliases":["parasol_on_ground"]},{"emoji":"🅿️","aliases":["parking"]},{"emoji":"🦜","aliases":["parrot"]},{"emoji":"〽️","aliases":["part_alternation_mark"]},{"emoji":"⛅","aliases":["partly_sunny"]},{"emoji":"🥳","aliases":["partying_face"]},{"emoji":"🛳️","aliases":["passenger_ship"]},{"emoji":"🛂","aliases":["passport_control"]},{"emoji":"⏸️","aliases":["pause_button"]},{"emoji":"🫛","aliases":["pea_pod"]},{"emoji":"☮️","aliases":["peace_symbol"]},{"emoji":"🍑","aliases":["peach"]},{"emoji":"🦚","aliases":["peacock"]},{"emoji":"🥜","aliases":["peanuts"]},{"emoji":"🍐","aliases":["pear"]},{"emoji":"🖊️","aliases":["pen"]},{"emoji":"✏️","aliases":["pencil2"]},{"emoji":"🐧","aliases":["penguin"]},{"emoji":"😔","aliases":["pensive"]},{"emoji":"🧑‍🤝‍🧑","aliases":["people_holding_hands"]},{"emoji":"🫂","aliases":["people_hugging"]},{"emoji":"🎭","aliases":["performing_arts"]},{"emoji":"😣","aliases":["persevere"]},{"emoji":"🧑‍🦲","aliases":["person_bald"]},{"emoji":"🧑‍🦱","aliases":["person_curly_hair"]},{"emoji":"🧑‍🍼","aliases":["person_feeding_baby"]},{"emoji":"🤺","aliases":["person_fencing"]},{"emoji":"🧑‍🦽","aliases":["person_in_manual_wheelchair"]},{"emoji":"🧑‍🦼","aliases":["person_in_motorized_wheelchair"]},{"emoji":"🤵","aliases":["person_in_tuxedo"]},{"emoji":"🧑‍🦰","aliases":["person_red_hair"]},{"emoji":"🧑‍🦳","aliases":["person_white_hair"]},{"emoji":"🫅","aliases":["person_with_crown"]},{"emoji":"🧑‍🦯","aliases":["person_with_probing_cane"]},{"emoji":"👳","aliases":["person_with_turban"]},{"emoji":"👰","aliases":["person_with_veil"]},{"emoji":"🇵🇪","aliases":["peru"]},{"emoji":"🧫","aliases":["petri_dish"]},{"emoji":"🇵🇭","aliases":["philippines"]},{"emoji":"☎️","aliases":["phone","telephone"]},{"emoji":"⛏️","aliases":["pick"]},{"emoji":"🛻","aliases":["pickup_truck"]},{"emoji":"🥧","aliases":["pie"]},{"emoji":"🐷","aliases":["pig"]},{"emoji":"🐖","aliases":["pig2"]},{"emoji":"🐽","aliases":["pig_nose"]},{"emoji":"💊","aliases":["pill"]},{"emoji":"🧑‍✈️","aliases":["pilot"]},{"emoji":"🪅","aliases":["pinata"]},{"emoji":"🤌","aliases":["pinched_fingers"]},{"emoji":"🤏","aliases":["pinching_hand"]},{"emoji":"🍍","aliases":["pineapple"]},{"emoji":"🏓","aliases":["ping_pong"]},{"emoji":"🩷","aliases":["pink_heart"]},{"emoji":"🏴‍☠️","aliases":["pirate_flag"]},{"emoji":"♓","aliases":["pisces"]},{"emoji":"🇵🇳","aliases":["pitcairn_islands"]},{"emoji":"🍕","aliases":["pizza"]},{"emoji":"🪧","aliases":["placard"]},{"emoji":"🛐","aliases":["place_of_worship"]},{"emoji":"🍽️","aliases":["plate_with_cutlery"]},{"emoji":"⏯️","aliases":["play_or_pause_button"]},{"emoji":"🛝","aliases":["playground_slide"]},{"emoji":"🥺","aliases":["pleading_face"]},{"emoji":"🪠","aliases":["plunger"]},{"emoji":"👇","aliases":["point_down"]},{"emoji":"👈","aliases":["point_left"]},{"emoji":"👉","aliases":["point_right"]},{"emoji":"☝️","aliases":["point_up"]},{"emoji":"👆","aliases":["point_up_2"]},{"emoji":"🇵🇱","aliases":["poland"]},{"emoji":"🐻‍❄️","aliases":["polar_bear"]},{"emoji":"🚓","aliases":["police_car"]},{"emoji":"👮","aliases":["police_officer","cop"]},{"emoji":"👮‍♂️","aliases":["policeman"]},{"emoji":"👮‍♀️","aliases":["policewoman"]},{"emoji":"🐩","aliases":["poodle"]},{"emoji":"🍿","aliases":["popcorn"]},{"emoji":"🇵🇹","aliases":["portugal"]},{"emoji":"🏣","aliases":["post_office"]},{"emoji":"📯","aliases":["postal_horn"]},{"emoji":"📮","aliases":["postbox"]},{"emoji":"🚰","aliases":["potable_water"]},{"emoji":"🥔","aliases":["potato"]},{"emoji":"🪴","aliases":["potted_plant"]},{"emoji":"👝","aliases":["pouch"]},{"emoji":"🍗","aliases":["poultry_leg"]},{"emoji":"💷","aliases":["pound"]},{"emoji":"🫗","aliases":["pouring_liquid"]},{"emoji":"😾","aliases":["pouting_cat"]},{"emoji":"🙎","aliases":["pouting_face"]},{"emoji":"🙎‍♂️","aliases":["pouting_man"]},{"emoji":"🙎‍♀️","aliases":["pouting_woman"]},{"emoji":"🙏","aliases":["pray"]},{"emoji":"📿","aliases":["prayer_beads"]},{"emoji":"🫃","aliases":["pregnant_man"]},{"emoji":"🫄","aliases":["pregnant_person"]},{"emoji":"🤰","aliases":["pregnant_woman"]},{"emoji":"🥨","aliases":["pretzel"]},{"emoji":"⏮️","aliases":["previous_track_button"]},{"emoji":"🤴","aliases":["prince"]},{"emoji":"👸","aliases":["princess"]},{"emoji":"🖨️","aliases":["printer"]},{"emoji":"🦯","aliases":["probing_cane"]},{"emoji":"🇵🇷","aliases":["puerto_rico"]},{"emoji":"🟣","aliases":["purple_circle"]},{"emoji":"💜","aliases":["purple_heart"]},{"emoji":"🟪","aliases":["purple_square"]},{"emoji":"👛","aliases":["purse"]},{"emoji":"📌","aliases":["pushpin"]},{"emoji":"🚮","aliases":["put_litter_in_its_place"]},{"emoji":"🇶🇦","aliases":["qatar"]},{"emoji":"❓","aliases":["question"]},{"emoji":"🐰","aliases":["rabbit"]},{"emoji":"🐇","aliases":["rabbit2"]},{"emoji":"🦝","aliases":["raccoon"]},{"emoji":"🐎","aliases":["racehorse"]},{"emoji":"🏎️","aliases":["racing_car"]},{"emoji":"📻","aliases":["radio"]},{"emoji":"🔘","aliases":["radio_button"]},{"emoji":"☢️","aliases":["radioactive"]},{"emoji":"😡","aliases":["rage","pout"]},{"emoji":"🚃","aliases":["railway_car"]},{"emoji":"🛤️","aliases":["railway_track"]},{"emoji":"🌈","aliases":["rainbow"]},{"emoji":"🏳️‍🌈","aliases":["rainbow_flag"]},{"emoji":"🤚","aliases":["raised_back_of_hand"]},{"emoji":"🤨","aliases":["raised_eyebrow"]},{"emoji":"🖐️","aliases":["raised_hand_with_fingers_splayed"]},{"emoji":"🙌","aliases":["raised_hands"]},{"emoji":"🙋","aliases":["raising_hand"]},{"emoji":"🙋‍♂️","aliases":["raising_hand_man"]},{"emoji":"🙋‍♀️","aliases":["raising_hand_woman"]},{"emoji":"🐏","aliases":["ram"]},{"emoji":"🍜","aliases":["ramen"]},{"emoji":"🐀","aliases":["rat"]},{"emoji":"🪒","aliases":["razor"]},{"emoji":"🧾","aliases":["receipt"]},{"emoji":"⏺️","aliases":["record_button"]},{"emoji":"♻️","aliases":["recycle"]},{"emoji":"🔴","aliases":["red_circle"]},{"emoji":"🧧","aliases":["red_envelope"]},{"emoji":"👨‍🦰","aliases":["red_haired_man"]},{"emoji":"👩‍🦰","aliases":["red_haired_woman"]},{"emoji":"🟥","aliases":["red_square"]},{"emoji":"®️","aliases":["registered"]},{"emoji":"☺️","aliases":["relaxed"]},{"emoji":"😌","aliases":["relieved"]},{"emoji":"🎗️","aliases":["reminder_ribbon"]},{"emoji":"🔁","aliases":["repeat"]},{"emoji":"🔂","aliases":["repeat_one"]},{"emoji":"⛑️","aliases":["rescue_worker_helmet"]},{"emoji":"🚻","aliases":["restroom"]},{"emoji":"🇷🇪","aliases":["reunion"]},{"emoji":"💞","aliases":["revolving_hearts"]},{"emoji":"⏪","aliases":["rewind"]},{"emoji":"🦏","aliases":["rhinoceros"]},{"emoji":"🎀","aliases":["ribbon"]},{"emoji":"🍚","aliases":["rice"]},{"emoji":"🍙","aliases":["rice_ball"]},{"emoji":"🍘","aliases":["rice_cracker"]},{"emoji":"🎑","aliases":["rice_scene"]},{"emoji":"🗯️","aliases":["right_anger_bubble"]},{"emoji":"🫱","aliases":["rightwards_hand"]},{"emoji":"🫸","aliases":["rightwards_pushing_hand"]},{"emoji":"💍","aliases":["ring"]},{"emoji":"🛟","aliases":["ring_buoy"]},{"emoji":"🪐","aliases":["ringed_planet"]},{"emoji":"🤖","aliases":["robot"]},{"emoji":"🪨","aliases":["rock"]},{"emoji":"🚀","aliases":["rocket"]},{"emoji":"🤣","aliases":["rofl"]},{"emoji":"🙄","aliases":["roll_eyes"]},{"emoji":"🧻","aliases":["roll_of_paper"]},{"emoji":"🎢","aliases":["roller_coaster"]},{"emoji":"🛼","aliases":["roller_skate"]},{"emoji":"🇷🇴","aliases":["romania"]},{"emoji":"🐓","aliases":["rooster"]},{"emoji":"🌹","aliases":["rose"]},{"emoji":"🏵️","aliases":["rosette"]},{"emoji":"🚨","aliases":["rotating_light"]},{"emoji":"📍","aliases":["round_pushpin"]},{"emoji":"🚣","aliases":["rowboat"]},{"emoji":"🚣‍♂️","aliases":["rowing_man"]},{"emoji":"🚣‍♀️","aliases":["rowing_woman"]},{"emoji":"🇷🇺","aliases":["ru"]},{"emoji":"🏉","aliases":["rugby_football"]},{"emoji":"🏃","aliases":["runner","running"]},{"emoji":"🏃‍♂️","aliases":["running_man"]},{"emoji":"🎽","aliases":["running_shirt_with_sash"]},{"emoji":"🏃‍♀️","aliases":["running_woman"]},{"emoji":"🇷🇼","aliases":["rwanda"]},{"emoji":"🈂️","aliases":["sa"]},{"emoji":"🧷","aliases":["safety_pin"]},{"emoji":"🦺","aliases":["safety_vest"]},{"emoji":"♐","aliases":["sagittarius"]},{"emoji":"🍶","aliases":["sake"]},{"emoji":"🧂","aliases":["salt"]},{"emoji":"🫡","aliases":["saluting_face"]},{"emoji":"🇼🇸","aliases":["samoa"]},{"emoji":"🇸🇲","aliases":["san_marino"]},{"emoji":"👡","aliases":["sandal"]},{"emoji":"🥪","aliases":["sandwich"]},{"emoji":"🎅","aliases":["santa"]},{"emoji":"🇸🇹","aliases":["sao_tome_principe"]},{"emoji":"🥻","aliases":["sari"]},{"emoji":"📡","aliases":["satellite"]},{"emoji":"🇸🇦","aliases":["saudi_arabia"]},{"emoji":"🧖‍♂️","aliases":["sauna_man"]},{"emoji":"🧖","aliases":["sauna_person"]},{"emoji":"🧖‍♀️","aliases":["sauna_woman"]},{"emoji":"🦕","aliases":["sauropod"]},{"emoji":"🎷","aliases":["saxophone"]},{"emoji":"🧣","aliases":["scarf"]},{"emoji":"🏫","aliases":["school"]},{"emoji":"🎒","aliases":["school_satchel"]},{"emoji":"🧑‍🔬","aliases":["scientist"]},{"emoji":"✂️","aliases":["scissors"]},{"emoji":"🦂","aliases":["scorpion"]},{"emoji":"♏","aliases":["scorpius"]},{"emoji":"🏴󠁧󠁢󠁳󠁣󠁴󠁿","aliases":["scotland"]},{"emoji":"😱","aliases":["scream"]},{"emoji":"🙀","aliases":["scream_cat"]},{"emoji":"🪛","aliases":["screwdriver"]},{"emoji":"📜","aliases":["scroll"]},{"emoji":"🦭","aliases":["seal"]},{"emoji":"💺","aliases":["seat"]},{"emoji":"㊙️","aliases":["secret"]},{"emoji":"🙈","aliases":["see_no_evil"]},{"emoji":"🌱","aliases":["seedling"]},{"emoji":"🤳","aliases":["selfie"]},{"emoji":"🇸🇳","aliases":["senegal"]},{"emoji":"🇷🇸","aliases":["serbia"]},{"emoji":"🐕‍🦺","aliases":["service_dog"]},{"emoji":"7️⃣","aliases":["seven"]},{"emoji":"🪡","aliases":["sewing_needle"]},{"emoji":"🇸🇨","aliases":["seychelles"]},{"emoji":"🫨","aliases":["shaking_face"]},{"emoji":"🥘","aliases":["shallow_pan_of_food"]},{"emoji":"☘️","aliases":["shamrock"]},{"emoji":"🦈","aliases":["shark"]},{"emoji":"🍧","aliases":["shaved_ice"]},{"emoji":"🐑","aliases":["sheep"]},{"emoji":"🐚","aliases":["shell"]},{"emoji":"🛡️","aliases":["shield"]},{"emoji":"⛩️","aliases":["shinto_shrine"]},{"emoji":"🚢","aliases":["ship"]},{"emoji":"👕","aliases":["shirt","tshirt"]},{"emoji":"🛍️","aliases":["shopping"]},{"emoji":"🛒","aliases":["shopping_cart"]},{"emoji":"🩳","aliases":["shorts"]},{"emoji":"🚿","aliases":["shower"]},{"emoji":"🦐","aliases":["shrimp"]},{"emoji":"🤷","aliases":["shrug"]},{"emoji":"🤫","aliases":["shushing_face"]},{"emoji":"🇸🇱","aliases":["sierra_leone"]},{"emoji":"📶","aliases":["signal_strength"]},{"emoji":"🇸🇬","aliases":["singapore"]},{"emoji":"🧑‍🎤","aliases":["singer"]},{"emoji":"🇸🇽","aliases":["sint_maarten"]},{"emoji":"6️⃣","aliases":["six"]},{"emoji":"🔯","aliases":["six_pointed_star"]},{"emoji":"🛹","aliases":["skateboard"]},{"emoji":"🎿","aliases":["ski"]},{"emoji":"⛷️","aliases":["skier"]},{"emoji":"💀","aliases":["skull"]},{"emoji":"☠️","aliases":["skull_and_crossbones"]},{"emoji":"🦨","aliases":["skunk"]},{"emoji":"🛷","aliases":["sled"]},{"emoji":"😴","aliases":["sleeping"]},{"emoji":"🛌","aliases":["sleeping_bed"]},{"emoji":"😪","aliases":["sleepy"]},{"emoji":"🙁","aliases":["slightly_frowning_face"]},{"emoji":"🙂","aliases":["slightly_smiling_face"]},{"emoji":"🎰","aliases":["slot_machine"]},{"emoji":"🦥","aliases":["sloth"]},{"emoji":"🇸🇰","aliases":["slovakia"]},{"emoji":"🇸🇮","aliases":["slovenia"]},{"emoji":"🛩️","aliases":["small_airplane"]},{"emoji":"🔹","aliases":["small_blue_diamond"]},{"emoji":"🔸","aliases":["small_orange_diamond"]},{"emoji":"🔺","aliases":["small_red_triangle"]},{"emoji":"🔻","aliases":["small_red_triangle_down"]},{"emoji":"😄","aliases":["smile"]},{"emoji":"😸","aliases":["smile_cat"]},{"emoji":"😃","aliases":["smiley"]},{"emoji":"😺","aliases":["smiley_cat"]},{"emoji":"🥲","aliases":["smiling_face_with_tear"]},{"emoji":"🥰","aliases":["smiling_face_with_three_hearts"]},{"emoji":"😈","aliases":["smiling_imp"]},{"emoji":"😏","aliases":["smirk"]},{"emoji":"😼","aliases":["smirk_cat"]},{"emoji":"🚬","aliases":["smoking"]},{"emoji":"🐌","aliases":["snail"]},{"emoji":"🐍","aliases":["snake"]},{"emoji":"🤧","aliases":["sneezing_face"]},{"emoji":"🏂","aliases":["snowboarder"]},{"emoji":"❄️","aliases":["snowflake"]},{"emoji":"⛄","aliases":["snowman"]},{"emoji":"☃️","aliases":["snowman_with_snow"]},{"emoji":"🧼","aliases":["soap"]},{"emoji":"😭","aliases":["sob"]},{"emoji":"⚽","aliases":["soccer"]},{"emoji":"🧦","aliases":["socks"]},{"emoji":"🥎","aliases":["softball"]},{"emoji":"🇸🇧","aliases":["solomon_islands"]},{"emoji":"🇸🇴","aliases":["somalia"]},{"emoji":"🔜","aliases":["soon"]},{"emoji":"🆘","aliases":["sos"]},{"emoji":"🔉","aliases":["sound"]},{"emoji":"🇿🇦","aliases":["south_africa"]},{"emoji":"🇬🇸","aliases":["south_georgia_south_sandwich_islands"]},{"emoji":"🇸🇸","aliases":["south_sudan"]},{"emoji":"👾","aliases":["space_invader"]},{"emoji":"♠️","aliases":["spades"]},{"emoji":"🍝","aliases":["spaghetti"]},{"emoji":"❇️","aliases":["sparkle"]},{"emoji":"🎇","aliases":["sparkler"]},{"emoji":"✨","aliases":["sparkles"]},{"emoji":"💖","aliases":["sparkling_heart"]},{"emoji":"🙊","aliases":["speak_no_evil"]},{"emoji":"🔈","aliases":["speaker"]},{"emoji":"🗣️","aliases":["speaking_head"]},{"emoji":"💬","aliases":["speech_balloon"]},{"emoji":"🚤","aliases":["speedboat"]},{"emoji":"🕷️","aliases":["spider"]},{"emoji":"🕸️","aliases":["spider_web"]},{"emoji":"🗓️","aliases":["spiral_calendar"]},{"emoji":"🗒️","aliases":["spiral_notepad"]},{"emoji":"🧽","aliases":["sponge"]},{"emoji":"🥄","aliases":["spoon"]},{"emoji":"🦑","aliases":["squid"]},{"emoji":"🇱🇰","aliases":["sri_lanka"]},{"emoji":"🇧🇱","aliases":["st_barthelemy"]},{"emoji":"🇸🇭","aliases":["st_helena"]},{"emoji":"🇰🇳","aliases":["st_kitts_nevis"]},{"emoji":"🇱🇨","aliases":["st_lucia"]},{"emoji":"🇲🇫","aliases":["st_martin"]},{"emoji":"🇵🇲","aliases":["st_pierre_miquelon"]},{"emoji":"🇻🇨","aliases":["st_vincent_grenadines"]},{"emoji":"🏟️","aliases":["stadium"]},{"emoji":"🧍‍♂️","aliases":["standing_man"]},{"emoji":"🧍","aliases":["standing_person"]},{"emoji":"🧍‍♀️","aliases":["standing_woman"]},{"emoji":"⭐","aliases":["star"]},{"emoji":"🌟","aliases":["star2"]},{"emoji":"☪️","aliases":["star_and_crescent"]},{"emoji":"✡️","aliases":["star_of_david"]},{"emoji":"🤩","aliases":["star_struck"]},{"emoji":"🌠","aliases":["stars"]},{"emoji":"🚉","aliases":["station"]},{"emoji":"🗽","aliases":["statue_of_liberty"]},{"emoji":"🚂","aliases":["steam_locomotive"]},{"emoji":"🩺","aliases":["stethoscope"]},{"emoji":"🍲","aliases":["stew"]},{"emoji":"⏹️","aliases":["stop_button"]},{"emoji":"🛑","aliases":["stop_sign"]},{"emoji":"⏱️","aliases":["stopwatch"]},{"emoji":"📏","aliases":["straight_ruler"]},{"emoji":"🍓","aliases":["strawberry"]},{"emoji":"😛","aliases":["stuck_out_tongue"]},{"emoji":"😝","aliases":["stuck_out_tongue_closed_eyes"]},{"emoji":"😜","aliases":["stuck_out_tongue_winking_eye"]},{"emoji":"🧑‍🎓","aliases":["student"]},{"emoji":"🎙️","aliases":["studio_microphone"]},{"emoji":"🥙","aliases":["stuffed_flatbread"]},{"emoji":"🇸🇩","aliases":["sudan"]},{"emoji":"🌥️","aliases":["sun_behind_large_cloud"]},{"emoji":"🌦️","aliases":["sun_behind_rain_cloud"]},{"emoji":"🌤️","aliases":["sun_behind_small_cloud"]},{"emoji":"🌞","aliases":["sun_with_face"]},{"emoji":"🌻","aliases":["sunflower"]},{"emoji":"😎","aliases":["sunglasses"]},{"emoji":"☀️","aliases":["sunny"]},{"emoji":"🌅","aliases":["sunrise"]},{"emoji":"🌄","aliases":["sunrise_over_mountains"]},{"emoji":"🦸","aliases":["superhero"]},{"emoji":"🦸‍♂️","aliases":["superhero_man"]},{"emoji":"🦸‍♀️","aliases":["superhero_woman"]},{"emoji":"🦹","aliases":["supervillain"]},{"emoji":"🦹‍♂️","aliases":["supervillain_man"]},{"emoji":"🦹‍♀️","aliases":["supervillain_woman"]},{"emoji":"🏄","aliases":["surfer"]},{"emoji":"🏄‍♂️","aliases":["surfing_man"]},{"emoji":"🏄‍♀️","aliases":["surfing_woman"]},{"emoji":"🇸🇷","aliases":["suriname"]},{"emoji":"🍣","aliases":["sushi"]},{"emoji":"🚟","aliases":["suspension_railway"]},{"emoji":"🇸🇯","aliases":["svalbard_jan_mayen"]},{"emoji":"🦢","aliases":["swan"]},{"emoji":"🇸🇿","aliases":["swaziland"]},{"emoji":"😓","aliases":["sweat"]},{"emoji":"💦","aliases":["sweat_drops"]},{"emoji":"😅","aliases":["sweat_smile"]},{"emoji":"🇸🇪","aliases":["sweden"]},{"emoji":"🍠","aliases":["sweet_potato"]},{"emoji":"🩲","aliases":["swim_brief"]},{"emoji":"🏊","aliases":["swimmer"]},{"emoji":"🏊‍♂️","aliases":["swimming_man"]},{"emoji":"🏊‍♀️","aliases":["swimming_woman"]},{"emoji":"🇨🇭","aliases":["switzerland"]},{"emoji":"🔣","aliases":["symbols"]},{"emoji":"🕍","aliases":["synagogue"]},{"emoji":"🇸🇾","aliases":["syria"]},{"emoji":"💉","aliases":["syringe"]},{"emoji":"🦖","aliases":["t-rex"]},{"emoji":"🌮","aliases":["taco"]},{"emoji":"🎉","aliases":["tada","hooray"]},{"emoji":"🇹🇼","aliases":["taiwan"]},{"emoji":"🇹🇯","aliases":["tajikistan"]},{"emoji":"🥡","aliases":["takeout_box"]},{"emoji":"🫔","aliases":["tamale"]},{"emoji":"🎋","aliases":["tanabata_tree"]},{"emoji":"🍊","aliases":["tangerine","orange","mandarin"]},{"emoji":"🇹🇿","aliases":["tanzania"]},{"emoji":"♉","aliases":["taurus"]},{"emoji":"🚕","aliases":["taxi"]},{"emoji":"🍵","aliases":["tea"]},{"emoji":"🧑‍🏫","aliases":["teacher"]},{"emoji":"🫖","aliases":["teapot"]},{"emoji":"🧑‍💻","aliases":["technologist"]},{"emoji":"🧸","aliases":["teddy_bear"]},{"emoji":"📞","aliases":["telephone_receiver"]},{"emoji":"🔭","aliases":["telescope"]},{"emoji":"🎾","aliases":["tennis"]},{"emoji":"⛺","aliases":["tent"]},{"emoji":"🧪","aliases":["test_tube"]},{"emoji":"🇹🇭","aliases":["thailand"]},{"emoji":"🌡️","aliases":["thermometer"]},{"emoji":"🤔","aliases":["thinking"]},{"emoji":"🩴","aliases":["thong_sandal"]},{"emoji":"💭","aliases":["thought_balloon"]},{"emoji":"🧵","aliases":["thread"]},{"emoji":"3️⃣","aliases":["three"]},{"emoji":"🎫","aliases":["ticket"]},{"emoji":"🎟️","aliases":["tickets"]},{"emoji":"🐯","aliases":["tiger"]},{"emoji":"🐅","aliases":["tiger2"]},{"emoji":"⏲️","aliases":["timer_clock"]},{"emoji":"🇹🇱","aliases":["timor_leste"]},{"emoji":"💁‍♂️","aliases":["tipping_hand_man","sassy_man"]},{"emoji":"💁","aliases":["tipping_hand_person","information_desk_person"]},{"emoji":"💁‍♀️","aliases":["tipping_hand_woman","sassy_woman"]},{"emoji":"😫","aliases":["tired_face"]},{"emoji":"™️","aliases":["tm"]},{"emoji":"🇹🇬","aliases":["togo"]},{"emoji":"🚽","aliases":["toilet"]},{"emoji":"🇹🇰","aliases":["tokelau"]},{"emoji":"🗼","aliases":["tokyo_tower"]},{"emoji":"🍅","aliases":["tomato"]},{"emoji":"🇹🇴","aliases":["tonga"]},{"emoji":"👅","aliases":["tongue"]},{"emoji":"🧰","aliases":["toolbox"]},{"emoji":"🦷","aliases":["tooth"]},{"emoji":"🪥","aliases":["toothbrush"]},{"emoji":"🔝","aliases":["top"]},{"emoji":"🎩","aliases":["tophat"]},{"emoji":"🌪️","aliases":["tornado"]},{"emoji":"🇹🇷","aliases":["tr"]},{"emoji":"🖲️","aliases":["trackball"]},{"emoji":"🚜","aliases":["tractor"]},{"emoji":"🚥","aliases":["traffic_light"]},{"emoji":"🚋","aliases":["train"]},{"emoji":"🚆","aliases":["train2"]},{"emoji":"🚊","aliases":["tram"]},{"emoji":"🏳️‍⚧️","aliases":["transgender_flag"]},{"emoji":"⚧️","aliases":["transgender_symbol"]},{"emoji":"🚩","aliases":["triangular_flag_on_post"]},{"emoji":"📐","aliases":["triangular_ruler"]},{"emoji":"🔱","aliases":["trident"]},{"emoji":"🇹🇹","aliases":["trinidad_tobago"]},{"emoji":"🇹🇦","aliases":["tristan_da_cunha"]},{"emoji":"😤","aliases":["triumph"]},{"emoji":"🧌","aliases":["troll"]},{"emoji":"🚎","aliases":["trolleybus"]},{"emoji":"🏆","aliases":["trophy"]},{"emoji":"🍹","aliases":["tropical_drink"]},{"emoji":"🐠","aliases":["tropical_fish"]},{"emoji":"🚚","aliases":["truck"]},{"emoji":"🎺","aliases":["trumpet"]},{"emoji":"🌷","aliases":["tulip"]},{"emoji":"🥃","aliases":["tumbler_glass"]},{"emoji":"🇹🇳","aliases":["tunisia"]},{"emoji":"🦃","aliases":["turkey"]},{"emoji":"🇹🇲","aliases":["turkmenistan"]},{"emoji":"🇹🇨","aliases":["turks_caicos_islands"]},{"emoji":"🐢","aliases":["turtle"]},{"emoji":"🇹🇻","aliases":["tuvalu"]},{"emoji":"📺","aliases":["tv"]},{"emoji":"🔀","aliases":["twisted_rightwards_arrows"]},{"emoji":"2️⃣","aliases":["two"]},{"emoji":"💕","aliases":["two_hearts"]},{"emoji":"👬","aliases":["two_men_holding_hands"]},{"emoji":"👭","aliases":["two_women_holding_hands"]},{"emoji":"🈹","aliases":["u5272"]},{"emoji":"🈴","aliases":["u5408"]},{"emoji":"🈺","aliases":["u55b6"]},{"emoji":"🈯","aliases":["u6307"]},{"emoji":"🈷️","aliases":["u6708"]},{"emoji":"🈶","aliases":["u6709"]},{"emoji":"🈵","aliases":["u6e80"]},{"emoji":"🈚","aliases":["u7121"]},{"emoji":"🈸","aliases":["u7533"]},{"emoji":"🈲","aliases":["u7981"]},{"emoji":"🈳","aliases":["u7a7a"]},{"emoji":"🇺🇬","aliases":["uganda"]},{"emoji":"🇺🇦","aliases":["ukraine"]},{"emoji":"☔","aliases":["umbrella"]},{"emoji":"😒","aliases":["unamused"]},{"emoji":"🔞","aliases":["underage"]},{"emoji":"🦄","aliases":["unicorn"]},{"emoji":"🇦🇪","aliases":["united_arab_emirates"]},{"emoji":"🇺🇳","aliases":["united_nations"]},{"emoji":"🔓","aliases":["unlock"]},{"emoji":"🆙","aliases":["up"]},{"emoji":"🙃","aliases":["upside_down_face"]},{"emoji":"🇺🇾","aliases":["uruguay"]},{"emoji":"🇺🇸","aliases":["us"]},{"emoji":"🇺🇲","aliases":["us_outlying_islands"]},{"emoji":"🇻🇮","aliases":["us_virgin_islands"]},{"emoji":"🇺🇿","aliases":["uzbekistan"]},{"emoji":"✌️","aliases":["v"]},{"emoji":"🧛","aliases":["vampire"]},{"emoji":"🧛‍♂️","aliases":["vampire_man"]},{"emoji":"🧛‍♀️","aliases":["vampire_woman"]},{"emoji":"🇻🇺","aliases":["vanuatu"]},{"emoji":"🇻🇦","aliases":["vatican_city"]},{"emoji":"🇻🇪","aliases":["venezuela"]},{"emoji":"🚦","aliases":["vertical_traffic_light"]},{"emoji":"📼","aliases":["vhs"]},{"emoji":"📳","aliases":["vibration_mode"]},{"emoji":"📹","aliases":["video_camera"]},{"emoji":"🎮","aliases":["video_game"]},{"emoji":"🇻🇳","aliases":["vietnam"]},{"emoji":"🎻","aliases":["violin"]},{"emoji":"♍","aliases":["virgo"]},{"emoji":"🌋","aliases":["volcano"]},{"emoji":"🏐","aliases":["volleyball"]},{"emoji":"🤮","aliases":["vomiting_face"]},{"emoji":"🆚","aliases":["vs"]},{"emoji":"🖖","aliases":["vulcan_salute"]},{"emoji":"🧇","aliases":["waffle"]},{"emoji":"🏴󠁧󠁢󠁷󠁬󠁳󠁿","aliases":["wales"]},{"emoji":"🚶","aliases":["walking"]},{"emoji":"🚶‍♂️","aliases":["walking_man"]},{"emoji":"🚶‍♀️","aliases":["walking_woman"]},{"emoji":"🇼🇫","aliases":["wallis_futuna"]},{"emoji":"🌘","aliases":["waning_crescent_moon"]},{"emoji":"🌖","aliases":["waning_gibbous_moon"]},{"emoji":"⚠️","aliases":["warning"]},{"emoji":"🗑️","aliases":["wastebasket"]},{"emoji":"⌚","aliases":["watch"]},{"emoji":"🐃","aliases":["water_buffalo"]},{"emoji":"🤽","aliases":["water_polo"]},{"emoji":"🍉","aliases":["watermelon"]},{"emoji":"👋","aliases":["wave"]},{"emoji":"〰️","aliases":["wavy_dash"]},{"emoji":"🌒","aliases":["waxing_crescent_moon"]},{"emoji":"🚾","aliases":["wc"]},{"emoji":"😩","aliases":["weary"]},{"emoji":"💒","aliases":["wedding"]},{"emoji":"🏋️","aliases":["weight_lifting"]},{"emoji":"🏋️‍♂️","aliases":["weight_lifting_man"]},{"emoji":"🏋️‍♀️","aliases":["weight_lifting_woman"]},{"emoji":"🇪🇭","aliases":["western_sahara"]},{"emoji":"🐳","aliases":["whale"]},{"emoji":"🐋","aliases":["whale2"]},{"emoji":"🛞","aliases":["wheel"]},{"emoji":"☸️","aliases":["wheel_of_dharma"]},{"emoji":"♿","aliases":["wheelchair"]},{"emoji":"✅","aliases":["white_check_mark"]},{"emoji":"⚪","aliases":["white_circle"]},{"emoji":"🏳️","aliases":["white_flag"]},{"emoji":"💮","aliases":["white_flower"]},{"emoji":"👨‍🦳","aliases":["white_haired_man"]},{"emoji":"👩‍🦳","aliases":["white_haired_woman"]},{"emoji":"🤍","aliases":["white_heart"]},{"emoji":"⬜","aliases":["white_large_square"]},{"emoji":"◽","aliases":["white_medium_small_square"]},{"emoji":"◻️","aliases":["white_medium_square"]},{"emoji":"▫️","aliases":["white_small_square"]},{"emoji":"🔳","aliases":["white_square_button"]},{"emoji":"🥀","aliases":["wilted_flower"]},{"emoji":"🎐","aliases":["wind_chime"]},{"emoji":"🌬️","aliases":["wind_face"]},{"emoji":"🪟","aliases":["window"]},{"emoji":"🍷","aliases":["wine_glass"]},{"emoji":"🪽","aliases":["wing"]},{"emoji":"😉","aliases":["wink"]},{"emoji":"🛜","aliases":["wireless"]},{"emoji":"🐺","aliases":["wolf"]},{"emoji":"👩","aliases":["woman"]},{"emoji":"👩‍🎨","aliases":["woman_artist"]},{"emoji":"👩‍🚀","aliases":["woman_astronaut"]},{"emoji":"🧔‍♀️","aliases":["woman_beard"]},{"emoji":"🤸‍♀️","aliases":["woman_cartwheeling"]},{"emoji":"👩‍🍳","aliases":["woman_cook"]},{"emoji":"💃","aliases":["woman_dancing","dancer"]},{"emoji":"🤦‍♀️","aliases":["woman_facepalming"]},{"emoji":"👩‍🏭","aliases":["woman_factory_worker"]},{"emoji":"👩‍🌾","aliases":["woman_farmer"]},{"emoji":"👩‍🍼","aliases":["woman_feeding_baby"]},{"emoji":"👩‍🚒","aliases":["woman_firefighter"]},{"emoji":"👩‍⚕️","aliases":["woman_health_worker"]},{"emoji":"👩‍🦽","aliases":["woman_in_manual_wheelchair"]},{"emoji":"👩‍🦼","aliases":["woman_in_motorized_wheelchair"]},{"emoji":"🤵‍♀️","aliases":["woman_in_tuxedo"]},{"emoji":"👩‍⚖️","aliases":["woman_judge"]},{"emoji":"🤹‍♀️","aliases":["woman_juggling"]},{"emoji":"👩‍🔧","aliases":["woman_mechanic"]},{"emoji":"👩‍💼","aliases":["woman_office_worker"]},{"emoji":"👩‍✈️","aliases":["woman_pilot"]},{"emoji":"🤾‍♀️","aliases":["woman_playing_handball"]},{"emoji":"🤽‍♀️","aliases":["woman_playing_water_polo"]},{"emoji":"👩‍🔬","aliases":["woman_scientist"]},{"emoji":"🤷‍♀️","aliases":["woman_shrugging"]},{"emoji":"👩‍🎤","aliases":["woman_singer"]},{"emoji":"👩‍🎓","aliases":["woman_student"]},{"emoji":"👩‍🏫","aliases":["woman_teacher"]},{"emoji":"👩‍💻","aliases":["woman_technologist"]},{"emoji":"🧕","aliases":["woman_with_headscarf"]},{"emoji":"👩‍🦯","aliases":["woman_with_probing_cane"]},{"emoji":"👳‍♀️","aliases":["woman_with_turban"]},{"emoji":"👰‍♀️","aliases":["woman_with_veil","bride_with_veil"]},{"emoji":"👚","aliases":["womans_clothes"]},{"emoji":"👒","aliases":["womans_hat"]},{"emoji":"🤼‍♀️","aliases":["women_wrestling"]},{"emoji":"🚺","aliases":["womens"]},{"emoji":"🪵","aliases":["wood"]},{"emoji":"🥴","aliases":["woozy_face"]},{"emoji":"🗺️","aliases":["world_map"]},{"emoji":"🪱","aliases":["worm"]},{"emoji":"😟","aliases":["worried"]},{"emoji":"🔧","aliases":["wrench"]},{"emoji":"🤼","aliases":["wrestling"]},{"emoji":"✍️","aliases":["writing_hand"]},{"emoji":"❌","aliases":["x"]},{"emoji":"🩻","aliases":["x_ray"]},{"emoji":"🧶","aliases":["yarn"]},{"emoji":"🥱","aliases":["yawning_face"]},{"emoji":"🟡","aliases":["yellow_circle"]},{"emoji":"💛","aliases":["yellow_heart"]},{"emoji":"🟨","aliases":["yellow_square"]},{"emoji":"🇾🇪","aliases":["yemen"]},{"emoji":"💴","aliases":["yen"]},{"emoji":"☯️","aliases":["yin_yang"]},{"emoji":"🪀","aliases":["yo_yo"]},{"emoji":"😋","aliases":["yum"]},{"emoji":"🇿🇲","aliases":["zambia"]},{"emoji":"🤪","aliases":["zany_face"]},{"emoji":"⚡","aliases":["zap"]},{"emoji":"🦓","aliases":["zebra"]},{"emoji":"0️⃣","aliases":["zero"]},{"emoji":"🇿🇼","aliases":["zimbabwe"]},{"emoji":"🤐","aliases":["zipper_mouth_face"]},{"emoji":"🧟","aliases":["zombie"]},{"emoji":"🧟‍♂️","aliases":["zombie_man"]},{"emoji":"🧟‍♀️","aliases":["zombie_woman"]},{"emoji":"💤","aliases":["zzz"]}] \ No newline at end of file diff --git a/build/code-batch-process.go b/build/code-batch-process.go index 8556c27ad7379..b3ee3994207dc 100644 --- a/build/code-batch-process.go +++ b/build/code-batch-process.go @@ -25,7 +25,7 @@ import ( var optionLogVerbose bool -func logVerbose(msg string, args ...interface{}) { +func logVerbose(msg string, args ...any) { if optionLogVerbose { log.Printf(msg, args...) } diff --git a/build/generate-emoji.go b/build/generate-emoji.go index 09bdeb6808283..17a9670f06a31 100644 --- a/build/generate-emoji.go +++ b/build/generate-emoji.go @@ -25,7 +25,7 @@ import ( const ( gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json" - maxUnicodeVersion = 14 + maxUnicodeVersion = 15 ) var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") diff --git a/cmd/cert.go b/cmd/cert.go index 816659023ca93..897c10c8993b2 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -63,7 +63,7 @@ Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, }, } -func publicKey(priv interface{}) interface{} { +func publicKey(priv any) any { switch k := priv.(type) { case *rsa.PrivateKey: return &k.PublicKey @@ -74,7 +74,7 @@ func publicKey(priv interface{}) interface{} { } } -func pemBlockForKey(priv interface{}) *pem.Block { +func pemBlockForKey(priv any) *pem.Block { switch k := priv.(type) { case *rsa.PrivateKey: return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} @@ -94,7 +94,7 @@ func runCert(c *cli.Context) error { return err } - var priv interface{} + var priv any var err error switch c.String("ecdsa-curve") { case "": diff --git a/cmd/cmd.go b/cmd/cmd.go index 8076acecaa257..4ed636a9b4cc5 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -106,5 +106,21 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) { WriterOption: log.WriterConsoleOption{Stderr: out == os.Stderr}, } writer := log.NewEventWriterConsole("console-default", writeMode) - log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer) + log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) +} + +// 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 { + level := defaultLevel + if c.Bool("quiet") || c.GlobalBoolT("quiet") { + level = log.FATAL + } + if c.Bool("debug") || c.GlobalBool("debug") || c.Bool("verbose") || c.GlobalBool("verbose") { + level = log.TRACE + } + log.SetConsoleLogger(log.DEFAULT, "console-default", level) + return nil + } } diff --git a/cmd/doctor.go b/cmd/doctor.go index b79436fc0a9fa..cd5f125e20e4e 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -151,7 +151,7 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { log.FallbackErrorf("unable to create file log writer: %v", err) return } - log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer) + log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) } } diff --git a/cmd/dump.go b/cmd/dump.go index 0b7c1d32c5b0a..b1aed8aef46db 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -161,7 +161,7 @@ It can be used for backup and capture Gitea server image to send to maintainer`, }, } -func fatal(format string, args ...interface{}) { +func fatal(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) log.Fatal(format, args...) } @@ -236,7 +236,7 @@ func runDump(ctx *cli.Context) error { return err } - var iface interface{} + var iface any if fileName == "-" { iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType)) } else { diff --git a/cmd/embedded.go b/cmd/embedded.go index 204a623cf7040..105acee26ce20 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -22,9 +22,9 @@ import ( "github.com/urfave/cli" ) -// Cmdembedded represents the available extract sub-command. +// CmdEmbedded represents the available extract sub-command. var ( - Cmdembedded = cli.Command{ + CmdEmbedded = cli.Command{ Name: "embedded", Usage: "Extract embedded resources", Description: "A command for extracting embedded resources, like templates and images", diff --git a/cmd/hook.go b/cmd/hook.go index 6453267832835..ed6efc19ea2e1 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -15,6 +15,7 @@ import ( "time" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -32,6 +33,7 @@ var ( Name: "hook", Usage: "Delegate commands to corresponding Git hooks", Description: "This should only be called by Git", + Before: PrepareConsoleLoggerLevel(log.FATAL), Subcommands: []cli.Command{ subcmdHookPreReceive, subcmdHookUpdate, diff --git a/cmd/keys.go b/cmd/keys.go index deb94fca5d6ab..8aeee37527da4 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" "github.com/urfave/cli" @@ -17,6 +18,7 @@ import ( var CmdKeys = cli.Command{ Name: "keys", Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint", + Before: PrepareConsoleLoggerLevel(log.FATAL), Action: runKeys, Flags: []cli.Flag{ cli.StringFlag{ diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index dd85cc26d8087..70d1beb26d0f3 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -178,7 +178,7 @@ func runAddConnLogger(c *cli.Context) error { defer cancel() setup(ctx, c.Bool("debug")) - vals := map[string]interface{}{} + vals := map[string]any{} mode := "conn" vals["net"] = "tcp" if c.IsSet("protocol") { @@ -208,7 +208,7 @@ func runAddFileLogger(c *cli.Context) error { defer cancel() setup(ctx, c.Bool("debug")) - vals := map[string]interface{}{} + vals := map[string]any{} mode := "file" if c.IsSet("filename") { vals["filename"] = c.String("filename") @@ -236,7 +236,7 @@ func runAddFileLogger(c *cli.Context) error { return commonAddLogger(c, mode, vals) } -func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error { +func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error { if len(c.String("level")) > 0 { vals["level"] = log.LevelFromString(c.String("level")).String() } diff --git a/cmd/serv.go b/cmd/serv.go index 01102d3800c00..484e3bf4048a8 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -44,6 +44,7 @@ var CmdServ = cli.Command{ Name: "serv", Usage: "This command should only be called by SSH shell", Description: "Serv provides access auth for repositories", + Before: PrepareConsoleLoggerLevel(log.FATAL), Action: runServ, Flags: []cli.Flag{ cli.BoolFlag{ @@ -94,7 +95,7 @@ var ( // 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 ...interface{}) error { +func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error { if userMessage == "" { userMessage = "Internal Server Error (no specific error)" } diff --git a/cmd/web.go b/cmd/web.go index 7a257a62a277d..05f3b2ddb2eaa 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -35,6 +35,7 @@ var CmdWeb = cli.Command{ Usage: "Start Gitea web server", Description: `Gitea web server is the only thing you need to run, and it takes care of all the other things for you`, + Before: PrepareConsoleLoggerLevel(log.INFO), Action: runWeb, Flags: []cli.Flag{ cli.StringFlag{ @@ -206,11 +207,6 @@ func servePprof() { } func runWeb(ctx *cli.Context) error { - if ctx.Bool("verbose") { - setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout) - } else if ctx.Bool("quiet") { - setupConsoleLogger(log.FATAL, log.CanColorStdout, os.Stdout) - } defer func() { if panicked := recover(); panicked != nil { log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 57adce83c04b1..83c713cb05e6f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -193,8 +193,8 @@ RUN_USER = ; git ;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself. ;SSH_KEYGEN_PATH = ;; -;; Enable SSH Authorized Key Backup when rewriting all keys, default is true -;SSH_AUTHORIZED_KEYS_BACKUP = true +;; Enable SSH Authorized Key Backup when rewriting all keys, default is false +;SSH_AUTHORIZED_KEYS_BACKUP = false ;; ;; Determines which principals to allow ;; - empty: if SSH_TRUSTED_USER_CA_KEYS is empty this will default to off, otherwise will default to email, username. @@ -303,7 +303,10 @@ RUN_USER = ; git ;; ;; ;; LFS authentication secret, change this yourself -LFS_JWT_SECRET = +;LFS_JWT_SECRET = +;; +;; Alternative location to specify LFS authentication secret. You cannot specify both this and LFS_JWT_SECRET, and must pick one +;LFS_JWT_SECRET_URI = file:/etc/gitea/lfs_jwt_secret ;; ;; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail. ;LFS_HTTP_AUTH_EXPIRY = 24h @@ -527,6 +530,9 @@ ENABLE = true ;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to HS256, HS384 or HS512. ;JWT_SECRET = ;; +;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one +;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret +;; ;; Lifetime of an OAuth2 access token in seconds ;ACCESS_TOKEN_EXPIRATION_TIME = 3600 ;; @@ -2541,8 +2547,8 @@ LEVEL = Info ;; Enable/Disable actions capabilities ;ENABLED = false ;; -;; Default address to get action plugins, e.g. the default value means downloading from "https://gitea.com/actions/checkout" for "uses: actions/checkout@v3" -;DEFAULT_ACTIONS_URL = https://gitea.com +;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance. +;DEFAULT_ACTIONS_URL = github ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docker/root/etc/s6/gitea/setup b/docker/root/etc/s6/gitea/setup index d8f6a3b319ee2..b801ef4e03540 100755 --- a/docker/root/etc/s6/gitea/setup +++ b/docker/root/etc/s6/gitea/setup @@ -2,7 +2,15 @@ if [ ! -d /data/git/.ssh ]; then mkdir -p /data/git/.ssh - chmod 700 /data/git/.ssh +fi + +# Set the correct permissions on the .ssh directory and authorized_keys file, +# or sshd will refuse to use them and lead to clone/push/pull failures. +# It could happen when users have copied their data to a new volume and changed the file permission by accident, +# and it would be very hard to troubleshoot unless users know how to check the logs of sshd which is started by s6. +chmod 700 /data/git/.ssh +if [ -f /data/git/.ssh/authorized_keys ]; then + chmod 600 /data/git/.ssh/authorized_keys fi if [ ! -f /data/git/.ssh/environment ]; then diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md index 2b9511034994b..9c307cbc48c78 100644 --- a/docs/content/doc/administration/config-cheat-sheet.en-us.md +++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md @@ -336,7 +336,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `SSH_LISTEN_PORT`: **%(SSH\_PORT)s**: Port for the built-in SSH server. - `SSH_ROOT_PATH`: **~/.ssh**: Root path of SSH directory. - `SSH_CREATE_AUTHORIZED_KEYS_FILE`: **true**: Gitea will create a authorized_keys file by default when it is not using the internal ssh server. If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. -- `SSH_AUTHORIZED_KEYS_BACKUP`: **true**: Enable SSH Authorized Key Backup when rewriting all keys, default is true. +- `SSH_AUTHORIZED_KEYS_BACKUP`: **false**: Enable SSH Authorized Key Backup when rewriting all keys, default is false. - `SSH_TRUSTED_USER_CA_KEYS`: **\**: Specifies the public keys of certificate authorities that are trusted to sign user certificates for authentication. Multiple keys should be comma separated. E.g.`ssh- ` or `ssh- , ssh- `. For more information see `TrustedUserCAKeys` in the sshd config man pages. When empty no file will be created and `SSH_AUTHORIZED_PRINCIPALS_ALLOW` will default to `off`. - `SSH_TRUSTED_USER_CA_KEYS_FILENAME`: **`RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem**: Absolute path of the `TrustedUserCaKeys` file Gitea will manage. If you're running your own ssh server and you want to use the Gitea managed file you'll also need to modify your sshd_config to point to this file. The official docker image will automatically work without further configuration. - `SSH_AUTHORIZED_PRINCIPALS_ALLOW`: **off** or **username, email**: \[off, username, email, anything\]: Specify the principals values that users are allowed to use as principal. When set to `anything` no checks are done on the principal string. When set to `off` authorized principal are not allowed to be set. @@ -368,6 +368,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `LFS_START_SERVER`: **false**: Enables Git LFS support. - `LFS_CONTENT_PATH`: **%(APP_DATA_PATH)s/lfs**: Default LFS content path. (if it is on local storage.) **DEPRECATED** use settings in `[lfs]`. - `LFS_JWT_SECRET`: **\**: LFS authentication secret, change this a unique string. +- `LFS_JWT_SECRET_URI`: **\**: Instead of defining LFS_JWT_SECRET in the configuration, this configuration option can be used to give Gitea a path to a file that contains the secret (example value: `file:/etc/gitea/lfs_jwt_secret`) - `LFS_HTTP_AUTH_EXPIRY`: **24h**: LFS authentication validity period in time.Duration, pushes taking longer than this may fail. - `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit). - `LFS_LOCKS_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page. @@ -1097,6 +1098,7 @@ This section only does "set" config, a removed config key from this section won' - `INVALIDATE_REFRESH_TOKENS`: **false**: Check if refresh token has already been used - `JWT_SIGNING_ALGORITHM`: **RS256**: Algorithm used to sign OAuth2 tokens. Valid values: \[`HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`\] - `JWT_SECRET`: **\**: OAuth2 authentication secret for access and refresh tokens, change this to a unique string. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `HS256`, `HS384` or `HS512`. +- `JWT_SECRET_URI`: **\**: Instead of defining JWT_SECRET in the configuration, this configuration option can be used to give Gitea a path to a file that contains the secret (example value: `file:/etc/gitea/oauth2_jwt_secret`) - `JWT_SIGNING_PRIVATE_KEY_FILE`: **jwt/private.pem**: Private key file path used to sign OAuth2 tokens. The path is relative to `APP_DATA_PATH`. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `RS256`, `RS384`, `RS512`, `ES256`, `ES384` or `ES512`. The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you. - `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider @@ -1376,39 +1378,22 @@ PROXY_HOSTS = *.github.com ## Actions (`actions`) - `ENABLED`: **false**: Enable/Disable actions capabilities -- `DEFAULT_ACTIONS_URL`: **https://gitea.com**: Default address to get action plugins, e.g. the default value means downloading from "" for "uses: actions/checkout@v3" +- `DEFAULT_ACTIONS_URL`: **github**: Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance. - `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` - `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio` -`DEFAULT_ACTIONS_URL` indicates where should we find the relative path action plugin. i.e. when use an action in a workflow file like - -```yaml -name: versions -on: - push: - branches: - - main - - releases/* -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 -``` - -Now we need to know how to get actions/checkout, this configuration is the default git server to get it. That means we will get the repository via git clone ${DEFAULT_ACTIONS_URL}/actions/checkout and fetch tag v3. - -To help people who don't want to mirror these actions in their git instances, the default value is https://gitea.com -To help people run actions totally in their network, they can change the value and copy all necessary action repositories into their git server. +`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path. +For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`. +And it can be changed to `self` to make it `root_url_of_your_gitea/actions/checkout@v3`. -Of course we should support the form in future PRs like - -```yaml -steps: - - uses: gitea.com/actions/checkout@v3 -``` +Please note that using `self` is not recommended for most cases, as it could make names globally ambiguous. +Additionally, it requires you to mirror all the actions you need to your Gitea instance, which may not be worth it. +Therefore, please use `self` only if you understand what you are doing. -although Github don't support this form. +In earlier versions (<= 1.19), `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`. +However, later updates removed those options, and now the only options are `github` and `self`, with the default value being `github`. +However, if you want to use actions from other git server, you can use a complete URL in `uses` field, it's supported by Gitea (but not GitHub). +Like `uses: https://gitea.com/actions/checkout@v3` or `uses: http://your-git-server/actions/checkout@v3`. ## Other (`other`) diff --git a/docs/content/doc/installation/with-docker-rootless.en-us.md b/docs/content/doc/installation/with-docker-rootless.en-us.md index b8c76438f9f68..b8b40fcbd72ee 100644 --- a/docs/content/doc/installation/with-docker-rootless.en-us.md +++ b/docs/content/doc/installation/with-docker-rootless.en-us.md @@ -119,7 +119,7 @@ services: - /etc/localtime:/etc/localtime:ro ports: - "3000:3000" - - "222:22" + - "2222:2222" + depends_on: + - db + diff --git a/docs/content/doc/usage/actions/faq.en-us.md b/docs/content/doc/usage/actions/faq.en-us.md index 194c297a024b0..69a4cf3e89acb 100644 --- a/docs/content/doc/usage/actions/faq.en-us.md +++ b/docs/content/doc/usage/actions/faq.en-us.md @@ -164,3 +164,23 @@ Although we would like to provide more options, our limited manpower means that However, both Gitea and act runner are completely open source, so anyone can create a new/better implementation. We support your choice, no matter how you decide. In case you fork act runner to create your own version: Please contribute the changes back if you can and if you think your changes will help others as well. + +## What workflow trigger events does Gitea support? + +All events listed in this table are supported events and are compatible with GitHub. +For events supported only by GitHub, see GitHub's [documentation](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). + +| trigger event | activity types | +|-----------------------------|--------------------------------------------------------------------------------------------------------------------------| +| create | not applicable | +| delete | not applicable | +| fork | not applicable | +| gollum | not applicable | +| push | not applicable | +| issues | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `milestoned`, `demilestoned`, `labeled`, `unlabeled` | +| issue_comment | `created`, `edited`, `deleted` | +| pull_request | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `synchronize`, `labeled`, `unlabeled` | +| pull_request_review | `submitted`, `edited` | +| pull_request_review_comment | `created`, `edited` | +| release | `published`, `edited` | +| registry_package | `published` | diff --git a/docs/content/doc/usage/actions/faq.zh-cn.md b/docs/content/doc/usage/actions/faq.zh-cn.md index c990c04f150a1..ae6edd06f28cd 100644 --- a/docs/content/doc/usage/actions/faq.zh-cn.md +++ b/docs/content/doc/usage/actions/faq.zh-cn.md @@ -164,3 +164,23 @@ defaults: 然而,无论您如何决定,Gitea 和act runner都是完全开源的,所以任何人都可以创建一个新的/更好的实现。 我们支持您的选择,无论您如何决定。 如果您选择分支act runner来创建自己的版本,请在您认为您的更改对其他人也有帮助的情况下贡献这些更改。 + +## Gitea 支持哪些工作流触发事件? + +表格中列出的所有事件都是支持的,并且与 GitHub 兼容。 +对于仅 GitHub 支持的事件,请参阅 GitHub 的[文档](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)。 + +| 触发事件 | 活动类型 | +|-----------------------------|--------------------------------------------------------------------------------------------------------------------------| +| create | 不适用 | +| delete | 不适用 | +| fork | 不适用 | +| gollum | 不适用 | +| push | 不适用 | +| issues | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `milestoned`, `demilestoned`, `labeled`, `unlabeled` | +| issue_comment | `created`, `edited`, `deleted` | +| pull_request | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `synchronize`, `labeled`, `unlabeled` | +| pull_request_review | `submitted`, `edited` | +| pull_request_review_comment | `created`, `edited` | +| release | `published`, `edited` | +| registry_package | `published` | diff --git a/main.go b/main.go index 7b447e7533f0d..9b561376c34a5 100644 --- a/main.go +++ b/main.go @@ -87,28 +87,36 @@ func main() { app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".` app.Version = Version + formatBuiltWith() app.EnableBashCompletion = true - app.Commands = []cli.Command{ + + // these sub-commands need to use config file + subCmdWithIni := []cli.Command{ cmd.CmdWeb, cmd.CmdServ, cmd.CmdHook, cmd.CmdDump, - cmd.CmdCert, cmd.CmdAdmin, - cmd.CmdGenerate, cmd.CmdMigrate, cmd.CmdKeys, cmd.CmdConvert, cmd.CmdDoctor, cmd.CmdManager, - cmd.Cmdembedded, + cmd.CmdEmbedded, cmd.CmdMigrateStorage, - cmd.CmdDocs, cmd.CmdDumpRepository, cmd.CmdRestoreRepository, cmd.CmdActions, + cmdHelp, // TODO: the "help" sub-command was used to show the more information for "work path" and "custom config", in the future, it should avoid doing so + } + // these sub-commands do not need the config file, and they do not depend on any path or environment variable. + subCmdStandalone := []cli.Command{ + cmd.CmdCert, + cmd.CmdGenerate, + cmd.CmdDocs, } - // default configuration flags + // shared configuration flags, they are for global and for each sub-command at the same time + // eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed + // keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore. globalFlags := []cli.Flag{ cli.HelpFlag, cli.StringFlag{ @@ -128,13 +136,15 @@ func main() { // Set the default to be equivalent to cmdWeb and add the default flags app.Flags = append(app.Flags, globalFlags...) - app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) + app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) // TODO: the web flags polluted the global flags, they are not really global flags app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action) app.HideHelp = true // use our own help action to show helps (with more information like default config) - app.Commands = append(app.Commands, cmdHelp) - for i := range app.Commands { - prepareSubcommands(&app.Commands[i], globalFlags) + app.Before = cmd.PrepareConsoleLoggerLevel(log.INFO) + for i := range subCmdWithIni { + prepareSubcommands(&subCmdWithIni[i], globalFlags) } + app.Commands = append(app.Commands, subCmdWithIni...) + app.Commands = append(app.Commands, subCmdStandalone...) err := app.Run(os.Args) if err != nil { diff --git a/models/actions/task.go b/models/actions/task.go index 79b1d46dd0d5f..719fd193657f3 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -344,6 +344,9 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error { return err } +// UpdateTaskByState updates the task by the state. +// It will always update the task if the state is not final, even there is no change. +// So it will update ActionTask.Updated to avoid the task being judged as a zombie task. func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) { stepStates := map[int64]*runnerv1.StepState{} for _, v := range state.Steps { @@ -384,6 +387,12 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT }, nil); err != nil { return nil, err } + } else { + // Force update ActionTask.Updated to avoid the task being judged as a zombie task + task.Updated = timeutil.TimeStampNow() + if err := UpdateTask(ctx, task, "updated"); err != nil { + return nil, err + } } if err := task.LoadAttributes(ctx); err != nil { diff --git a/models/admin/task.go b/models/admin/task.go index fc40b13d0265a..8aa397ad351ad 100644 --- a/models/admin/task.go +++ b/models/admin/task.go @@ -44,7 +44,7 @@ func init() { // TranslatableMessage represents JSON struct that can be translated with a Locale type TranslatableMessage struct { Format string - Args []interface{} `json:"omitempty"` + Args []any `json:"omitempty"` } // LoadRepo loads repository of the task diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index db6e78cad522c..65af0bc94532b 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -455,9 +455,9 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use // CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository // There are several trust models in Gitea -func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) { +func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error { if !verification.Verified { - return + return nil } // In the Committer trust model a signature is trusted if it matches the committer @@ -475,7 +475,7 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_ verification.SigningUser.Email == verification.CommittingUser.Email) { verification.TrustStatus = "trusted" } - return + return nil } // Now we drop to the more nuanced trust models... @@ -490,10 +490,11 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_ verification.SigningUser.Email != verification.CommittingUser.Email) { verification.TrustStatus = "untrusted" } - return + return nil } // Check we actually have a GPG SigningKey + var err error if verification.SigningKey != nil { var isMember bool if keyMap != nil { diff --git a/models/asymkey/ssh_key_authorized_keys.go b/models/asymkey/ssh_key_authorized_keys.go index e138182d6864e..77803d6709ec3 100644 --- a/models/asymkey/ssh_key_authorized_keys.go +++ b/models/asymkey/ssh_key_authorized_keys.go @@ -47,7 +47,7 @@ var sshOpLocker sync.Mutex // AuthorizedStringForKey creates the authorized keys string appropriate for the provided key func AuthorizedStringForKey(key *PublicKey) string { sb := &strings.Builder{} - _ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]interface{}{ + _ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]any{ "AppPath": util.ShellEscape(setting.AppPath), "AppWorkPath": util.ShellEscape(setting.AppWorkPath), "CustomConf": util.ShellEscape(setting.CustomConf), @@ -175,7 +175,7 @@ func RewriteAllPublicKeys() error { // RegeneratePublicKeys regenerates the authorized_keys file func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error { - if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { + if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) { _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString()) return err }); err != nil { diff --git a/models/asymkey/ssh_key_authorized_principals.go b/models/asymkey/ssh_key_authorized_principals.go index 092839611f91c..592196c255ae7 100644 --- a/models/asymkey/ssh_key_authorized_principals.go +++ b/models/asymkey/ssh_key_authorized_principals.go @@ -97,7 +97,7 @@ func RewriteAllPrincipalKeys(ctx context.Context) error { } func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error { - if err := db.GetEngine(ctx).Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { + if err := db.GetEngine(ctx).Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) { _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString()) return err }); err != nil { diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 53a5c28b4a59e..0f64b56c1635b 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -306,9 +306,10 @@ func (code *OAuth2AuthorizationCode) TableName() string { } // GenerateRedirectURI generates a redirect URI for a successful authorization request. State will be used if not empty. -func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (redirect *url.URL, err error) { - if redirect, err = url.Parse(code.RedirectURI); err != nil { - return +func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL, error) { + redirect, err := url.Parse(code.RedirectURI) + if err != nil { + return nil, err } q := redirect.Query() if state != "" { diff --git a/models/db/context.go b/models/db/context.go index 59be1e138914a..351aea8faf908 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -52,7 +52,7 @@ func (ctx *Context) Engine() Engine { } // Value shadows Value for context.Context but allows us to get ourselves and an Engined object -func (ctx *Context) Value(key interface{}) interface{} { +func (ctx *Context) Value(key any) any { if key == enginedContextKey { return ctx } @@ -163,28 +163,28 @@ func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) } // Insert inserts records into database -func Insert(ctx context.Context, beans ...interface{}) error { +func Insert(ctx context.Context, beans ...any) error { _, err := GetEngine(ctx).Insert(beans...) return err } // Exec executes a sql with args -func Exec(ctx context.Context, sqlAndArgs ...interface{}) (sql.Result, error) { +func Exec(ctx context.Context, sqlAndArgs ...any) (sql.Result, error) { return GetEngine(ctx).Exec(sqlAndArgs...) } // GetByBean filled empty fields of the bean according non-empty fields to query in database. -func GetByBean(ctx context.Context, bean interface{}) (bool, error) { +func GetByBean(ctx context.Context, bean any) (bool, error) { return GetEngine(ctx).Get(bean) } // DeleteByBean deletes all records according non-empty fields of the bean as conditions. -func DeleteByBean(ctx context.Context, bean interface{}) (int64, error) { +func DeleteByBean(ctx context.Context, bean any) (int64, error) { return GetEngine(ctx).Delete(bean) } // DeleteByID deletes the given bean with the given ID -func DeleteByID(ctx context.Context, id int64, bean interface{}) (int64, error) { +func DeleteByID(ctx context.Context, id int64, bean any) (int64, error) { return GetEngine(ctx).ID(id).NoAutoTime().Delete(bean) } @@ -203,13 +203,13 @@ func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([ // DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one // Timestamps of the entities won't be updated -func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean interface{}) error { +func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error { _, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean) return err } // DeleteBeans deletes all given beans, beans must contain delete conditions. -func DeleteBeans(ctx context.Context, beans ...interface{}) (err error) { +func DeleteBeans(ctx context.Context, beans ...any) (err error) { e := GetEngine(ctx) for i := range beans { if _, err = e.Delete(beans[i]); err != nil { @@ -220,7 +220,7 @@ func DeleteBeans(ctx context.Context, beans ...interface{}) (err error) { } // TruncateBeans deletes all given beans, beans may contain delete conditions. -func TruncateBeans(ctx context.Context, beans ...interface{}) (err error) { +func TruncateBeans(ctx context.Context, beans ...any) (err error) { e := GetEngine(ctx) for i := range beans { if _, err = e.Truncate(beans[i]); err != nil { @@ -231,12 +231,12 @@ func TruncateBeans(ctx context.Context, beans ...interface{}) (err error) { } // CountByBean counts the number of database records according non-empty fields of the bean as conditions. -func CountByBean(ctx context.Context, bean interface{}) (int64, error) { +func CountByBean(ctx context.Context, bean any) (int64, error) { return GetEngine(ctx).Count(bean) } // TableName returns the table name according a bean object -func TableName(bean interface{}) string { +func TableName(bean any) string { return x.TableName(bean) } diff --git a/models/db/engine.go b/models/db/engine.go index 3eb16f8042ddc..07e4d00270bf2 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -25,7 +25,7 @@ import ( var ( x *xorm.Engine - tables []interface{} + tables []any initFuncs []func() error // HasEngine specifies if we have a xorm.Engine @@ -34,41 +34,41 @@ var ( // Engine represents a xorm engine or session. type Engine interface { - Table(tableNameOrBean interface{}) *xorm.Session - Count(...interface{}) (int64, error) - Decr(column string, arg ...interface{}) *xorm.Session - Delete(...interface{}) (int64, error) - Truncate(...interface{}) (int64, error) - Exec(...interface{}) (sql.Result, error) - Find(interface{}, ...interface{}) error - Get(beans ...interface{}) (bool, error) - ID(interface{}) *xorm.Session - In(string, ...interface{}) *xorm.Session - Incr(column string, arg ...interface{}) *xorm.Session - Insert(...interface{}) (int64, error) - Iterate(interface{}, xorm.IterFunc) error - Join(joinOperator string, tablename, condition interface{}, args ...interface{}) *xorm.Session - SQL(interface{}, ...interface{}) *xorm.Session - Where(interface{}, ...interface{}) *xorm.Session + Table(tableNameOrBean any) *xorm.Session + Count(...any) (int64, error) + Decr(column string, arg ...any) *xorm.Session + Delete(...any) (int64, error) + Truncate(...any) (int64, error) + Exec(...any) (sql.Result, error) + Find(any, ...any) error + Get(beans ...any) (bool, error) + ID(any) *xorm.Session + In(string, ...any) *xorm.Session + Incr(column string, arg ...any) *xorm.Session + Insert(...any) (int64, error) + Iterate(any, xorm.IterFunc) error + Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session + SQL(any, ...any) *xorm.Session + Where(any, ...any) *xorm.Session Asc(colNames ...string) *xorm.Session Desc(colNames ...string) *xorm.Session Limit(limit int, start ...int) *xorm.Session NoAutoTime() *xorm.Session - SumInt(bean interface{}, columnName string) (res int64, err error) - Sync2(...interface{}) error + SumInt(bean any, columnName string) (res int64, err error) + Sync2(...any) error Select(string) *xorm.Session - NotIn(string, ...interface{}) *xorm.Session - OrderBy(interface{}, ...interface{}) *xorm.Session - Exist(...interface{}) (bool, error) + NotIn(string, ...any) *xorm.Session + OrderBy(any, ...any) *xorm.Session + Exist(...any) (bool, error) Distinct(...string) *xorm.Session - Query(...interface{}) ([]map[string][]byte, error) + Query(...any) ([]map[string][]byte, error) Cols(...string) *xorm.Session Context(ctx context.Context) *xorm.Session Ping() error } // TableInfo returns table's information via an object -func TableInfo(v interface{}) (*schemas.Table, error) { +func TableInfo(v any) (*schemas.Table, error) { return x.TableInfo(v) } @@ -78,7 +78,7 @@ func DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) erro } // RegisterModel registers model, if initfunc provided, it will be invoked after data model sync -func RegisterModel(bean interface{}, initFunc ...func() error) { +func RegisterModel(bean any, initFunc ...func() error) { tables = append(tables, bean) if len(initFuncs) > 0 && initFunc[0] != nil { initFuncs = append(initFuncs, initFunc[0]) @@ -209,14 +209,14 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) } // NamesToBean return a list of beans or an error -func NamesToBean(names ...string) ([]interface{}, error) { - beans := []interface{}{} +func NamesToBean(names ...string) ([]any, error) { + beans := []any{} if len(names) == 0 { beans = append(beans, tables...) return beans, nil } // Need to map provided names to beans... - beanMap := make(map[string]interface{}) + beanMap := make(map[string]any) for _, bean := range tables { beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean @@ -224,7 +224,7 @@ func NamesToBean(names ...string) ([]interface{}, error) { beanMap[strings.ToLower(x.TableName(bean, true))] = bean } - gotBean := make(map[interface{}]bool) + gotBean := make(map[any]bool) for _, name := range names { bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))] if !ok { @@ -266,7 +266,7 @@ func DumpDatabase(filePath, dbType string) error { } // MaxBatchInsertSize returns the table's max batch insert size -func MaxBatchInsertSize(bean interface{}) int { +func MaxBatchInsertSize(bean any) int { t, err := x.TableInfo(bean) if err != nil { return 50 @@ -286,7 +286,7 @@ func DeleteAllRecords(tableName string) error { } // GetMaxID will return max id of the table -func GetMaxID(beanOrTableName interface{}) (maxID int64, err error) { +func GetMaxID(beanOrTableName any) (maxID int64, err error) { _, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID) return maxID, err } diff --git a/models/db/error.go b/models/db/error.go index edc8e80a9c605..665e970e17c1e 100644 --- a/models/db/error.go +++ b/models/db/error.go @@ -25,7 +25,7 @@ func (err ErrCancelled) Error() string { } // ErrCancelledf returns an ErrCancelled for the provided format and args -func ErrCancelledf(format string, args ...interface{}) error { +func ErrCancelledf(format string, args ...any) error { return ErrCancelled{ fmt.Sprintf(format, args...), } diff --git a/models/db/log.go b/models/db/log.go index dd95f64ca80b9..307788ea2e49f 100644 --- a/models/db/log.go +++ b/models/db/log.go @@ -28,47 +28,47 @@ func NewXORMLogger(showSQL bool) xormlog.Logger { const stackLevel = 8 // Log a message with defined skip and at logging level -func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...interface{}) { +func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...any) { l.logger.Log(skip+1, level, format, v...) } // Debug show debug log -func (l *XORMLogBridge) Debug(v ...interface{}) { +func (l *XORMLogBridge) Debug(v ...any) { l.Log(stackLevel, log.DEBUG, "%s", fmt.Sprint(v...)) } // Debugf show debug log -func (l *XORMLogBridge) Debugf(format string, v ...interface{}) { +func (l *XORMLogBridge) Debugf(format string, v ...any) { l.Log(stackLevel, log.DEBUG, format, v...) } // Error show error log -func (l *XORMLogBridge) Error(v ...interface{}) { +func (l *XORMLogBridge) Error(v ...any) { l.Log(stackLevel, log.ERROR, "%s", fmt.Sprint(v...)) } // Errorf show error log -func (l *XORMLogBridge) Errorf(format string, v ...interface{}) { +func (l *XORMLogBridge) Errorf(format string, v ...any) { l.Log(stackLevel, log.ERROR, format, v...) } // Info show information level log -func (l *XORMLogBridge) Info(v ...interface{}) { +func (l *XORMLogBridge) Info(v ...any) { l.Log(stackLevel, log.INFO, "%s", fmt.Sprint(v...)) } // Infof show information level log -func (l *XORMLogBridge) Infof(format string, v ...interface{}) { +func (l *XORMLogBridge) Infof(format string, v ...any) { l.Log(stackLevel, log.INFO, format, v...) } // Warn show warning log -func (l *XORMLogBridge) Warn(v ...interface{}) { +func (l *XORMLogBridge) Warn(v ...any) { l.Log(stackLevel, log.WARN, "%s", fmt.Sprint(v...)) } // Warnf show warnning log -func (l *XORMLogBridge) Warnf(format string, v ...interface{}) { +func (l *XORMLogBridge) Warnf(format string, v ...any) { l.Log(stackLevel, log.WARN, format, v...) } diff --git a/models/db/search.go b/models/db/search.go index 105cb64c41511..aa577f08e0439 100644 --- a/models/db/search.go +++ b/models/db/search.go @@ -20,6 +20,10 @@ const ( SearchOrderByNewest SearchOrderBy = "created_unix DESC" SearchOrderBySize SearchOrderBy = "size ASC" SearchOrderBySizeReverse SearchOrderBy = "size DESC" + SearchOrderByGitSize SearchOrderBy = "git_size ASC" + SearchOrderByGitSizeReverse SearchOrderBy = "git_size DESC" + SearchOrderByLFSSize SearchOrderBy = "lfs_size ASC" + SearchOrderByLFSSizeReverse SearchOrderBy = "lfs_size DESC" SearchOrderByID SearchOrderBy = "id ASC" SearchOrderByIDReverse SearchOrderBy = "id DESC" SearchOrderByStars SearchOrderBy = "num_stars ASC" diff --git a/models/dbfs/dbfile.go b/models/dbfs/dbfile.go index bac1cb9eb608a..3650ce057e4c5 100644 --- a/models/dbfs/dbfile.go +++ b/models/dbfs/dbfile.go @@ -7,6 +7,7 @@ import ( "context" "errors" "io" + "io/fs" "os" "path/filepath" "strconv" @@ -21,6 +22,7 @@ var defaultFileBlockSize int64 = 32 * 1024 type File interface { io.ReadWriteCloser io.Seeker + fs.File } type file struct { @@ -193,10 +195,26 @@ func (f *file) Close() error { return nil } +func (f *file) Stat() (os.FileInfo, error) { + if f.metaID == 0 { + return nil, os.ErrInvalid + } + + fileMeta, err := findFileMetaByID(f.ctx, f.metaID) + if err != nil { + return nil, err + } + return fileMeta, nil +} + func timeToFileTimestamp(t time.Time) int64 { return t.UnixMicro() } +func fileTimestampToTime(timestamp int64) time.Time { + return time.UnixMicro(timestamp) +} + func (f *file) loadMetaByPath() (*dbfsMeta, error) { var fileMeta dbfsMeta if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil { diff --git a/models/dbfs/dbfs.go b/models/dbfs/dbfs.go index 6b5b3beeb2745..f68b4a2b70b48 100644 --- a/models/dbfs/dbfs.go +++ b/models/dbfs/dbfs.go @@ -5,7 +5,10 @@ package dbfs import ( "context" + "io/fs" "os" + "path" + "time" "code.gitea.io/gitea/models/db" ) @@ -100,3 +103,29 @@ func Remove(ctx context.Context, name string) error { defer f.Close() return f.delete() } + +var _ fs.FileInfo = (*dbfsMeta)(nil) + +func (m *dbfsMeta) Name() string { + return path.Base(m.FullPath) +} + +func (m *dbfsMeta) Size() int64 { + return m.FileSize +} + +func (m *dbfsMeta) Mode() fs.FileMode { + return os.ModePerm +} + +func (m *dbfsMeta) ModTime() time.Time { + return fileTimestampToTime(m.ModifyTimestamp) +} + +func (m *dbfsMeta) IsDir() bool { + return false +} + +func (m *dbfsMeta) Sys() any { + return nil +} diff --git a/models/dbfs/dbfs_test.go b/models/dbfs/dbfs_test.go index 300758c623ad7..96cb1014c71f8 100644 --- a/models/dbfs/dbfs_test.go +++ b/models/dbfs/dbfs_test.go @@ -111,6 +111,19 @@ func TestDbfsBasic(t *testing.T) { _, err = OpenFile(db.DefaultContext, "test2.txt", os.O_RDONLY) assert.Error(t, err) + + // test stat + f, err = OpenFile(db.DefaultContext, "test/test.txt", os.O_RDWR|os.O_CREATE) + assert.NoError(t, err) + stat, err := f.Stat() + assert.NoError(t, err) + assert.EqualValues(t, "test.txt", stat.Name()) + assert.EqualValues(t, 0, stat.Size()) + _, err = f.Write([]byte("0123456789")) + assert.NoError(t, err) + stat, err = f.Stat() + assert.NoError(t, err) + assert.EqualValues(t, 10, stat.Size()) } func TestDbfsReadWrite(t *testing.T) { diff --git a/models/error.go b/models/error.go index 8223f23585bb5..b7bb967b739fb 100644 --- a/models/error.go +++ b/models/error.go @@ -318,90 +318,6 @@ func (err ErrFilePathProtected) Unwrap() error { return util.ErrPermissionDenied } -// __________ .__ -// \______ \____________ ____ ____ | |__ -// | | _/\_ __ \__ \ / \_/ ___\| | \ -// | | \ | | \// __ \| | \ \___| Y \ -// |______ / |__| (____ /___| /\___ >___| / -// \/ \/ \/ \/ \/ - -// ErrBranchDoesNotExist represents an error that branch with such name does not exist. -type ErrBranchDoesNotExist struct { - BranchName string -} - -// IsErrBranchDoesNotExist checks if an error is an ErrBranchDoesNotExist. -func IsErrBranchDoesNotExist(err error) bool { - _, ok := err.(ErrBranchDoesNotExist) - return ok -} - -func (err ErrBranchDoesNotExist) Error() string { - return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName) -} - -func (err ErrBranchDoesNotExist) Unwrap() error { - return util.ErrNotExist -} - -// ErrBranchAlreadyExists represents an error that branch with such name already exists. -type ErrBranchAlreadyExists struct { - BranchName string -} - -// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists. -func IsErrBranchAlreadyExists(err error) bool { - _, ok := err.(ErrBranchAlreadyExists) - return ok -} - -func (err ErrBranchAlreadyExists) Error() string { - return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) -} - -func (err ErrBranchAlreadyExists) Unwrap() error { - return util.ErrAlreadyExist -} - -// ErrBranchNameConflict represents an error that branch name conflicts with other branch. -type ErrBranchNameConflict struct { - BranchName string -} - -// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict. -func IsErrBranchNameConflict(err error) bool { - _, ok := err.(ErrBranchNameConflict) - return ok -} - -func (err ErrBranchNameConflict) Error() string { - return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) -} - -func (err ErrBranchNameConflict) Unwrap() error { - return util.ErrAlreadyExist -} - -// ErrBranchesEqual represents an error that branch name conflicts with other branch. -type ErrBranchesEqual struct { - BaseBranchName string - HeadBranchName string -} - -// IsErrBranchesEqual checks if an error is an ErrBranchesEqual. -func IsErrBranchesEqual(err error) bool { - _, ok := err.(ErrBranchesEqual) - return ok -} - -func (err ErrBranchesEqual) Error() string { - return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) -} - -func (err ErrBranchesEqual) Unwrap() error { - return util.ErrInvalidArgument -} - // ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. type ErrDisallowedToMerge struct { Reason string diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml new file mode 100644 index 0000000000000..93003049c67fb --- /dev/null +++ b/models/fixtures/branch.yml @@ -0,0 +1,47 @@ +- + id: 1 + repo_id: 1 + name: 'foo' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: 'first commit' + commit_time: 978307100 + pusher_id: 1 + is_deleted: true + deleted_by_id: 1 + deleted_unix: 978307200 + +- + id: 2 + repo_id: 1 + name: 'bar' + commit_id: '62fb502a7172d4453f0322a2cc85bddffa57f07a' + commit_message: 'second commit' + commit_time: 978307100 + pusher_id: 1 + is_deleted: true + deleted_by_id: 99 + deleted_unix: 978307200 + +- + id: 3 + repo_id: 1 + name: 'branch2' + commit_id: '985f0301dba5e7b34be866819cd15ad3d8f508ee' + commit_message: 'make pull5 outdated' + commit_time: 1579166279 + pusher_id: 1 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 4 + repo_id: 1 + name: 'master' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: 'Initial commit' + commit_time: 1489927679 + pusher_id: 1 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 diff --git a/models/fixtures/deleted_branch.yml b/models/fixtures/deleted_branch.yml deleted file mode 100644 index 6a08a78343b56..0000000000000 --- a/models/fixtures/deleted_branch.yml +++ /dev/null @@ -1,15 +0,0 @@ -- - id: 1 - repo_id: 1 - name: foo - commit: 1213212312313213213132131 - deleted_by_id: 1 - deleted_unix: 978307200 - -- - id: 2 - repo_id: 1 - name: bar - commit: 5655464564554545466464655 - deleted_by_id: 99 - deleted_unix: 978307200 diff --git a/models/fixtures/mirror.yml b/models/fixtures/mirror.yml new file mode 100644 index 0000000000000..97bc4ae60dda2 --- /dev/null +++ b/models/fixtures/mirror.yml @@ -0,0 +1,49 @@ +- + id: 1 + repo_id: 5 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 2 + repo_id: 25 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 3 + repo_id: 26 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 4 + repo_id: 27 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 5 + repo_id: 28 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index ef7730780f740..050a9e2d06f4a 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -141,7 +141,7 @@ num_projects: 0 num_closed_projects: 0 is_private: true - is_empty: true + is_empty: false is_archived: false is_mirror: true status: 0 diff --git a/models/git/branch.go b/models/git/branch.go new file mode 100644 index 0000000000000..97891f01ebb40 --- /dev/null +++ b/models/git/branch.go @@ -0,0 +1,401 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + "fmt" + "time" + + "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/log" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" +) + +// ErrBranchNotExist represents an error that branch with such name does not exist. +type ErrBranchNotExist struct { + RepoID int64 + BranchName string +} + +// IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist. +func IsErrBranchNotExist(err error) bool { + _, ok := err.(ErrBranchNotExist) + return ok +} + +func (err ErrBranchNotExist) Error() string { + return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName) +} + +func (err ErrBranchNotExist) Unwrap() error { + return util.ErrNotExist +} + +// ErrBranchAlreadyExists represents an error that branch with such name already exists. +type ErrBranchAlreadyExists struct { + BranchName string +} + +// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists. +func IsErrBranchAlreadyExists(err error) bool { + _, ok := err.(ErrBranchAlreadyExists) + return ok +} + +func (err ErrBranchAlreadyExists) Error() string { + return fmt.Sprintf("branch already exists [name: %s]", err.BranchName) +} + +func (err ErrBranchAlreadyExists) Unwrap() error { + return util.ErrAlreadyExist +} + +// ErrBranchNameConflict represents an error that branch name conflicts with other branch. +type ErrBranchNameConflict struct { + BranchName string +} + +// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict. +func IsErrBranchNameConflict(err error) bool { + _, ok := err.(ErrBranchNameConflict) + return ok +} + +func (err ErrBranchNameConflict) Error() string { + return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName) +} + +func (err ErrBranchNameConflict) Unwrap() error { + return util.ErrAlreadyExist +} + +// ErrBranchesEqual represents an error that base branch is equal to the head branch. +type ErrBranchesEqual struct { + BaseBranchName string + HeadBranchName string +} + +// IsErrBranchesEqual checks if an error is an ErrBranchesEqual. +func IsErrBranchesEqual(err error) bool { + _, ok := err.(ErrBranchesEqual) + return ok +} + +func (err ErrBranchesEqual) Error() string { + return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) +} + +func (err ErrBranchesEqual) Unwrap() error { + return util.ErrInvalidArgument +} + +// Branch represents a branch of a repository +// For those repository who have many branches, stored into database is a good choice +// for pagination, keyword search and filtering +type Branch struct { + ID int64 + RepoID int64 `xorm:"UNIQUE(s)"` + Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment + CommitID string + CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line) + PusherID int64 + Pusher *user_model.User `xorm:"-"` + IsDeleted bool `xorm:"index"` + DeletedByID int64 + DeletedBy *user_model.User `xorm:"-"` + DeletedUnix timeutil.TimeStamp `xorm:"index"` + CommitTime timeutil.TimeStamp // The commit + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` +} + +func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) { + if b.DeletedBy == nil { + b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID) + if user_model.IsErrUserNotExist(err) { + b.DeletedBy = user_model.NewGhostUser() + err = nil + } + } + return err +} + +func (b *Branch) LoadPusher(ctx context.Context) (err error) { + if b.Pusher == nil && b.PusherID > 0 { + b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID) + if user_model.IsErrUserNotExist(err) { + b.Pusher = user_model.NewGhostUser() + err = nil + } + } + return err +} + +func init() { + db.RegisterModel(new(Branch)) + db.RegisterModel(new(RenamedBranch)) +} + +func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) { + var branch Branch + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch) + if err != nil { + return nil, err + } else if !has { + return nil, ErrBranchNotExist{ + RepoID: repoID, + BranchName: branchName, + } + } + return &branch, nil +} + +func AddBranches(ctx context.Context, branches []*Branch) error { + for _, branch := range branches { + if _, err := db.GetEngine(ctx).Insert(branch); err != nil { + return err + } + } + return nil +} + +func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) { + var branch Branch + has, err := db.GetEngine(ctx).ID(branchID).Get(&branch) + if err != nil { + return nil, err + } else if !has { + return nil, ErrBranchNotExist{ + RepoID: repoID, + } + } + if branch.RepoID != repoID { + return nil, ErrBranchNotExist{ + RepoID: repoID, + } + } + if !branch.IsDeleted { + return nil, ErrBranchNotExist{ + RepoID: repoID, + } + } + return &branch, nil +} + +func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { + branches := make([]*Branch, 0, len(branchIDs)) + if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil { + return err + } + for _, branch := range branches { + if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil { + return err + } + } + return nil + }) +} + +// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information +// If it doest not exist, insert a new record into database +func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error { + cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). + Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). + Update(&Branch{ + CommitID: commit.ID.String(), + CommitMessage: commit.Summary(), + PusherID: pusherID, + CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), + IsDeleted: false, + }) + if err != nil { + return err + } + if cnt > 0 { + return nil + } + + return db.Insert(ctx, &Branch{ + RepoID: repoID, + Name: branchName, + CommitID: commit.ID.String(), + CommitMessage: commit.Summary(), + PusherID: pusherID, + CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), + }) +} + +// AddDeletedBranch adds a deleted branch to the database +func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { + branch, err := GetBranch(ctx, repoID, branchName) + if err != nil { + return err + } + if branch.IsDeleted { + return nil + } + + cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false). + Cols("is_deleted, deleted_by_id, deleted_unix"). + Update(&Branch{ + IsDeleted: true, + DeletedByID: deletedByID, + DeletedUnix: timeutil.TimeStampNow(), + }) + if err != nil { + return err + } + if cnt == 0 { + return fmt.Errorf("branch %s not found or has been deleted", branchName) + } + return err +} + +func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch)) + return err +} + +// RemoveOldDeletedBranches removes old deleted branches +func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { + // Nothing to do for shutdown or terminate + log.Trace("Doing: DeletedBranchesCleanup") + + deleteBefore := time.Now().Add(-olderThan) + _, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch)) + if err != nil { + log.Error("DeletedBranchesCleanup: %v", err) + } +} + +// RenamedBranch provide renamed branch log +// will check it when a branch can't be found +type RenamedBranch struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX NOT NULL"` + From string + To string + CreatedUnix timeutil.TimeStamp `xorm:"created"` +} + +// FindRenamedBranch check if a branch was renamed +func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { + branch = &RenamedBranch{ + RepoID: repoID, + From: from, + } + exist, err = db.GetEngine(ctx).Get(branch) + + return branch, exist, err +} + +// RenameBranch rename a branch +func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + sess := db.GetEngine(ctx) + + // 1. update branch in database + if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ + Name: to, + }); err != nil { + return err + } else if n <= 0 { + return ErrBranchNotExist{ + RepoID: repo.ID, + BranchName: from, + } + } + + // 2. update default branch if needed + isDefault := repo.DefaultBranch == from + if isDefault { + repo.DefaultBranch = to + _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) + if err != nil { + return err + } + } + + // 3. Update protected branch if needed + protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) + if err != nil { + return err + } + + if protectedBranch != nil { + // there is a protect rule for this branch + protectedBranch.RuleName = to + _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) + if err != nil { + return err + } + } else { + // some glob protect rules may match this branch + protected, err := IsBranchProtected(ctx, repo.ID, from) + if err != nil { + return err + } + if protected { + return ErrBranchIsProtected + } + } + + // 4. Update all not merged pull request base branch name + _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?", + repo.ID, from, false). + Update(map[string]any{"base_branch": to}) + if err != nil { + return err + } + + // 5. do git action + if err = gitAction(isDefault); err != nil { + return err + } + + // 6. insert renamed branch record + renamedBranch := &RenamedBranch{ + RepoID: repo.ID, + From: from, + To: to, + } + err = db.Insert(ctx, renamedBranch) + if err != nil { + return err + } + + return committer.Commit() +} + +// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created +func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64) (BranchList, error) { + branches := make(BranchList, 0, 2) + subQuery := builder.Select("head_branch").From("pull_request"). + InnerJoin("issue", "issue.id = pull_request.issue_id"). + Where(builder.Eq{ + "pull_request.head_repo_id": repoID, + "issue.is_closed": false, + }) + err := db.GetEngine(ctx). + Where("pusher_id=? AND is_deleted=?", userID, false). + And("updated_unix >= ?", time.Now().Add(-time.Hour*6).Unix()). + NotIn("name", subQuery). + OrderBy("branch.updated_unix DESC"). + Limit(2). + Find(&branches) + return branches, err +} diff --git a/models/git/branch_list.go b/models/git/branch_list.go new file mode 100644 index 0000000000000..131a149782e27 --- /dev/null +++ b/models/git/branch_list.go @@ -0,0 +1,145 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" + "xorm.io/xorm" +) + +type BranchList []*Branch + +func (branches BranchList) LoadDeletedBy(ctx context.Context) error { + ids := container.Set[int64]{} + for _, branch := range branches { + if !branch.IsDeleted { + continue + } + ids.Add(branch.DeletedByID) + } + usersMap := make(map[int64]*user_model.User, len(ids)) + if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { + return err + } + for _, branch := range branches { + if !branch.IsDeleted { + continue + } + branch.DeletedBy = usersMap[branch.DeletedByID] + if branch.DeletedBy == nil { + branch.DeletedBy = user_model.NewGhostUser() + } + } + return nil +} + +func (branches BranchList) LoadPusher(ctx context.Context) error { + ids := container.Set[int64]{} + for _, branch := range branches { + if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher + ids.Add(branch.PusherID) + } + } + usersMap := make(map[int64]*user_model.User, len(ids)) + if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { + return err + } + for _, branch := range branches { + if branch.PusherID <= 0 { + continue + } + branch.Pusher = usersMap[branch.PusherID] + if branch.Pusher == nil { + branch.Pusher = user_model.NewGhostUser() + } + } + return nil +} + +type FindBranchOptions struct { + db.ListOptions + RepoID int64 + ExcludeBranchNames []string + IsDeletedBranch util.OptionalBool + OrderBy string +} + +func (opts *FindBranchOptions) Cond() builder.Cond { + cond := builder.NewCond() + if opts.RepoID > 0 { + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + } + + if len(opts.ExcludeBranchNames) > 0 { + cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames)) + } + if !opts.IsDeletedBranch.IsNone() { + cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()}) + } + return cond +} + +func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{}) +} + +func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { + if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end + sess = sess.OrderBy("is_deleted ASC") + } + + if opts.OrderBy == "" { + // the commit_time might be the same, so add the "name" to make sure the order is stable + opts.OrderBy = "commit_time DESC, name ASC" + } + return sess.OrderBy(opts.OrderBy) +} + +func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) { + sess := db.GetEngine(ctx).Where(opts.Cond()) + if opts.PageSize > 0 && !opts.IsListAll() { + sess = db.SetSessionPagination(sess, &opts.ListOptions) + } + sess = orderByBranches(sess, opts) + + var branches []*Branch + return branches, sess.Find(&branches) +} + +func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) { + sess := db.GetEngine(ctx).Select("name").Where(opts.Cond()) + if opts.PageSize > 0 && !opts.IsListAll() { + sess = db.SetSessionPagination(sess, &opts.ListOptions) + } + sess = orderByBranches(sess, opts) + var branches []string + if err := sess.Table("branch").Find(&branches); err != nil { + return nil, err + } + return branches, nil +} + +func FindBranchesByRepoAndBranchName(ctx context.Context, repoBranches map[int64]string) (map[int64]string, error) { + cond := builder.NewCond() + for repoID, branchName := range repoBranches { + cond = cond.Or(builder.And(builder.Eq{"repo_id": repoID}, builder.Eq{"name": branchName})) + } + var branches []*Branch + if err := db.GetEngine(ctx). + Where(cond).Find(&branches); err != nil { + return nil, err + } + branchMap := make(map[int64]string, len(branches)) + for _, branch := range branches { + branchMap[branch.RepoID] = branch.CommitID + } + return branchMap, nil +} diff --git a/models/git/branches_test.go b/models/git/branch_test.go similarity index 74% rename from models/git/branches_test.go rename to models/git/branch_test.go index 5d18d9525ef80..ba6902692792e 100644 --- a/models/git/branches_test.go +++ b/models/git/branch_test.go @@ -11,6 +11,8 @@ import ( issues_model "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/git" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -18,24 +20,45 @@ import ( func TestAddDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) - assert.Error(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID)) - assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1))) + assert.True(t, firstBranch.IsDeleted) + assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.DeletedByID)) + assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "branch2", int64(1))) + + secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"}) + assert.True(t, secondBranch.IsDeleted) + + commit := &git.Commit{ + ID: git.MustIDFromString(secondBranch.CommitID), + CommitMessage: secondBranch.CommitMessage, + Committer: &git.Signature{ + When: secondBranch.CommitTime.AsLocalTime(), + }, + } + + err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) + assert.NoError(t, err) } func TestGetDeletedBranches(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - branches, err := git_model.GetDeletedBranches(db.DefaultContext, repo.ID) + branches, err := git_model.FindBranches(db.DefaultContext, git_model.FindBranchOptions{ + ListOptions: db.ListOptions{ + ListAll: true, + }, + RepoID: repo.ID, + IsDeletedBranch: util.OptionalBoolTrue, + }) assert.NoError(t, err) assert.Len(t, branches, 2) } func TestGetDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) assert.NotNil(t, getDeletedBranch(t, firstBranch)) } @@ -43,18 +66,18 @@ func TestGetDeletedBranch(t *testing.T) { func TestDeletedBranchLoadUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) - secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) + secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2}) branch := getDeletedBranch(t, firstBranch) assert.Nil(t, branch.DeletedBy) - branch.LoadUser(db.DefaultContext) + branch.LoadDeletedBy(db.DefaultContext) assert.NotNil(t, branch.DeletedBy) assert.Equal(t, "user1", branch.DeletedBy.Name) branch = getDeletedBranch(t, secondBranch) assert.Nil(t, branch.DeletedBy) - branch.LoadUser(db.DefaultContext) + branch.LoadDeletedBy(db.DefaultContext) assert.NotNil(t, branch.DeletedBy) assert.Equal(t, "Ghost", branch.DeletedBy.Name) } @@ -63,22 +86,22 @@ func TestRemoveDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) + firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1) assert.NoError(t, err) unittest.AssertNotExistsBean(t, firstBranch) - unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) + unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2}) } -func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch { +func getDeletedBranch(t *testing.T, branch *git_model.Branch) *git_model.Branch { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID) assert.NoError(t, err) assert.Equal(t, branch.ID, deletedBranch.ID) assert.Equal(t, branch.Name, deletedBranch.Name) - assert.Equal(t, branch.Commit, deletedBranch.Commit) + assert.Equal(t, branch.CommitID, deletedBranch.CommitID) assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID) return deletedBranch @@ -146,8 +169,8 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) { deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1) - // Expect no error, and the returned branch is nil. - assert.NoError(t, err) + // Expect error, and the returned branch is nil. + assert.Error(t, err) assert.Nil(t, deletedBranch) // Now get the deletedBranch with ID of 1 on repo with ID 1. diff --git a/models/git/branches.go b/models/git/branches.go deleted file mode 100644 index b94ea329599a1..0000000000000 --- a/models/git/branches.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2016 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "context" - "fmt" - "time" - - "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/log" - "code.gitea.io/gitea/modules/timeutil" -) - -// DeletedBranch struct -type DeletedBranch struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"UNIQUE(s) NOT NULL"` - Commit string `xorm:"UNIQUE(s) NOT NULL"` - DeletedByID int64 `xorm:"INDEX"` - DeletedBy *user_model.User `xorm:"-"` - DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"` -} - -func init() { - db.RegisterModel(new(DeletedBranch)) - db.RegisterModel(new(RenamedBranch)) -} - -// AddDeletedBranch adds a deleted branch to the database -func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error { - deletedBranch := &DeletedBranch{ - RepoID: repoID, - Name: branchName, - Commit: commit, - DeletedByID: deletedByID, - } - - _, err := db.GetEngine(ctx).Insert(deletedBranch) - return err -} - -// GetDeletedBranches returns all the deleted branches -func GetDeletedBranches(ctx context.Context, repoID int64) ([]*DeletedBranch, error) { - deletedBranches := make([]*DeletedBranch, 0) - return deletedBranches, db.GetEngine(ctx).Where("repo_id = ?", repoID).Desc("deleted_unix").Find(&deletedBranches) -} - -// GetDeletedBranchByID get a deleted branch by its ID -func GetDeletedBranchByID(ctx context.Context, repoID, id int64) (*DeletedBranch, error) { - deletedBranch := &DeletedBranch{} - has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("id = ?", id).Get(deletedBranch) - if err != nil { - return nil, err - } - if !has { - return nil, nil - } - return deletedBranch, nil -} - -// RemoveDeletedBranchByID removes a deleted branch from the database -func RemoveDeletedBranchByID(ctx context.Context, repoID, id int64) (err error) { - deletedBranch := &DeletedBranch{ - RepoID: repoID, - ID: id, - } - - if affected, err := db.GetEngine(ctx).Delete(deletedBranch); err != nil { - return err - } else if affected != 1 { - return fmt.Errorf("remove deleted branch ID(%v) failed", id) - } - - return nil -} - -// LoadUser loads the user that deleted the branch -// When there's no user found it returns a user_model.NewGhostUser -func (deletedBranch *DeletedBranch) LoadUser(ctx context.Context) { - user, err := user_model.GetUserByID(ctx, deletedBranch.DeletedByID) - if err != nil { - user = user_model.NewGhostUser() - } - deletedBranch.DeletedBy = user -} - -// RemoveDeletedBranchByName removes all deleted branches -func RemoveDeletedBranchByName(ctx context.Context, repoID int64, branch string) error { - _, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch)) - return err -} - -// RemoveOldDeletedBranches removes old deleted branches -func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) { - // Nothing to do for shutdown or terminate - log.Trace("Doing: DeletedBranchesCleanup") - - deleteBefore := time.Now().Add(-olderThan) - _, err := db.GetEngine(ctx).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch)) - if err != nil { - log.Error("DeletedBranchesCleanup: %v", err) - } -} - -// RenamedBranch provide renamed branch log -// will check it when a branch can't be found -type RenamedBranch struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX NOT NULL"` - From string - To string - CreatedUnix timeutil.TimeStamp `xorm:"created"` -} - -// FindRenamedBranch check if a branch was renamed -func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) { - branch = &RenamedBranch{ - RepoID: repoID, - From: from, - } - exist, err = db.GetEngine(ctx).Get(branch) - - return branch, exist, err -} - -// RenameBranch rename a branch -func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - sess := db.GetEngine(ctx) - // 1. update default branch if needed - isDefault := repo.DefaultBranch == from - if isDefault { - repo.DefaultBranch = to - _, err = sess.ID(repo.ID).Cols("default_branch").Update(repo) - if err != nil { - return err - } - } - - // 2. Update protected branch if needed - protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from) - if err != nil { - return err - } - - if protectedBranch != nil { - protectedBranch.RuleName = to - _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch) - if err != nil { - return err - } - } else { - protected, err := IsBranchProtected(ctx, repo.ID, from) - if err != nil { - return err - } - if protected { - return ErrBranchIsProtected - } - } - - // 3. Update all not merged pull request base branch name - _, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?", - repo.ID, from, false). - Update(map[string]interface{}{"base_branch": to}) - if err != nil { - return err - } - - // 4. do git action - if err = gitAction(isDefault); err != nil { - return err - } - - // 5. insert renamed branch record - renamedBranch := &RenamedBranch{ - RepoID: repo.ID, - From: from, - To: to, - } - err = db.Insert(ctx, renamedBranch) - if err != nil { - return err - } - - return committer.Commit() -} diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 49143a87e83f0..c418cd23eb63c 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -346,6 +346,53 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA return repoStatuses, nil } +// GetLatestCommitStatusForRepoCommitIDs returns all statuses with a unique context for a given list of repo-sha pairs +func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, commitIDs []string) (map[string][]*CommitStatus, error) { + type result struct { + ID int64 + Sha string + } + + results := make([]result, 0, len(commitIDs)) + + sess := db.GetEngine(ctx).Table(&CommitStatus{}) + + // Create a disjunction of conditions for each repoID and SHA pair + conds := make([]builder.Cond, 0, len(commitIDs)) + for _, sha := range commitIDs { + conds = append(conds, builder.Eq{"sha": sha}) + } + sess = sess.Where(builder.Eq{"repo_id": repoID}.And(builder.Or(conds...))). + Select("max( id ) as id, sha"). + GroupBy("context_hash, sha").OrderBy("max( id ) desc") + + err := sess.Find(&results) + if err != nil { + return nil, err + } + + ids := make([]int64, 0, len(results)) + repoStatuses := make(map[string][]*CommitStatus) + for _, result := range results { + ids = append(ids, result.ID) + } + + statuses := make([]*CommitStatus, 0, len(ids)) + if len(ids) > 0 { + err = db.GetEngine(ctx).In("id", ids).Find(&statuses) + if err != nil { + return nil, err + } + + // Group the statuses by repo ID + for _, status := range statuses { + repoStatuses[status.SHA] = append(repoStatuses[status.SHA], status) + } + } + + return repoStatuses, nil +} + // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) { start := timeutil.TimeStampNow().AddDuration(-before) diff --git a/models/git/lfs.go b/models/git/lfs.go index 0f90b2920047b..7d3da72a9410c 100644 --- a/models/git/lfs.go +++ b/models/git/lfs.go @@ -264,7 +264,7 @@ func LFSAutoAssociate(ctx context.Context, metas []*LFSMetaObject, user *user_mo sess := db.GetEngine(ctx) - oids := make([]interface{}, len(metas)) + oids := make([]any, len(metas)) oidMap := make(map[string]*LFSMetaObject, len(metas)) for i, meta := range metas { oids[i] = meta.Oid diff --git a/models/git/protected_branch_list.go b/models/git/protected_branch_list.go index 17fe6d701fb4a..eeb307e2454e7 100644 --- a/models/git/protected_branch_list.go +++ b/models/git/protected_branch_list.go @@ -8,7 +8,7 @@ import ( "sort" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" ) @@ -47,19 +47,32 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB } // FindAllMatchedBranches find all matched branches -func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) { - // FIXME: how many should we get? - branches, _, err := gitRepo.GetBranchNames(0, 9999999) - if err != nil { - return nil, err - } - rule := glob.MustCompile(ruleName) - results := make([]string, 0, len(branches)) - for _, branch := range branches { - if rule.Match(branch) { - results = append(results, branch) +func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) { + results := make([]string, 0, 10) + for page := 1; ; page++ { + brancheNames, err := FindBranchNames(ctx, FindBranchOptions{ + ListOptions: db.ListOptions{ + PageSize: 100, + Page: page, + }, + RepoID: repoID, + IsDeletedBranch: util.OptionalBoolFalse, + }) + if err != nil { + return nil, err + } + rule := glob.MustCompile(ruleName) + + for _, branch := range brancheNames { + if rule.Match(branch) { + results = append(results, branch) + } + } + if len(brancheNames) < 100 { + break } } + return results, nil } diff --git a/models/issues/comment.go b/models/issues/comment.go index dbe4434ca7542..303c23916ba2a 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -1131,7 +1131,7 @@ func DeleteComment(ctx context.Context, comment *Comment) error { } if _, err := e.Table("action"). Where("comment_id = ?", comment.ID). - Update(map[string]interface{}{ + Update(map[string]any{ "is_deleted": true, }); err != nil { return err @@ -1156,7 +1156,7 @@ func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID }), )). And("comment.original_author_id = ?", originalAuthorID). - Update(map[string]interface{}{ + Update(map[string]any{ "poster_id": posterID, "original_author": "", "original_author_id": 0, diff --git a/models/issues/issue.go b/models/issues/issue.go index eab18f4892ce9..364d53ba318c3 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -714,7 +714,7 @@ func (issue *Issue) Pin(ctx context.Context, user *user_model.User) error { _, err = db.GetEngine(ctx).Table("issue"). Where("id = ?", issue.ID). - Update(map[string]interface{}{ + Update(map[string]any{ "pin_order": maxPin + 1, }) if err != nil { @@ -750,7 +750,7 @@ func (issue *Issue) Unpin(ctx context.Context, user *user_model.User) error { _, err = db.GetEngine(ctx).Table("issue"). Where("id = ?", issue.ID). - Update(map[string]interface{}{ + Update(map[string]any{ "pin_order": 0, }) if err != nil { @@ -822,7 +822,7 @@ func (issue *Issue) MovePin(ctx context.Context, newPosition int) error { _, err = db.GetEngine(dbctx).Table("issue"). Where("id = ?", issue.ID). - Update(map[string]interface{}{ + Update(map[string]any{ "pin_order": newPosition, }) if err != nil { diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index b6fd720fe5eb8..9453ddc085d4d 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -511,7 +511,7 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *us } // DeleteInIssue delete records in beans with external key issue_id = ? -func DeleteInIssue(ctx context.Context, issueID int64, beans ...interface{}) error { +func DeleteInIssue(ctx context.Context, issueID int64, beans ...any) error { e := db.GetEngine(ctx) for _, bean := range beans { if _, err := e.In("issue_id", issueID).Delete(bean); err != nil { @@ -673,7 +673,7 @@ func UpdateIssuesMigrationsByType(gitServiceType api.GitServiceType, originalAut _, err := db.GetEngine(db.DefaultContext).Table("issue"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). - Update(map[string]interface{}{ + Update(map[string]any{ "poster_id": posterID, "original_author": "", "original_author_id": 0, @@ -686,7 +686,7 @@ func UpdateReactionsMigrationsByType(gitServiceType api.GitServiceType, original _, err := db.GetEngine(db.DefaultContext).Table("reaction"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(gitServiceType)). - Update(map[string]interface{}{ + Update(map[string]any{ "user_id": userID, "original_author": "", "original_author_id": 0, diff --git a/models/issues/review.go b/models/issues/review.go index 3685c65ce581c..dbacfa3a8785d 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -1111,7 +1111,7 @@ func UpdateReviewsMigrationsByType(tp structs.GitServiceType, originalAuthorID s _, err := db.GetEngine(db.DefaultContext).Table("review"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(tp)). - Update(map[string]interface{}{ + Update(map[string]any{ "reviewer_id": posterID, "original_author": "", "original_author_id": 0, diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go index b038ad73372e4..51351cc7d3297 100644 --- a/models/migrations/base/db.go +++ b/models/migrations/base/db.go @@ -27,7 +27,7 @@ import ( // RecreateTables will recreate the tables for the provided beans using the newly provided bean definition and move all data to that new table // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION -func RecreateTables(beans ...interface{}) func(*xorm.Engine) error { +func RecreateTables(beans ...any) func(*xorm.Engine) error { return func(x *xorm.Engine) error { sess := x.NewSession() defer sess.Close() @@ -48,7 +48,7 @@ func RecreateTables(beans ...interface{}) func(*xorm.Engine) error { // RecreateTable will recreate the table using the newly provided bean definition and move all data to that new table // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION // WARNING: YOU MUST COMMIT THE SESSION AT THE END -func RecreateTable(sess *xorm.Session, bean interface{}) error { +func RecreateTable(sess *xorm.Session, bean any) error { // TODO: This will not work if there are foreign keys tableName := sess.Engine().TableName(bean) diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go index c3100ba665948..e7ff5241447a7 100644 --- a/models/migrations/base/tests.go +++ b/models/migrations/base/tests.go @@ -30,7 +30,7 @@ import ( // Provide models to be sync'd with the database - in particular any models you expect fixtures to be loaded from. // // fixtures in `models/migrations/fixtures/` will be loaded automatically -func PrepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.Engine, func()) { +func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, func()) { t.Helper() ourSkip := 2 ourSkip += skip diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 30a0b6e7eb733..a15b6e4eec8ca 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -507,6 +507,10 @@ var migrations = []Migration{ NewMigration("Add variable table", v1_21.CreateVariableTable), // v262 -> v263 NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun), + // v263 -> v264 + NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable), + // v264 -> v265 + NewMigration("Add branch table", v1_21.AddBranchTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_10/v100.go b/models/migrations/v1_10/v100.go index bd11790b9884c..e94024f4df43b 100644 --- a/models/migrations/v1_10/v100.go +++ b/models/migrations/v1_10/v100.go @@ -59,11 +59,11 @@ func UpdateMigrationServiceTypes(x *xorm.Engine) error { } type ExternalLoginUser struct { - ExternalID string `xorm:"pk NOT NULL"` - UserID int64 `xorm:"INDEX NOT NULL"` - LoginSourceID int64 `xorm:"pk NOT NULL"` - RawData map[string]interface{} `xorm:"TEXT JSON"` - Provider string `xorm:"index VARCHAR(25)"` + ExternalID string `xorm:"pk NOT NULL"` + UserID int64 `xorm:"INDEX NOT NULL"` + LoginSourceID int64 `xorm:"pk NOT NULL"` + RawData map[string]any `xorm:"TEXT JSON"` + Provider string `xorm:"index VARCHAR(25)"` Email string Name string FirstName string diff --git a/models/migrations/v1_11/v106.go b/models/migrations/v1_11/v106.go index 3e06309a8d8ec..18d436ae20cf0 100644 --- a/models/migrations/v1_11/v106.go +++ b/models/migrations/v1_11/v106.go @@ -16,10 +16,10 @@ type Watch struct { Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` } -func AddModeColumnToWatch(x *xorm.Engine) (err error) { - if err = x.Sync2(new(Watch)); err != nil { - return +func AddModeColumnToWatch(x *xorm.Engine) error { + if err := x.Sync2(new(Watch)); err != nil { + return err } - _, err = x.Exec("UPDATE `watch` SET `mode` = 1") + _, err := x.Exec("UPDATE `watch` SET `mode` = 1") return err } diff --git a/models/migrations/v1_13/v143.go b/models/migrations/v1_13/v143.go index ad1a8c66a5d15..885768dff37de 100644 --- a/models/migrations/v1_13/v143.go +++ b/models/migrations/v1_13/v143.go @@ -23,25 +23,25 @@ func RecalculateStars(x *xorm.Engine) (err error) { for start := 0; ; start += batchSize { users := make([]User, 0, batchSize) - if err = sess.Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil { - return + if err := sess.Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil { + return err } if len(users) == 0 { break } - if err = sess.Begin(); err != nil { - return + if err := sess.Begin(); err != nil { + return err } for _, user := range users { - if _, err = sess.Exec("UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil { - return + if _, err := sess.Exec("UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil { + return err } } - if err = sess.Commit(); err != nil { - return + if err := sess.Commit(); err != nil { + return err } } diff --git a/models/migrations/v1_15/v180.go b/models/migrations/v1_15/v180.go index 17163ee2c683f..c71e77186170c 100644 --- a/models/migrations/v1_15/v180.go +++ b/models/migrations/v1_15/v180.go @@ -44,25 +44,25 @@ func DeleteMigrationCredentials(x *xorm.Engine) (err error) { for start := 0; ; start += batchSize { tasks := make([]*Task, 0, batchSize) - if err = sess.Limit(batchSize, start).Where(cond, 0).Find(&tasks); err != nil { - return + if err := sess.Limit(batchSize, start).Where(cond, 0).Find(&tasks); err != nil { + return err } if len(tasks) == 0 { break } - if err = sess.Begin(); err != nil { - return + if err := sess.Begin(); err != nil { + return err } for _, t := range tasks { if t.PayloadContent, err = removeCredentials(t.PayloadContent); err != nil { - return + return err } - if _, err = sess.ID(t.ID).Cols("payload_content").Update(t); err != nil { - return + if _, err := sess.ID(t.ID).Cols("payload_content").Update(t); err != nil { + return err } } - if err = sess.Commit(); err != nil { - return + if err := sess.Commit(); err != nil { + return err } } return err diff --git a/models/migrations/v1_15/v181.go b/models/migrations/v1_15/v181.go index e2bb3208c41d8..f4dd1d6016cd7 100644 --- a/models/migrations/v1_15/v181.go +++ b/models/migrations/v1_15/v181.go @@ -9,7 +9,7 @@ import ( "xorm.io/xorm" ) -func AddPrimaryEmail2EmailAddress(x *xorm.Engine) (err error) { +func AddPrimaryEmail2EmailAddress(x *xorm.Engine) error { type User struct { ID int64 `xorm:"pk autoincr"` Email string `xorm:"NOT NULL"` @@ -26,12 +26,12 @@ func AddPrimaryEmail2EmailAddress(x *xorm.Engine) (err error) { } // Add lower_email and is_primary columns - if err = x.Table("email_address").Sync2(new(EmailAddress1)); err != nil { - return + if err := x.Table("email_address").Sync2(new(EmailAddress1)); err != nil { + return err } - if _, err = x.Exec("UPDATE email_address SET lower_email=LOWER(email), is_primary=?", false); err != nil { - return + if _, err := x.Exec("UPDATE email_address SET lower_email=LOWER(email), is_primary=?", false); err != nil { + return err } type EmailAddress struct { @@ -44,8 +44,8 @@ func AddPrimaryEmail2EmailAddress(x *xorm.Engine) (err error) { } // change lower_email as unique - if err = x.Sync2(new(EmailAddress)); err != nil { - return + if err := x.Sync2(new(EmailAddress)); err != nil { + return err } sess := x.NewSession() @@ -55,34 +55,33 @@ func AddPrimaryEmail2EmailAddress(x *xorm.Engine) (err error) { for start := 0; ; start += batchSize { users := make([]*User, 0, batchSize) - if err = sess.Limit(batchSize, start).Find(&users); err != nil { - return + if err := sess.Limit(batchSize, start).Find(&users); err != nil { + return err } if len(users) == 0 { break } for _, user := range users { - var exist bool - exist, err = sess.Where("email=?", user.Email).Table("email_address").Exist() + exist, err := sess.Where("email=?", user.Email).Table("email_address").Exist() if err != nil { - return + return err } if !exist { - if _, err = sess.Insert(&EmailAddress{ + if _, err := sess.Insert(&EmailAddress{ UID: user.ID, Email: user.Email, LowerEmail: strings.ToLower(user.Email), IsActivated: user.IsActive, IsPrimary: true, }); err != nil { - return + return err } } else { - if _, err = sess.Where("email=?", user.Email).Cols("is_primary").Update(&EmailAddress{ + if _, err := sess.Where("email=?", user.Email).Cols("is_primary").Update(&EmailAddress{ IsPrimary: true, }); err != nil { - return + return err } } } diff --git a/models/migrations/v1_16/v189.go b/models/migrations/v1_16/v189.go index 32e3899a3a1c4..79e3289ba7f51 100644 --- a/models/migrations/v1_16/v189.go +++ b/models/migrations/v1_16/v189.go @@ -14,7 +14,7 @@ import ( ) func UnwrapLDAPSourceCfg(x *xorm.Engine) error { - jsonUnmarshalHandleDoubleEncode := func(bs []byte, v interface{}) error { + jsonUnmarshalHandleDoubleEncode := func(bs []byte, v any) error { err := json.Unmarshal(bs, v) if err != nil { ok := true @@ -54,7 +54,7 @@ func UnwrapLDAPSourceCfg(x *xorm.Engine) error { const dldapType = 5 type WrappedSource struct { - Source map[string]interface{} + Source map[string]any } // change lower_email as unique @@ -77,7 +77,7 @@ func UnwrapLDAPSourceCfg(x *xorm.Engine) error { for _, source := range sources { wrapped := &WrappedSource{ - Source: map[string]interface{}{}, + Source: map[string]any{}, } err := jsonUnmarshalHandleDoubleEncode([]byte(source.Cfg), &wrapped) if err != nil { diff --git a/models/migrations/v1_16/v189_test.go b/models/migrations/v1_16/v189_test.go index 96cb97c328383..32ef821d27886 100644 --- a/models/migrations/v1_16/v189_test.go +++ b/models/migrations/v1_16/v189_test.go @@ -62,8 +62,8 @@ func Test_UnwrapLDAPSourceCfg(t *testing.T) { } for _, source := range sources { - converted := map[string]interface{}{} - expected := map[string]interface{}{} + converted := map[string]any{} + expected := map[string]any{} if err := json.Unmarshal([]byte(source.Cfg), &converted); err != nil { assert.NoError(t, err) diff --git a/models/migrations/v1_19/v233_test.go b/models/migrations/v1_19/v233_test.go index 83558da334e8d..32c10ab0f4511 100644 --- a/models/migrations/v1_19/v233_test.go +++ b/models/migrations/v1_19/v233_test.go @@ -79,7 +79,7 @@ func Test_AddHeaderAuthorizationEncryptedColWebhook(t *testing.T) { return } for _, h := range hookTasks { - var m map[string]interface{} + var m map[string]any err := json.Unmarshal([]byte(h.PayloadContent), &m) assert.NoError(t, err) assert.Nil(t, m["access_token"]) diff --git a/models/migrations/v1_21/v263.go b/models/migrations/v1_21/v263.go new file mode 100644 index 0000000000000..88a5cb92b49fb --- /dev/null +++ b/models/migrations/v1_21/v263.go @@ -0,0 +1,41 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +// AddGitSizeAndLFSSizeToRepositoryTable: add GitSize and LFSSize columns to Repository +func AddGitSizeAndLFSSizeToRepositoryTable(x *xorm.Engine) error { + type Repository struct { + GitSize int64 `xorm:"NOT NULL DEFAULT 0"` + LFSSize int64 `xorm:"NOT NULL DEFAULT 0"` + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + if err := sess.Sync2(new(Repository)); err != nil { + return fmt.Errorf("Sync2: %w", err) + } + + _, err := sess.Exec(`UPDATE repository SET lfs_size=(SELECT SUM(size) FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID) WHERE EXISTS (SELECT 1 FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID)`) + if err != nil { + return err + } + + _, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size`) + if err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/v1_21/v264.go b/models/migrations/v1_21/v264.go new file mode 100644 index 0000000000000..60b7a7acf7d0c --- /dev/null +++ b/models/migrations/v1_21/v264.go @@ -0,0 +1,93 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func AddBranchTable(x *xorm.Engine) error { + type Branch struct { + ID int64 + RepoID int64 `xorm:"UNIQUE(s)"` + Name string `xorm:"UNIQUE(s) NOT NULL"` + CommitID string + CommitMessage string `xorm:"TEXT"` + PusherID int64 + IsDeleted bool `xorm:"index"` + DeletedByID int64 + DeletedUnix timeutil.TimeStamp `xorm:"index"` + CommitTime timeutil.TimeStamp // The commit + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` + } + + if err := x.Sync(new(Branch)); err != nil { + return err + } + + if exist, err := x.IsTableExist("deleted_branches"); err != nil { + return err + } else if !exist { + return nil + } + + type DeletedBranch struct { + ID int64 + RepoID int64 `xorm:"index UNIQUE(s)"` + Name string `xorm:"UNIQUE(s) NOT NULL"` + Commit string + DeletedByID int64 + DeletedUnix timeutil.TimeStamp + } + + var adminUserID int64 + has, err := x.Table("user"). + Select("id"). + Where("is_admin=?", true). + Asc("id"). // Reliably get the admin with the lowest ID. + Get(&adminUserID) + if err != nil { + return err + } else if !has { + return fmt.Errorf("no admin user found") + } + + branches := make([]Branch, 0, 100) + if err := db.Iterate(context.Background(), nil, func(ctx context.Context, deletedBranch *DeletedBranch) error { + branches = append(branches, Branch{ + RepoID: deletedBranch.RepoID, + Name: deletedBranch.Name, + CommitID: deletedBranch.Commit, + PusherID: adminUserID, + IsDeleted: true, + DeletedByID: deletedBranch.DeletedByID, + DeletedUnix: deletedBranch.DeletedUnix, + }) + if len(branches) >= 100 { + _, err := x.Insert(&branches) + if err != nil { + return err + } + branches = branches[:0] + } + return nil + }); err != nil { + return err + } + + if len(branches) > 0 { + if _, err := x.Insert(&branches); err != nil { + return err + } + } + + return x.DropTables("deleted_branches") +} diff --git a/models/migrations/v1_6/v70.go b/models/migrations/v1_6/v70.go index fec88266ba6e8..74434a84a14f4 100644 --- a/models/migrations/v1_6/v70.go +++ b/models/migrations/v1_6/v70.go @@ -81,11 +81,11 @@ func AddIssueDependencies(x *xorm.Engine) (err error) { // RepoUnit describes all units of a repository type RepoUnit struct { ID int64 - RepoID int64 `xorm:"INDEX(s)"` - Type int `xorm:"INDEX(s)"` - Config map[string]interface{} `xorm:"JSON"` - CreatedUnix int64 `xorm:"INDEX CREATED"` - Created time.Time `xorm:"-"` + RepoID int64 `xorm:"INDEX(s)"` + Type int `xorm:"INDEX(s)"` + Config map[string]any `xorm:"JSON"` + CreatedUnix int64 `xorm:"INDEX CREATED"` + Created time.Time `xorm:"-"` } // Updating existing issue units @@ -96,7 +96,7 @@ func AddIssueDependencies(x *xorm.Engine) (err error) { } for _, unit := range units { if unit.Config == nil { - unit.Config = make(map[string]interface{}) + unit.Config = make(map[string]any) } if _, ok := unit.Config["EnableDependencies"]; !ok { unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies diff --git a/models/migrations/v1_8/v76.go b/models/migrations/v1_8/v76.go index f35856cc606cc..d3fbd94deb104 100644 --- a/models/migrations/v1_8/v76.go +++ b/models/migrations/v1_8/v76.go @@ -15,10 +15,10 @@ func AddPullRequestRebaseWithMerge(x *xorm.Engine) error { // RepoUnit describes all units of a repository type RepoUnit struct { ID int64 - RepoID int64 `xorm:"INDEX(s)"` - Type int `xorm:"INDEX(s)"` - Config map[string]interface{} `xorm:"JSON"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` + RepoID int64 `xorm:"INDEX(s)"` + Type int `xorm:"INDEX(s)"` + Config map[string]any `xorm:"JSON"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` } const ( @@ -46,7 +46,7 @@ func AddPullRequestRebaseWithMerge(x *xorm.Engine) error { } for _, unit := range units { if unit.Config == nil { - unit.Config = make(map[string]interface{}) + unit.Config = make(map[string]any) } // Allow the new merge style if all other merge styles are allowed allowMergeRebase := true diff --git a/models/organization/org.go b/models/organization/org.go index 05db6ba15f8ab..8fd4ad076bd2d 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -532,27 +532,6 @@ func GetOrgsCanCreateRepoByUserID(userID int64) ([]*Organization, error) { Find(&orgs) } -// GetOrgUsersByUserID returns all organization-user relations by user ID. -func GetOrgUsersByUserID(uid int64, opts *SearchOrganizationsOptions) ([]*OrgUser, error) { - ous := make([]*OrgUser, 0, 10) - sess := db.GetEngine(db.DefaultContext). - Join("LEFT", "`user`", "`org_user`.org_id=`user`.id"). - Where("`org_user`.uid=?", uid) - if !opts.All { - // Only show public organizations - sess.And("is_public=?", true) - } - - if opts.PageSize != 0 { - sess = db.SetSessionPagination(sess, opts) - } - - err := sess. - Asc("`user`.name"). - Find(&ous) - return ous, err -} - // GetOrgUsersByOrgID returns all organization-user relations by organization ID. func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) { sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID) diff --git a/models/organization/org_test.go b/models/organization/org_test.go index 27a173d497c52..226807232c66c 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -207,42 +207,6 @@ func TestFindOrgs(t *testing.T) { assert.EqualValues(t, 1, total) } -func TestGetOrgUsersByUserID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - orgUsers, err := organization.GetOrgUsersByUserID(5, &organization.SearchOrganizationsOptions{All: true}) - assert.NoError(t, err) - if assert.Len(t, orgUsers, 3) { - assert.Equal(t, organization.OrgUser{ - ID: orgUsers[0].ID, - OrgID: 23, - UID: 5, - IsPublic: false, - }, *orgUsers[0]) - assert.Equal(t, organization.OrgUser{ - ID: orgUsers[1].ID, - OrgID: 6, - UID: 5, - IsPublic: true, - }, *orgUsers[1]) - assert.Equal(t, organization.OrgUser{ - ID: orgUsers[2].ID, - OrgID: 7, - UID: 5, - IsPublic: false, - }, *orgUsers[2]) - } - - publicOrgUsers, err := organization.GetOrgUsersByUserID(5, &organization.SearchOrganizationsOptions{All: false}) - assert.NoError(t, err) - assert.Len(t, publicOrgUsers, 1) - assert.Equal(t, *orgUsers[1], *publicOrgUsers[0]) - - orgUsers, err = organization.GetOrgUsersByUserID(1, &organization.SearchOrganizationsOptions{All: true}) - assert.NoError(t, err) - assert.Len(t, orgUsers, 0) -} - func TestGetOrgUsersByOrgID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index ee35ffe0f2a3a..f849ab5c0417c 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -59,7 +59,7 @@ type PackageDescriptor struct { Creator *user_model.User PackageProperties PackagePropertyList VersionProperties PackagePropertyList - Metadata interface{} + Metadata any Files []*PackageFileDescriptor } @@ -136,7 +136,7 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc return nil, err } - var metadata interface{} + var metadata any switch p.Type { case TypeAlpine: metadata = &alpine.VersionMetadata{} diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 64df5355bba6a..2027b87ecb43c 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -127,7 +127,8 @@ func (p *Permission) LogString() string { } // GetUserRepoPermission returns the user permissions to the repository -func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { +func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) { + var perm Permission if log.IsTrace() { defer func() { if user == nil { @@ -147,30 +148,31 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use // TODO: anonymous user visit public unit of private repo??? if user == nil && repo.IsPrivate { perm.AccessMode = perm_model.AccessModeNone - return + return perm, nil } - var is bool + var isCollaborator bool + var err error if user != nil { - is, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID) + isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID) if err != nil { return perm, err } } - if err = repo.LoadOwner(ctx); err != nil { - return + if err := repo.LoadOwner(ctx); err != nil { + return perm, err } // 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 - if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !is { + if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator { perm.AccessMode = perm_model.AccessModeNone - return + return perm, nil } - if err = repo.LoadUnits(ctx); err != nil { - return + if err := repo.LoadUnits(ctx); err != nil { + return perm, err } perm.Units = repo.Units @@ -178,32 +180,32 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use // anonymous visit public repo if user == nil { perm.AccessMode = perm_model.AccessModeRead - return + return perm, nil } // Admin or the owner has super access to the repository if user.IsAdmin || user.ID == repo.OwnerID { perm.AccessMode = perm_model.AccessModeOwner - return + return perm, nil } // plain user perm.AccessMode, err = accessLevel(ctx, user, repo) if err != nil { - return + return perm, err } - if err = repo.LoadOwner(ctx); err != nil { - return + if err := repo.LoadOwner(ctx); err != nil { + return perm, err } if !repo.Owner.IsOrganization() { - return + return perm, nil } perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode) // Collaborators on organization - if is { + if isCollaborator { for _, u := range repo.Units { perm.UnitsMode[u.Type] = perm.AccessMode } @@ -212,7 +214,7 @@ 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 + return perm, err } // if user in an owner team @@ -220,7 +222,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use if team.AccessMode >= perm_model.AccessModeAdmin { perm.AccessMode = perm_model.AccessModeOwner perm.UnitsMode = nil - return + return perm, nil } } diff --git a/models/repo.go b/models/repo.go index 2e0e8af16c4a5..9044fc8aedbc3 100644 --- a/models/repo.go +++ b/models/repo.go @@ -147,7 +147,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { &repo_model.Collaboration{RepoID: repoID}, &issues_model.Comment{RefRepoID: repoID}, &git_model.CommitStatus{RepoID: repoID}, - &git_model.DeletedBranch{RepoID: repoID}, + &git_model.Branch{RepoID: repoID}, &git_model.LFSLock{RepoID: repoID}, &repo_model.LanguageStat{RepoID: repoID}, &issues_model.Milestone{RepoID: repoID}, @@ -456,7 +456,7 @@ func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error { return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true) } -func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) { +func statsQuery(args ...any) func(context.Context) ([]map[string][]byte, error) { return func(ctx context.Context) ([]map[string][]byte, error) { return db.GetEngine(ctx).Query(args...) } diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go index 0a6444de858ac..a29dfe99a50f1 100644 --- a/models/repo/collaboration_test.go +++ b/models/repo/collaboration_test.go @@ -33,6 +33,19 @@ func TestRepository_GetCollaborators(t *testing.T) { test(2) test(3) test(4) + + // Test db.ListOptions + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) + + collaborators1, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{PageSize: 1, Page: 1}) + assert.NoError(t, err) + assert.Len(t, collaborators1, 1) + + collaborators2, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{PageSize: 1, Page: 2}) + assert.NoError(t, err) + assert.Len(t, collaborators2, 1) + + assert.NotEqualValues(t, collaborators1[0].ID, collaborators2[0].ID) } func TestRepository_IsCollaborator(t *testing.T) { @@ -66,5 +79,80 @@ func TestRepository_ChangeCollaborationAccessMode(t *testing.T) { assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, unittest.NonexistentID, perm.AccessModeAdmin)) + // Disvard invalid input. + assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, 4, perm.AccessMode(unittest.NonexistentID))) + unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID}) } + +func TestRepository_CountCollaborators(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + count, err := repo_model.CountCollaborators(repo1.ID) + assert.NoError(t, err) + assert.EqualValues(t, 2, count) + + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) + count, err = repo_model.CountCollaborators(repo2.ID) + assert.NoError(t, err) + assert.EqualValues(t, 2, count) + + // Non-existent repository. + count, err = repo_model.CountCollaborators(unittest.NonexistentID) + assert.NoError(t, err) + assert.EqualValues(t, 0, count) +} + +func TestRepository_IsOwnerMemberCollaborator(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + + // Organisation owner. + actual, err := repo_model.IsOwnerMemberCollaborator(repo1, 2) + assert.NoError(t, err) + assert.True(t, actual) + + // Team member. + actual, err = repo_model.IsOwnerMemberCollaborator(repo1, 4) + assert.NoError(t, err) + assert.True(t, actual) + + // Normal user. + actual, err = repo_model.IsOwnerMemberCollaborator(repo1, 1) + assert.NoError(t, err) + assert.False(t, actual) + + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + + // Collaborator. + actual, err = repo_model.IsOwnerMemberCollaborator(repo2, 4) + assert.NoError(t, err) + assert.True(t, actual) + + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) + + // Repository owner. + actual, err = repo_model.IsOwnerMemberCollaborator(repo3, 2) + assert.NoError(t, err) + assert.True(t, actual) +} + +func TestRepo_GetCollaboration(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + + // Existing collaboration. + collab, err := repo_model.GetCollaboration(db.DefaultContext, repo.ID, 4) + assert.NoError(t, err) + assert.NotNil(t, collab) + assert.EqualValues(t, 4, collab.UserID) + assert.EqualValues(t, 4, collab.RepoID) + + // Non-existing collaboration. + collab, err = repo_model.GetCollaboration(db.DefaultContext, repo.ID, 1) + assert.NoError(t, err) + assert.Nil(t, collab) +} diff --git a/models/repo/mirror.go b/models/repo/mirror.go index c1d24a4886375..39482037b2032 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -105,7 +105,7 @@ func DeleteMirrorByRepoID(repoID int64) error { } // MirrorsIterate iterates all mirror repositories. -func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error { +func MirrorsIterate(limit int, f func(idx int, bean any) error) error { sess := db.GetEngine(db.DefaultContext). Where("next_update_unix<=?", time.Now().Unix()). And("next_update_unix!=0"). diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go index 642020bb5e6d8..f34484f638788 100644 --- a/models/repo/pushmirror.go +++ b/models/repo/pushmirror.go @@ -127,7 +127,7 @@ func GetPushMirrorsSyncedOnCommit(ctx context.Context, repoID int64) ([]*PushMir } // PushMirrorsIterate iterates all push-mirror repositories. -func PushMirrorsIterate(ctx context.Context, limit int, f func(idx int, bean interface{}) error) error { +func PushMirrorsIterate(ctx context.Context, limit int, f func(idx int, bean any) error) error { sess := db.GetEngine(ctx). Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()). And("`interval` != 0"). diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go index 2b3c5be292c7e..9ab70235913d4 100644 --- a/models/repo/pushmirror_test.go +++ b/models/repo/pushmirror_test.go @@ -41,7 +41,7 @@ func TestPushMirrorsIterate(t *testing.T) { time.Sleep(1 * time.Millisecond) - repo_model.PushMirrorsIterate(db.DefaultContext, 1, func(idx int, bean interface{}) error { + repo_model.PushMirrorsIterate(db.DefaultContext, 1, func(idx int, bean any) error { m, ok := bean.(*repo_model.PushMirror) assert.True(t, ok) assert.Equal(t, "test-1", m.RemoteName) diff --git a/models/repo/release.go b/models/repo/release.go index b77490584f901..c63b32445767a 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -339,7 +339,7 @@ func (s releaseMetaSearch) Less(i, j int) bool { // GetReleaseAttachments retrieves the attachments for releases func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) { if len(rels) == 0 { - return + return nil } // To keep this efficient as possible sort all releases by id, @@ -442,7 +442,7 @@ func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, origi _, err := db.GetEngine(db.DefaultContext).Table("release"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). - Update(map[string]interface{}{ + Update(map[string]any{ "publisher_id": posterID, "original_author": "", "original_author_id": 0, diff --git a/models/repo/repo.go b/models/repo/repo.go index d3e6daa95b53a..3d1f2dcfa8a67 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" @@ -163,6 +164,8 @@ type Repository struct { IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"` TemplateID int64 `xorm:"INDEX"` Size int64 `xorm:"NOT NULL DEFAULT 0"` + GitSize int64 `xorm:"NOT NULL DEFAULT 0"` + LFSSize int64 `xorm:"NOT NULL DEFAULT 0"` CodeIndexerStatus *RepoIndexerStatus `xorm:"-"` StatsIndexerStatus *RepoIndexerStatus `xorm:"-"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` @@ -196,6 +199,42 @@ func (repo *Repository) SanitizedOriginalURL() string { return u.String() } +// text representations to be returned in SizeDetail.Name +const ( + SizeDetailNameGit = "git" + SizeDetailNameLFS = "lfs" +) + +type SizeDetail struct { + Name string + Size int64 +} + +// SizeDetails forms a struct with various size details about repository +func (repo *Repository) SizeDetails() []SizeDetail { + sizeDetails := []SizeDetail{ + { + Name: SizeDetailNameGit, + Size: repo.GitSize, + }, + { + Name: SizeDetailNameLFS, + Size: repo.LFSSize, + }, + } + return sizeDetails +} + +// SizeDetailsString returns a concatenation of all repository size details as a string +func (repo *Repository) SizeDetailsString() string { + var str strings.Builder + sizeDetails := repo.SizeDetails() + for _, detail := range sizeDetails { + str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size))) + } + return strings.TrimSuffix(str.String(), ", ") +} + func (repo *Repository) LogString() string { if repo == nil { return "" @@ -489,6 +528,18 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID)) } +func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, branchName string) string { + if baseRepo == nil { + baseRepo = repo + } + var cmpBranchEscaped string + if repo.ID != baseRepo.ID { + cmpBranchEscaped = fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name)) + } + cmpBranchEscaped = fmt.Sprintf("%s%s", cmpBranchEscaped, util.PathEscapeSegments(branchName)) + return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseRepo.DefaultBranch), cmpBranchEscaped) +} + // IsOwnedBy returns true when user owns this repository func (repo *Repository) IsOwnedBy(userID int64) bool { return repo.OwnerID == userID diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 92b9c15b4bd8d..83ba02e31653a 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -560,7 +560,7 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c opts.OrderBy = db.SearchOrderByAlphabetically } - args := make([]interface{}, 0) + 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)) args = append(args, opts.PriorityOwnerID) diff --git a/models/repo/update.go b/models/repo/update.go index 4894e0a1b9c5f..c4fba32ad205c 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -185,9 +185,11 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s } // UpdateRepoSize updates the repository size, calculating it using getDirectorySize -func UpdateRepoSize(ctx context.Context, repoID, size int64) error { - _, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{ - Size: size, +func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error { + _, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{ + Size: gitSize + lfsSize, + GitSize: gitSize, + LFSSize: lfsSize, }) return err } diff --git a/models/system/notice.go b/models/system/notice.go index e598abe22251a..784ad74375bd9 100644 --- a/models/system/notice.go +++ b/models/system/notice.go @@ -43,7 +43,7 @@ func (n *Notice) TrStr() string { } // CreateNotice creates new system notice. -func CreateNotice(ctx context.Context, tp NoticeType, desc string, args ...interface{}) error { +func CreateNotice(ctx context.Context, tp NoticeType, desc string, args ...any) error { if len(args) > 0 { desc = fmt.Sprintf(desc, args...) } @@ -55,7 +55,7 @@ func CreateNotice(ctx context.Context, tp NoticeType, desc string, args ...inter } // CreateRepositoryNotice creates new system notice with type NoticeRepository. -func CreateRepositoryNotice(desc string, args ...interface{}) error { +func CreateRepositoryNotice(desc string, args ...any) error { // Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled return CreateNotice(db.DefaultContext, NoticeRepository, desc, args...) } diff --git a/models/unittest/consistency.go b/models/unittest/consistency.go index 41798c62536f5..faa02589aabde 100644 --- a/models/unittest/consistency.go +++ b/models/unittest/consistency.go @@ -21,10 +21,10 @@ const ( modelsCommentTypeComment = 0 ) -var consistencyCheckMap = make(map[string]func(t assert.TestingT, bean interface{})) +var consistencyCheckMap = make(map[string]func(t assert.TestingT, bean any)) // CheckConsistencyFor test that all matching database entries are consistent -func CheckConsistencyFor(t assert.TestingT, beansToCheck ...interface{}) { +func CheckConsistencyFor(t assert.TestingT, beansToCheck ...any) { for _, bean := range beansToCheck { sliceType := reflect.SliceOf(reflect.TypeOf(bean)) sliceValue := reflect.MakeSlice(sliceType, 0, 10) @@ -42,7 +42,7 @@ func CheckConsistencyFor(t assert.TestingT, beansToCheck ...interface{}) { } } -func checkForConsistency(t assert.TestingT, bean interface{}) { +func checkForConsistency(t assert.TestingT, bean any) { tb, err := db.TableInfo(bean) assert.NoError(t, err) f := consistencyCheckMap[tb.Name] @@ -63,7 +63,7 @@ func init() { return i } - checkForUserConsistency := func(t assert.TestingT, bean interface{}) { + checkForUserConsistency := func(t assert.TestingT, bean any) { user := reflectionWrap(bean) AssertCountByCond(t, "repository", builder.Eq{"owner_id": user.int("ID")}, user.int("NumRepos")) AssertCountByCond(t, "star", builder.Eq{"uid": user.int("ID")}, user.int("NumStars")) @@ -77,7 +77,7 @@ func init() { } } - checkForRepoConsistency := func(t assert.TestingT, bean interface{}) { + checkForRepoConsistency := func(t assert.TestingT, bean any) { repo := reflectionWrap(bean) assert.Equal(t, repo.str("LowerName"), strings.ToLower(repo.str("Name")), "repo: %+v", repo) AssertCountByCond(t, "star", builder.Eq{"repo_id": repo.int("ID")}, repo.int("NumStars")) @@ -113,7 +113,7 @@ func init() { "Unexpected number of closed milestones for repo id: %d", repo.int("ID")) } - checkForIssueConsistency := func(t assert.TestingT, bean interface{}) { + checkForIssueConsistency := func(t assert.TestingT, bean any) { issue := reflectionWrap(bean) typeComment := modelsCommentTypeComment actual := GetCountByCond(t, "comment", builder.Eq{"`type`": typeComment, "issue_id": issue.int("ID")}) @@ -124,14 +124,14 @@ func init() { } } - checkForPullRequestConsistency := func(t assert.TestingT, bean interface{}) { + checkForPullRequestConsistency := func(t assert.TestingT, bean any) { pr := reflectionWrap(bean) issueRow := AssertExistsAndLoadMap(t, "issue", builder.Eq{"id": pr.int("IssueID")}) assert.True(t, parseBool(issueRow["is_pull"])) assert.EqualValues(t, parseInt(issueRow["index"]), pr.int("Index"), "Unexpected index for pull request id: %d", pr.int("ID")) } - checkForMilestoneConsistency := func(t assert.TestingT, bean interface{}) { + checkForMilestoneConsistency := func(t assert.TestingT, bean any) { milestone := reflectionWrap(bean) AssertCountByCond(t, "issue", builder.Eq{"milestone_id": milestone.int("ID")}, milestone.int("NumIssues")) @@ -145,7 +145,7 @@ func init() { assert.Equal(t, completeness, milestone.int("Completeness")) } - checkForLabelConsistency := func(t assert.TestingT, bean interface{}) { + checkForLabelConsistency := func(t assert.TestingT, bean any) { label := reflectionWrap(bean) issueLabels, err := db.GetEngine(db.DefaultContext).Table("issue_label"). Where(builder.Eq{"label_id": label.int("ID")}). @@ -166,13 +166,13 @@ func init() { assert.EqualValues(t, expected, label.int("NumClosedIssues"), "Unexpected number of closed issues for label id: %d", label.int("ID")) } - checkForTeamConsistency := func(t assert.TestingT, bean interface{}) { + checkForTeamConsistency := func(t assert.TestingT, bean any) { team := reflectionWrap(bean) AssertCountByCond(t, "team_user", builder.Eq{"team_id": team.int("ID")}, team.int("NumMembers")) AssertCountByCond(t, "team_repo", builder.Eq{"team_id": team.int("ID")}, team.int("NumRepos")) } - checkForActionConsistency := func(t assert.TestingT, bean interface{}) { + checkForActionConsistency := func(t assert.TestingT, bean any) { action := reflectionWrap(bean) if action.int("RepoID") != 1700 { // dangling intentional repoRow := AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": action.int("RepoID")}) diff --git a/models/unittest/reflection.go b/models/unittest/reflection.go index 1b149b19fe072..141fc66b9981a 100644 --- a/models/unittest/reflection.go +++ b/models/unittest/reflection.go @@ -23,7 +23,7 @@ type reflectionValue struct { v reflect.Value } -func reflectionWrap(v interface{}) *reflectionValue { +func reflectionWrap(v any) *reflectionValue { return &reflectionValue{v: reflect.ValueOf(v)} } diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index f926a65538efa..1ff0fdc25bda8 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -37,7 +37,7 @@ func FixturesDir() string { return fixturesDir } -func fatalTestError(fmtStr string, args ...interface{}) { +func fatalTestError(fmtStr string, args ...any) { _, _ = fmt.Fprintf(os.Stderr, fmtStr, args...) os.Exit(1) } diff --git a/models/user/external_login_user.go b/models/user/external_login_user.go index f70f3effcc520..3a56240435f87 100644 --- a/models/user/external_login_user.go +++ b/models/user/external_login_user.go @@ -57,11 +57,11 @@ func (err ErrExternalLoginUserNotExist) Unwrap() error { // ExternalLoginUser makes the connecting between some existing user and additional external login sources type ExternalLoginUser struct { - ExternalID string `xorm:"pk NOT NULL"` - UserID int64 `xorm:"INDEX NOT NULL"` - LoginSourceID int64 `xorm:"pk NOT NULL"` - RawData map[string]interface{} `xorm:"TEXT JSON"` - Provider string `xorm:"index VARCHAR(25)"` + ExternalID string `xorm:"pk NOT NULL"` + UserID int64 `xorm:"INDEX NOT NULL"` + LoginSourceID int64 `xorm:"pk NOT NULL"` + RawData map[string]any `xorm:"TEXT JSON"` + Provider string `xorm:"index VARCHAR(25)"` Email string Name string FirstName string diff --git a/models/user/redirect.go b/models/user/redirect.go index 9d8903c05994a..42e991888a69e 100644 --- a/models/user/redirect.go +++ b/models/user/redirect.go @@ -64,6 +64,10 @@ func NewUserRedirect(ctx context.Context, ID int64, oldUserName, newUserName str oldUserName = strings.ToLower(oldUserName) newUserName = strings.ToLower(newUserName) + if err := DeleteUserRedirect(ctx, oldUserName); err != nil { + return err + } + if err := DeleteUserRedirect(ctx, newUserName); err != nil { return err } diff --git a/models/user/user.go b/models/user/user.go index 2077d55f513e0..4b19eda67b61c 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -336,7 +336,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO // GetUserFollowing returns range of user's following. func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { - sess := db.GetEngine(db.DefaultContext). + sess := db.GetEngine(ctx). Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). @@ -1171,9 +1171,9 @@ func GetUserByOpenID(uri string) (*User, error) { } // GetAdminUser returns the first administrator -func GetAdminUser() (*User, error) { +func GetAdminUser(ctx context.Context) (*User, error) { var admin User - has, err := db.GetEngine(db.DefaultContext). + has, err := db.GetEngine(ctx). Where("is_admin=?", true). Asc("id"). // Reliably get the admin with the lowest ID. Get(&admin) diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go index f9fc886826394..3ece5f062d921 100644 --- a/models/webhook/hooktask.go +++ b/models/webhook/hooktask.go @@ -92,7 +92,7 @@ func (t *HookTask) AfterLoad() { } } -func (t *HookTask) simpleMarshalJSON(v interface{}) string { +func (t *HookTask) simpleMarshalJSON(v any) string { p, err := json.Marshal(v) if err != nil { log.Error("Marshal [%d]: %v", t.ID, err) diff --git a/modules/actions/log.go b/modules/actions/log.go index 3868101992a95..cdf18646aaf02 100644 --- a/modules/actions/log.go +++ b/modules/actions/log.go @@ -29,12 +29,28 @@ const ( ) func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) { + flag := os.O_WRONLY + if offset == 0 { + // Create file only if offset is 0, or it could result in content holes if the file doesn't exist. + flag |= os.O_CREATE + } name := DBFSPrefix + filename - f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE) + f, err := dbfs.OpenFile(ctx, name, flag) if err != nil { return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err) } defer f.Close() + + stat, err := f.Stat() + if err != nil { + return nil, fmt.Errorf("dbfs Stat %q: %w", name, err) + } + if stat.Size() < offset { + // If the size is less than offset, refuse to write, or it could result in content holes. + // However, if the size is greater than offset, we can still write to overwrite the content. + return nil, fmt.Errorf("size of %q is less than offset", name) + } + if _, err := f.Seek(offset, io.SeekStart); err != nil { return nil, fmt.Errorf("dbfs Seek %q: %w", name, err) } @@ -57,7 +73,7 @@ func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runne } func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) { - f, err := openLogs(ctx, inStorage, filename) + f, err := OpenLogs(ctx, inStorage, filename) if err != nil { return nil, err } @@ -125,7 +141,7 @@ func RemoveLogs(ctx context.Context, inStorage bool, filename string) error { return nil } -func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) { +func OpenLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) { if !inStorage { name := DBFSPrefix + filename f, err := dbfs.Open(ctx, name) diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 3786f2a274ce3..2c7cec5591de3 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -23,13 +23,11 @@ import ( type DetectedWorkflow struct { EntryName string TriggerEvent string - Commit *git.Commit - Ref string Content []byte } func init() { - model.OnDecodeNodeError = func(node yaml.Node, out interface{}, err error) { + model.OnDecodeNodeError = func(node yaml.Node, out any, err error) { // Log the error instead of panic or fatal. // It will be a big job to refactor act/pkg/model to return decode error, // so we just log the error and return empty value, and improve it later. @@ -120,7 +118,6 @@ func DetectWorkflows(commit *git.Commit, triggedEvent webhook_module.HookEventTy dwf := &DetectedWorkflow{ EntryName: entry.Name(), TriggerEvent: evt.Name, - Commit: commit, Content: content, } workflows = append(workflows, dwf) @@ -335,44 +332,47 @@ func matchIssuesEvent(commit *git.Commit, issuePayload *api.IssuePayload, evt *j } func matchPullRequestEvent(commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool { - // with no special filter parameters - if len(evt.Acts()) == 0 { + acts := evt.Acts() + activityTypeMatched := false + matchTimes := 0 + + if vals, ok := acts["types"]; !ok { // defaultly, only pull request `opened`, `reopened` and `synchronized` will trigger workflow // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request - return prPayload.Action == api.HookIssueSynchronized || prPayload.Action == api.HookIssueOpened || prPayload.Action == api.HookIssueReOpened + activityTypeMatched = prPayload.Action == api.HookIssueSynchronized || prPayload.Action == api.HookIssueOpened || prPayload.Action == api.HookIssueReOpened + } else { + // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request + // Actions with the same name: + // opened, edited, closed, reopened, assigned, unassigned + // Actions need to be converted: + // synchronized -> synchronize + // label_updated -> labeled + // label_cleared -> unlabeled + // Unsupported activity types: + // converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled + + action := prPayload.Action + switch action { + case api.HookIssueSynchronized: + action = "synchronize" + case api.HookIssueLabelUpdated: + action = "labeled" + case api.HookIssueLabelCleared: + action = "unlabeled" + } + log.Trace("matching pull_request %s with %v", action, vals) + for _, val := range vals { + if glob.MustCompile(val, '/').Match(string(action)) { + activityTypeMatched = true + matchTimes++ + break + } + } } - matchTimes := 0 // all acts conditions should be satisfied - for cond, vals := range evt.Acts() { + for cond, vals := range acts { switch cond { - case "types": - // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request - // Actions with the same name: - // opened, edited, closed, reopened, assigned, unassigned - // Actions need to be converted: - // synchronized -> synchronize - // label_updated -> labeled - // label_cleared -> unlabeled - // Unsupported activity types: - // converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled - - action := prPayload.Action - switch action { - case api.HookIssueSynchronized: - action = "synchronize" - case api.HookIssueLabelUpdated: - action = "labeled" - case api.HookIssueLabelCleared: - action = "unlabeled" - } - log.Trace("matching pull_request %s with %v", action, vals) - for _, val := range vals { - if glob.MustCompile(val, '/').Match(string(action)) { - matchTimes++ - break - } - } case "branches": refName := git.RefName(prPayload.PullRequest.Base.Ref) patterns, err := workflowpattern.CompilePatterns(vals...) @@ -421,7 +421,7 @@ func matchPullRequestEvent(commit *git.Commit, prPayload *api.PullRequestPayload log.Warn("pull request event unsupported condition %q", cond) } } - return matchTimes == len(evt.Acts()) + return activityTypeMatched && matchTimes == len(evt.Acts()) } func matchIssueCommentEvent(commit *git.Commit, issueCommentPayload *api.IssueCommentPayload, evt *jobparser.Event) bool { diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 2c374d2c0d0fc..ef553c4a57267 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -57,6 +57,25 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on: pull_request", expected: false, }, + { + desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with no activity type", + triggedEvent: webhook_module.HookEventPullRequest, + payload: &api.PullRequestPayload{Action: api.HookIssueClosed}, + yamlOn: "on: pull_request", + expected: false, + }, + { + desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with branches", + triggedEvent: webhook_module.HookEventPullRequest, + payload: &api.PullRequestPayload{ + Action: api.HookIssueClosed, + PullRequest: &api.PullRequest{ + Base: &api.PRBranchInfo{}, + }, + }, + yamlOn: "on:\n pull_request:\n branches: [main]", + expected: false, + }, { desc: "HookEventPullRequest(pull_request) `label_updated` action matches GithubEventPullRequest(pull_request) with `label` activity type", triggedEvent: webhook_module.HookEventPullRequest, diff --git a/modules/avatar/hash_test.go b/modules/avatar/hash_test.go new file mode 100644 index 0000000000000..1b8249c696c3a --- /dev/null +++ b/modules/avatar/hash_test.go @@ -0,0 +1,26 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package avatar_test + +import ( + "bytes" + "image" + "image/png" + "testing" + + "code.gitea.io/gitea/modules/avatar" + + "github.com/stretchr/testify/assert" +) + +func Test_HashAvatar(t *testing.T) { + myImage := image.NewRGBA(image.Rect(0, 0, 32, 32)) + var buff bytes.Buffer + png.Encode(&buff, myImage) + + assert.EqualValues(t, "9ddb5bac41d57e72aa876321d0c09d71090c05f94bc625303801be2f3240d2cb", avatar.HashAvatar(1, buff.Bytes())) + assert.EqualValues(t, "9a5d44e5d637b9582a976676e8f3de1dccd877c2fe3e66ca3fab1629f2f47609", avatar.HashAvatar(8, buff.Bytes())) + assert.EqualValues(t, "ed7399158672088770de6f5211ce15528ebd675e92fc4fc060c025f4b2794ccb", avatar.HashAvatar(1024, buff.Bytes())) + assert.EqualValues(t, "161178642c7d59eb25a61dddced5e6b66eae1c70880d5f148b1b497b767e72d9", avatar.HashAvatar(1024, []byte{})) +} diff --git a/modules/base/tool.go b/modules/base/tool.go index 004781835a59a..71dcb83fb4850 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -107,7 +107,7 @@ const TimeLimitCodeLength = 12 + 6 + 40 // CreateTimeLimitCode create a time limit code // code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string -func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string { +func CreateTimeLimitCode(data string, minutes int, startInf any) string { format := "200601021504" var start, end time.Time @@ -245,7 +245,7 @@ func SetupGiteaRoot() string { } // FormatNumberSI format a number -func FormatNumberSI(data interface{}) string { +func FormatNumberSI(data any) string { var num int64 if num1, ok := data.(int64); ok { num = num1 diff --git a/modules/cache/cache_redis.go b/modules/cache/cache_redis.go index f22482de48a2f..6c358b0a78a4d 100644 --- a/modules/cache/cache_redis.go +++ b/modules/cache/cache_redis.go @@ -24,7 +24,7 @@ type RedisCacher struct { } // toStr convert string/int/int64 interface to string. it's only used by the RedisCacher.Put internally -func toStr(v interface{}) string { +func toStr(v any) string { if v == nil { return "" } @@ -44,7 +44,7 @@ func toStr(v interface{}) string { // Put puts value (string type) into cache with key and expire time. // If expired is 0, it lives forever. -func (c *RedisCacher) Put(key string, val interface{}, expire int64) error { +func (c *RedisCacher) Put(key string, val any, expire int64) error { // this function is not well-designed, it only puts string values into cache key = c.prefix + key if expire == 0 { @@ -65,7 +65,7 @@ func (c *RedisCacher) Put(key string, val interface{}, expire int64) error { } // Get gets cached value by given key. -func (c *RedisCacher) Get(key string) interface{} { +func (c *RedisCacher) Get(key string) any { val, err := c.c.Get(graceful.GetManager().HammerContext(), c.prefix+key).Result() if err != nil { return nil diff --git a/modules/cache/cache_twoqueue.go b/modules/cache/cache_twoqueue.go index 2e2efd00f2401..184db255736f4 100644 --- a/modules/cache/cache_twoqueue.go +++ b/modules/cache/cache_twoqueue.go @@ -30,7 +30,7 @@ type TwoQueueCacheConfig struct { // MemoryItem represents a memory cache item. type MemoryItem struct { - Val interface{} + Val any Created int64 Timeout int64 } @@ -43,7 +43,7 @@ func (item *MemoryItem) hasExpired() bool { var _ mc.Cache = &TwoQueueCache{} // Put puts value into cache with key and expire time. -func (c *TwoQueueCache) Put(key string, val interface{}, timeout int64) error { +func (c *TwoQueueCache) Put(key string, val any, timeout int64) error { item := &MemoryItem{ Val: val, Created: time.Now().Unix(), @@ -56,7 +56,7 @@ func (c *TwoQueueCache) Put(key string, val interface{}, timeout int64) error { } // Get gets cached value by given key. -func (c *TwoQueueCache) Get(key string) interface{} { +func (c *TwoQueueCache) Get(key string) any { c.lock.Lock() defer c.lock.Unlock() cached, ok := c.cache.Get(key) @@ -146,7 +146,7 @@ func (c *TwoQueueCache) Flush() error { return nil } -func (c *TwoQueueCache) checkAndInvalidate(key interface{}) { +func (c *TwoQueueCache) checkAndInvalidate(key any) { c.lock.Lock() defer c.lock.Unlock() cached, ok := c.cache.Peek(key) diff --git a/modules/charset/ambiguous/generate.go b/modules/charset/ambiguous/generate.go index b0c2531008203..e3fda5be982ef 100644 --- a/modules/charset/ambiguous/generate.go +++ b/modules/charset/ambiguous/generate.go @@ -90,7 +90,7 @@ Usage: %[1]s [-v] [-o output.go] ambiguous.json sort.Slice(tables, func(i, j int) bool { return tables[i].Locale < tables[j].Locale }) - data := map[string]interface{}{ + data := map[string]any{ "Tables": tables, } @@ -99,7 +99,7 @@ Usage: %[1]s [-v] [-o output.go] ambiguous.json } } -func runTemplate(t *template.Template, filename string, data interface{}) error { +func runTemplate(t *template.Template, filename string, data any) error { buf := bytes.NewBuffer(nil) if err := t.Execute(buf, data); err != nil { return fmt.Errorf("unable to execute template: %w", err) @@ -172,17 +172,17 @@ var AmbiguousCharacters = map[string]*AmbiguousTable{ `)) -func logf(format string, args ...interface{}) { +func logf(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) } -func verbosef(format string, args ...interface{}) { +func verbosef(format string, args ...any) { if verbose { logf(format, args...) } } -func fatalf(format string, args ...interface{}) { +func fatalf(format string, args ...any) { logf("fatal: "+format+"\n", args...) os.Exit(1) } diff --git a/modules/charset/invisible/generate.go b/modules/charset/invisible/generate.go index 9dfc253333559..bd57dd6c4dd61 100644 --- a/modules/charset/invisible/generate.go +++ b/modules/charset/invisible/generate.go @@ -52,7 +52,7 @@ Usage: %[1]s [-v] [-o output.go] } } -func runTemplate(t *template.Template, filename string, data interface{}) error { +func runTemplate(t *template.Template, filename string, data any) error { buf := bytes.NewBuffer(nil) if err := t.Execute(buf, data); err != nil { return fmt.Errorf("unable to execute template: %w", err) @@ -105,17 +105,17 @@ var InvisibleRanges = &unicode.RangeTable{ } `)) -func logf(format string, args ...interface{}) { +func logf(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) } -func verbosef(format string, args ...interface{}) { +func verbosef(format string, args ...any) { if verbose { logf(format, args...) } } -func fatalf(format string, args ...interface{}) { +func fatalf(format string, args ...any) { logf("fatal: "+format+"\n", args...) os.Exit(1) } diff --git a/modules/context/access_log.go b/modules/context/access_log.go index 373574ba148f4..0926748ac540f 100644 --- a/modules/context/access_log.go +++ b/modules/context/access_log.go @@ -23,7 +23,7 @@ type routerLoggerOptions struct { Identity *string Start *time.Time ResponseWriter http.ResponseWriter - Ctx map[string]interface{} + Ctx map[string]any RequestID *string } @@ -84,7 +84,7 @@ func AccessLogger() func(http.Handler) http.Handler { Identity: &identity, Start: &start, ResponseWriter: rw, - Ctx: map[string]interface{}{ + Ctx: map[string]any{ "RemoteAddr": req.RemoteAddr, "RemoteHost": reqHost, "Req": req, diff --git a/modules/context/api.go b/modules/context/api.go index 3c4d02041334e..93a587d436933 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -108,7 +108,7 @@ func (ctx *APIContext) ServerError(title string, err error) { // Error responds with an error message to client with given obj as the message. // If status is 500, also it prints error to log. -func (ctx *APIContext) Error(status int, title string, obj interface{}) { +func (ctx *APIContext) Error(status int, title string, obj any) { var message string if err, ok := obj.(error); ok { message = err.Error() @@ -265,7 +265,7 @@ func APIContexter() func(http.Handler) http.Handler { // NotFound handles 404s for APIContext // String will replace message, errors will be added to a slice -func (ctx *APIContext) NotFound(objs ...interface{}) { +func (ctx *APIContext) NotFound(objs ...any) { message := ctx.Tr("error.not_found") var errors []string for _, obj := range objs { @@ -281,7 +281,7 @@ func (ctx *APIContext) NotFound(objs ...interface{}) { } } - ctx.JSON(http.StatusNotFound, map[string]interface{}{ + ctx.JSON(http.StatusNotFound, map[string]any{ "message": message, "url": setting.API.SwaggerURL, "errors": errors, diff --git a/modules/context/base.go b/modules/context/base.go index 839f3e10df480..8566ef7861bae 100644 --- a/modules/context/base.go +++ b/modules/context/base.go @@ -128,7 +128,7 @@ func (b *Base) Error(status int, contents ...string) { } // JSON render content as JSON -func (b *Base) JSON(status int, content interface{}) { +func (b *Base) JSON(status int, content any) { b.Resp.Header().Set("Content-Type", "application/json;charset=utf-8") b.Resp.WriteHeader(status) if err := json.NewEncoder(b.Resp).Encode(content); err != nil { diff --git a/modules/context/captcha.go b/modules/context/captcha.go index 07232e9390663..a1999900c94de 100644 --- a/modules/context/captcha.go +++ b/modules/context/captcha.go @@ -60,7 +60,7 @@ const ( // VerifyCaptcha verifies Captcha data // No-op if captchas are not enabled -func VerifyCaptcha(ctx *Context, tpl base.TplName, form interface{}) { +func VerifyCaptcha(ctx *Context, tpl base.TplName, form any) { if !setting.Service.EnableCaptcha { return } diff --git a/modules/context/context.go b/modules/context/context.go index 93d448fca5550..47a04c989c81f 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -32,7 +32,7 @@ import ( // Render represents a template render type Render interface { TemplateLookup(tmpl string) (templates.TemplateExecutor, error) - HTML(w io.Writer, status int, name string, data interface{}) error + HTML(w io.Writer, status int, name string, data any) error } // Context represents context of a request. @@ -69,7 +69,7 @@ func init() { // TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString. // This is useful if the locale message is intended to only produce HTML content. func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string { - trArgs := make([]interface{}, len(args)) + trArgs := make([]any, len(args)) for i, arg := range args { trArgs[i] = html.EscapeString(arg) } diff --git a/modules/context/context_response.go b/modules/context/context_response.go index 88e375986cd6b..bb3ccf69ce421 100644 --- a/modules/context/context_response.go +++ b/modules/context/context_response.go @@ -91,14 +91,14 @@ func (ctx *Context) HTML(status int, name base.TplName) { } // RenderToString renders the template content to a string -func (ctx *Context) RenderToString(name base.TplName, data map[string]interface{}) (string, error) { +func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) { var buf strings.Builder err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data) return buf.String(), err } // RenderWithErr used for page has form validation but need to prompt error to users. -func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) { +func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form any) { if form != nil { middleware.AssignForm(form, ctx.Data) } diff --git a/modules/context/org.go b/modules/context/org.go index 355ba0ebd01f5..835c761372fa9 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -161,7 +161,6 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { } ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember - ctx.Data["IsProjectEnabled"] = true ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsPublicMember"] = func(uid int64) bool { diff --git a/modules/context/package.go b/modules/context/package.go index 8052032787117..8e80fa66ec43d 100644 --- a/modules/context/package.go +++ b/modules/context/package.go @@ -33,7 +33,7 @@ type packageAssignmentCtx struct { // PackageAssignment returns a middleware to handle Context.Package assignment func PackageAssignment() func(ctx *Context) { return func(ctx *Context) { - errorFn := func(status int, title string, obj interface{}) { + errorFn := func(status int, title string, obj any) { err, ok := obj.(error) if !ok { err = fmt.Errorf("%s", obj) @@ -57,7 +57,7 @@ func PackageAssignmentAPI() func(ctx *APIContext) { } } -func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, interface{})) *Package { +func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, any)) *Package { pkg := &Package{ Owner: ctx.ContextUser, } diff --git a/modules/context/pagination.go b/modules/context/pagination.go index 5a88c92053aa7..68237c630c0ae 100644 --- a/modules/context/pagination.go +++ b/modules/context/pagination.go @@ -32,7 +32,7 @@ func (p *Pagination) AddParam(ctx *Context, paramKey, ctxKey string) { if !exists { return } - paramData := fmt.Sprintf("%v", ctx.Data[ctxKey]) // cast interface{} to string + paramData := fmt.Sprintf("%v", ctx.Data[ctxKey]) // cast any to string urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(paramKey), url.QueryEscape(paramData)) p.urlParams = append(p.urlParams, urlParam) } diff --git a/modules/context/permission.go b/modules/context/permission.go index 0f72b8e244d4c..09343b8b50bc0 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -90,7 +90,7 @@ func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) { } if log.IsTrace() { var format string - var args []interface{} + var args []any if ctx.IsSigned { format = "Permission Denied: User %-v cannot read [" args = append(args, ctx.Doer) diff --git a/modules/context/private.go b/modules/context/private.go index 2e9b31140ab56..8b41949f604e1 100644 --- a/modules/context/private.go +++ b/modules/context/private.go @@ -53,7 +53,7 @@ func (ctx *PrivateContext) Err() error { return ctx.Base.Err() } -var privateContextKey interface{} = "default_private_context" +var privateContextKey any = "default_private_context" // GetPrivateContext returns a context for Private routes func GetPrivateContext(req *http.Request) *PrivateContext { diff --git a/modules/context/repo.go b/modules/context/repo.go index 003309f1b01ec..eae71cfb7be67 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -422,10 +422,10 @@ func RepoIDAssignment() func(ctx *Context) { } // RepoAssignment returns a middleware to handle repository assignment -func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { +func RepoAssignment(ctx *Context) context.CancelFunc { if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce { log.Trace("RepoAssignment was exec already, skipping second call ...") - return + return nil } ctx.Data["repoAssignmentExecuted"] = true @@ -453,7 +453,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { // https://github.com/golang/go/issues/19760 if ctx.FormString("go-get") == "1" { EarlyResponseForGoGetMeta(ctx) - return + return nil } if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil { @@ -466,7 +466,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } else { ctx.ServerError("GetUserByName", err) } - return + return nil } } ctx.Repo.Owner = owner @@ -490,7 +490,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { redirectPath += "?" + ctx.Req.URL.RawQuery } ctx.Redirect(path.Join(setting.AppSubURL, redirectPath)) - return + return nil } // Get repository. @@ -503,7 +503,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } else if repo_model.IsErrRedirectNotExist(err) { if ctx.FormString("go-get") == "1" { EarlyResponseForGoGetMeta(ctx) - return + return nil } ctx.NotFound("GetRepositoryByName", nil) } else { @@ -512,13 +512,13 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { } else { ctx.ServerError("GetRepositoryByName", err) } - return + return nil } repo.Owner = owner repoAssignment(ctx, repo) if ctx.Written() { - return + return nil } ctx.Repo.RepoLink = repo.Link() @@ -542,12 +542,12 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { }) if err != nil { ctx.ServerError("GetReleaseCountByRepoID", err) - return + return nil } ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{}) if err != nil { ctx.ServerError("GetReleaseCountByRepoID", err) - return + return nil } ctx.Data["Title"] = owner.Name + "/" + repo.Name @@ -563,14 +563,14 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { canSignedUserFork, err := repo_module.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository) if err != nil { ctx.ServerError("CanUserForkRepo", err) - return + return nil } ctx.Data["CanSignedUserFork"] = canSignedUserFork userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository) if err != nil { ctx.ServerError("GetForksByUserAndOrgs", err) - return + return nil } ctx.Data["UserAndOrgForks"] = userAndOrgForks @@ -604,14 +604,14 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { if repo.IsFork { RetrieveBaseRepo(ctx, repo) if ctx.Written() { - return + return nil } } if repo.IsGenerated() { RetrieveTemplateRepo(ctx, repo) if ctx.Written() { - return + return nil } } @@ -623,7 +623,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { if !isHomeOrSettings { ctx.Redirect(ctx.Repo.RepoLink) } - return + return nil } gitRepo, err := git.OpenRepository(ctx, repo_model.RepoPath(userName, repoName)) @@ -636,10 +636,10 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { if !isHomeOrSettings { ctx.Redirect(ctx.Repo.RepoLink) } - return + return nil } ctx.ServerError("RepoAssignment Invalid repo "+repo_model.RepoPath(userName, repoName), err) - return + return nil } if ctx.Repo.GitRepo != nil { ctx.Repo.GitRepo.Close() @@ -647,7 +647,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { ctx.Repo.GitRepo = gitRepo // We opened it, we should close it - cancel = func() { + cancel := func() { // If it's been set to nil then assume someone else has closed it. if ctx.Repo.GitRepo != nil { ctx.Repo.GitRepo.Close() @@ -657,23 +657,48 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { // Stop at this point when the repo is empty. if ctx.Repo.Repository.IsEmpty { ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch - return + return cancel } tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("GetTagNamesByRepoID", err) - return + return cancel } ctx.Data["Tags"] = tags - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) + branchOpts := git_model.FindBranchOptions{ + RepoID: ctx.Repo.Repository.ID, + IsDeletedBranch: util.OptionalBoolFalse, + ListOptions: db.ListOptions{ + ListAll: true, + }, + } + branchesTotal, err := git_model.CountBranches(ctx, branchOpts) + if err != nil { + ctx.ServerError("CountBranches", err) + return cancel + } + + // non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet + if branchesTotal == 0 { // fallback to do a sync immediately + branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) + if err != nil { + ctx.ServerError("SyncRepoBranches", err) + return cancel + } + } + + // FIXME: use paganation and async loading + branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch} + brs, err := git_model.FindBranchNames(ctx, branchOpts) if err != nil { ctx.ServerError("GetBranches", err) - return + return cancel } - ctx.Data["Branches"] = brs - ctx.Data["BranchesCount"] = len(brs) + // always put default branch on the top + ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...) + ctx.Data["BranchesCount"] = branchesTotal // If not branch selected, try default one. // If default branch doesn't exist, fall back to some other branch. @@ -716,12 +741,12 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) if err != nil { ctx.ServerError("GetPendingRepositoryTransfer", err) - return + return cancel } if err := repoTransfer.LoadAttributes(ctx); err != nil { ctx.ServerError("LoadRecipient", err) - return + return cancel } ctx.Data["RepoTransfer"] = repoTransfer @@ -869,7 +894,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Repo.IsViewBranch = true ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch ctx.Data["TreePath"] = "" - return + return nil } var ( @@ -882,7 +907,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Repo.GitRepo, err = git.OpenRepository(ctx, repoPath) if err != nil { ctx.ServerError("RepoRef Invalid repo "+repoPath, err) - return + return nil } // We opened it, we should close it cancel = func() { @@ -897,9 +922,9 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context if len(ctx.Params("*")) == 0 { refName = ctx.Repo.Repository.DefaultBranch if !ctx.Repo.GitRepo.IsBranchExist(refName) { - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) + brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1) if err == nil && len(brs) != 0 { - refName = brs[0] + refName = brs[0].Name } else if len(brs) == 0 { log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) ctx.Repo.Repository.MarkAsBrokenEmpty() @@ -919,7 +944,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Repo.Repository.MarkAsBrokenEmpty() } else { ctx.ServerError("GetBranchCommit", err) - return + return cancel } ctx.Repo.IsViewBranch = true } else { @@ -931,7 +956,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName)) link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1) ctx.Redirect(link) - return + return cancel } if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) { @@ -941,7 +966,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) if err != nil { ctx.ServerError("GetBranchCommit", err) - return + return cancel } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() @@ -953,10 +978,10 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context if err != nil { if git.IsErrNotExist(err) { ctx.NotFound("GetTagCommit", err) - return + return cancel } ctx.ServerError("GetTagCommit", err) - return + return cancel } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() } else if len(refName) >= 7 && len(refName) <= git.SHAFullLength { @@ -966,7 +991,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) if err != nil { ctx.NotFound("GetCommit", err) - return + return cancel } // If short commit ID add canonical link header if len(refName) < git.SHAFullLength { @@ -975,10 +1000,10 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context } } else { if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] { - return + return cancel } ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) - return + return cancel } if refType == RepoRefLegacy { @@ -990,7 +1015,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context util.PathEscapeSegments(prefix), ctx.Repo.BranchNameSubURL(), util.PathEscapeSegments(ctx.Repo.TreePath))) - return + return cancel } } @@ -1008,7 +1033,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() if err != nil { ctx.ServerError("GetCommitsCount", err) - return + return cancel } 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/modules/doctor/fix16961.go b/modules/doctor/fix16961.go index ea14a9b2c4164..562c78dd76597 100644 --- a/modules/doctor/fix16961.go +++ b/modules/doctor/fix16961.go @@ -6,6 +6,7 @@ package doctor import ( "bytes" "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -40,12 +41,12 @@ func parseBool16961(bs []byte) (bool, error) { func fixUnitConfig16961(bs []byte, cfg *repo_model.UnitConfig) (fixed bool, err error) { err = json.UnmarshalHandleDoubleEncode(bs, &cfg) if err == nil { - return + return false, nil } // Handle #16961 if string(bs) != "&{}" && len(bs) != 0 { - return + return false, err } return true, nil @@ -54,14 +55,14 @@ func fixUnitConfig16961(bs []byte, cfg *repo_model.UnitConfig) (fixed bool, err func fixExternalWikiConfig16961(bs []byte, cfg *repo_model.ExternalWikiConfig) (fixed bool, err error) { err = json.UnmarshalHandleDoubleEncode(bs, &cfg) if err == nil { - return + return false, nil } if len(bs) < 3 { - return + return false, err } if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { - return + return false, err } cfg.ExternalWikiURL = string(bs[2 : len(bs)-1]) return true, nil @@ -70,20 +71,20 @@ func fixExternalWikiConfig16961(bs []byte, cfg *repo_model.ExternalWikiConfig) ( func fixExternalTrackerConfig16961(bs []byte, cfg *repo_model.ExternalTrackerConfig) (fixed bool, err error) { err = json.UnmarshalHandleDoubleEncode(bs, &cfg) if err == nil { - return + return false, nil } // Handle #16961 if len(bs) < 3 { - return + return false, err } if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { - return + return false, err } parts := bytes.Split(bs[2:len(bs)-1], []byte{' '}) if len(parts) != 3 { - return + return false, err } cfg.ExternalTrackerURL = string(bytes.Join(parts[:len(parts)-2], []byte{' '})) @@ -95,16 +96,16 @@ func fixExternalTrackerConfig16961(bs []byte, cfg *repo_model.ExternalTrackerCon func fixPullRequestsConfig16961(bs []byte, cfg *repo_model.PullRequestsConfig) (fixed bool, err error) { err = json.UnmarshalHandleDoubleEncode(bs, &cfg) if err == nil { - return + return false, nil } // Handle #16961 if len(bs) < 3 { - return + return false, err } if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { - return + return false, err } // PullRequestsConfig was the following in 1.14 @@ -123,37 +124,37 @@ func fixPullRequestsConfig16961(bs []byte, cfg *repo_model.PullRequestsConfig) ( // DefaultMergeStyle MergeStyle parts := bytes.Split(bs[2:len(bs)-1], []byte{' '}) if len(parts) < 7 { - return + return false, err } var parseErr error cfg.IgnoreWhitespaceConflicts, parseErr = parseBool16961(parts[0]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.AllowMerge, parseErr = parseBool16961(parts[1]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.AllowRebase, parseErr = parseBool16961(parts[2]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.AllowRebaseMerge, parseErr = parseBool16961(parts[3]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.AllowSquash, parseErr = parseBool16961(parts[4]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.AllowManualMerge, parseErr = parseBool16961(parts[5]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.AutodetectManualMerge, parseErr = parseBool16961(parts[6]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } // 1.14 unit @@ -162,12 +163,12 @@ func fixPullRequestsConfig16961(bs []byte, cfg *repo_model.PullRequestsConfig) ( } if len(parts) < 9 { - return + return false, err } cfg.DefaultDeleteBranchAfterMerge, parseErr = parseBool16961(parts[7]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.DefaultMergeStyle = repo_model.MergeStyle(string(bytes.Join(parts[8:], []byte{' '}))) @@ -177,34 +178,34 @@ func fixPullRequestsConfig16961(bs []byte, cfg *repo_model.PullRequestsConfig) ( func fixIssuesConfig16961(bs []byte, cfg *repo_model.IssuesConfig) (fixed bool, err error) { err = json.UnmarshalHandleDoubleEncode(bs, &cfg) if err == nil { - return + return false, nil } // Handle #16961 if len(bs) < 3 { - return + return false, err } if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' { - return + return false, err } parts := bytes.Split(bs[2:len(bs)-1], []byte{' '}) if len(parts) != 3 { - return + return false, err } var parseErr error cfg.EnableTimetracker, parseErr = parseBool16961(parts[0]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.AllowOnlyContributorsToTrackTime, parseErr = parseBool16961(parts[1]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } cfg.EnableDependencies, parseErr = parseBool16961(parts[2]) if parseErr != nil { - return + return false, errors.Join(err, parseErr) } return true, nil } diff --git a/modules/doctor/lfs.go b/modules/doctor/lfs.go index 64ee4c40bfebf..5f110b8f97d14 100644 --- a/modules/doctor/lfs.go +++ b/modules/doctor/lfs.go @@ -31,8 +31,8 @@ func garbageCollectLFSCheck(ctx context.Context, logger log.Logger, autofix bool } if err := repository.GarbageCollectLFSMetaObjects(ctx, repository.GarbageCollectLFSMetaObjectsOptions{ - Logger: logger, - AutoFix: autofix, + LogDetail: logger.Info, + AutoFix: autofix, // Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload // and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby // an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid diff --git a/modules/emoji/emoji_data.go b/modules/emoji/emoji_data.go index a365e423cbe53..8d0ae0a43e677 100644 --- a/modules/emoji/emoji_data.go +++ b/modules/emoji/emoji_data.go @@ -230,6 +230,7 @@ var GemojiData = Gemoji{ {"\U0001f382", "birthday cake", []string{"birthday"}, "6.0", false}, {"\U0001f9ac", "bison", []string{"bison"}, "13.0", false}, {"\U0001fae6", "biting lip", []string{"biting_lip"}, "14.0", false}, + {"\U0001f426\u200d\u2b1b", "black bird", []string{"black_bird"}, "15.0", false}, {"\U0001f408\u200d\u2b1b", "black cat", []string{"black_cat"}, "13.0", false}, {"\u26ab", "black circle", []string{"black_circle"}, "4.1", false}, {"\U0001f3f4", "black flag", []string{"black_flag"}, "7.0", false}, @@ -748,6 +749,7 @@ var GemojiData = Gemoji{ {"\U0001f42c", "dolphin", []string{"dolphin", "flipper"}, "6.0", false}, {"\U0001f1e9\U0001f1f2", "flag: Dominica", []string{"dominica"}, "6.0", false}, {"\U0001f1e9\U0001f1f4", "flag: Dominican Republic", []string{"dominican_republic"}, "6.0", false}, + {"\U0001facf", "donkey", []string{"donkey"}, "15.0", false}, {"\U0001f6aa", "door", []string{"door"}, "6.0", false}, {"\U0001fae5", "dotted line face", []string{"dotted_line_face"}, "14.0", false}, {"\U0001f369", "doughnut", []string{"doughnut"}, "6.0", false}, @@ -982,11 +984,13 @@ var GemojiData = Gemoji{ {"\U0001f4be", "floppy disk", []string{"floppy_disk"}, "6.0", false}, {"\U0001f3b4", "flower playing cards", []string{"flower_playing_cards"}, "6.0", false}, {"\U0001f633", "flushed face", []string{"flushed"}, "6.0", false}, + {"\U0001fa88", "flute", []string{"flute"}, "15.0", false}, {"\U0001fab0", "fly", []string{"fly"}, "13.0", false}, {"\U0001f94f", "flying disc", []string{"flying_disc"}, "11.0", false}, {"\U0001f6f8", "flying saucer", []string{"flying_saucer"}, "11.0", false}, {"\U0001f32b\ufe0f", "fog", []string{"fog"}, "7.0", false}, {"\U0001f301", "foggy", []string{"foggy"}, "6.0", false}, + {"\U0001faad", "folding hand fan", []string{"folding_hand_fan"}, "15.0", false}, {"\U0001fad5", "fondue", []string{"fondue"}, "13.0", false}, {"\U0001f9b6", "foot", []string{"foot"}, "11.0", true}, {"\U0001f9b6\U0001f3ff", "foot: Dark Skin Tone", []string{"foot_Dark_Skin_Tone"}, "12.0", false}, @@ -1054,6 +1058,7 @@ var GemojiData = Gemoji{ {"\U0001f1ec\U0001f1ee", "flag: Gibraltar", []string{"gibraltar"}, "6.0", false}, {"\U0001f381", "wrapped gift", []string{"gift"}, "6.0", false}, {"\U0001f49d", "heart with ribbon", []string{"gift_heart"}, "6.0", false}, + {"\U0001fada", "ginger root", []string{"ginger_root"}, "15.0", false}, {"\U0001f992", "giraffe", []string{"giraffe"}, "11.0", false}, {"\U0001f467", "girl", []string{"girl"}, "6.0", true}, {"\U0001f467\U0001f3ff", "girl: Dark Skin Tone", []string{"girl_Dark_Skin_Tone"}, "12.0", false}, @@ -1085,6 +1090,7 @@ var GemojiData = Gemoji{ {"\U0001f3cc\U0001f3fe\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Dark Skin Tone", []string{"golfing_woman_Medium-Dark_Skin_Tone"}, "12.0", false}, {"\U0001f3cc\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Light Skin Tone", []string{"golfing_woman_Medium-Light_Skin_Tone"}, "12.0", false}, {"\U0001f3cc\U0001f3fd\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium Skin Tone", []string{"golfing_woman_Medium_Skin_Tone"}, "12.0", false}, + {"\U0001fabf", "goose", []string{"goose"}, "15.0", false}, {"\U0001f98d", "gorilla", []string{"gorilla"}, "9.0", false}, {"\U0001f347", "grapes", []string{"grapes"}, "6.0", false}, {"\U0001f1ec\U0001f1f7", "flag: Greece", []string{"greece"}, "6.0", false}, @@ -1097,6 +1103,7 @@ var GemojiData = Gemoji{ {"\U0001f1ec\U0001f1f1", "flag: Greenland", []string{"greenland"}, "6.0", false}, {"\U0001f1ec\U0001f1e9", "flag: Grenada", []string{"grenada"}, "6.0", false}, {"\u2755", "white exclamation mark", []string{"grey_exclamation"}, "6.0", false}, + {"\U0001fa76", "grey heart", []string{"grey_heart"}, "15.0", false}, {"\u2754", "white question mark", []string{"grey_question"}, "6.0", false}, {"\U0001f62c", "grimacing face", []string{"grimacing"}, "6.1", false}, {"\U0001f601", "beaming face with smiling eyes", []string{"grin"}, "6.0", false}, @@ -1129,6 +1136,7 @@ var GemojiData = Gemoji{ {"\U0001f3b8", "guitar", []string{"guitar"}, "6.0", false}, {"\U0001f52b", "water pistol", []string{"gun"}, "6.0", false}, {"\U0001f1ec\U0001f1fe", "flag: Guyana", []string{"guyana"}, "6.0", false}, + {"\U0001faae", "hair pick", []string{"hair_pick"}, "15.0", false}, {"\U0001f487", "person getting haircut", []string{"haircut"}, "6.0", true}, {"\U0001f487\U0001f3ff", "person getting haircut: Dark Skin Tone", []string{"haircut_Dark_Skin_Tone"}, "12.0", false}, {"\U0001f487\U0001f3fb", "person getting haircut: Light Skin Tone", []string{"haircut_Light_Skin_Tone"}, "12.0", false}, @@ -1253,6 +1261,7 @@ var GemojiData = Gemoji{ {"\U0001f1ed\U0001f1fa", "flag: Hungary", []string{"hungary"}, "6.0", false}, {"\U0001f62f", "hushed face", []string{"hushed"}, "6.1", false}, {"\U0001f6d6", "hut", []string{"hut"}, "13.0", false}, + {"\U0001fabb", "hyacinth", []string{"hyacinth"}, "15.0", false}, {"\U0001f368", "ice cream", []string{"ice_cream"}, "6.0", false}, {"\U0001f9ca", "ice", []string{"ice_cube"}, "12.0", false}, {"\U0001f3d2", "ice hockey", []string{"ice_hockey"}, "8.0", false}, @@ -1293,6 +1302,7 @@ var GemojiData = Gemoji{ {"\U0001f479", "ogre", []string{"japanese_ogre"}, "6.0", false}, {"\U0001fad9", "jar", []string{"jar"}, "14.0", false}, {"\U0001f456", "jeans", []string{"jeans"}, "6.0", false}, + {"\U0001fabc", "jellyfish", []string{"jellyfish"}, "15.0", false}, {"\U0001f1ef\U0001f1ea", "flag: Jersey", []string{"jersey"}, "6.0", false}, {"\U0001f9e9", "puzzle piece", []string{"jigsaw"}, "11.0", false}, {"\U0001f1ef\U0001f1f4", "flag: Jordan", []string{"jordan"}, "6.0", false}, @@ -1319,6 +1329,7 @@ var GemojiData = Gemoji{ {"\U0001f511", "key", []string{"key"}, "6.0", false}, {"\u2328\ufe0f", "keyboard", []string{"keyboard"}, "", false}, {"\U0001f51f", "keycap: 10", []string{"keycap_ten"}, "6.0", false}, + {"\U0001faaf", "khanda", []string{"khanda"}, "15.0", false}, {"\U0001f6f4", "kick scooter", []string{"kick_scooter"}, "9.0", false}, {"\U0001f458", "kimono", []string{"kimono"}, "6.0", false}, {"\U0001f1f0\U0001f1ee", "flag: Kiribati", []string{"kiribati"}, "6.0", false}, @@ -1383,6 +1394,12 @@ var GemojiData = Gemoji{ {"\U0001faf2\U0001f3fe", "leftwards hand: Medium-Dark Skin Tone", []string{"leftwards_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, {"\U0001faf2\U0001f3fc", "leftwards hand: Medium-Light Skin Tone", []string{"leftwards_hand_Medium-Light_Skin_Tone"}, "12.0", false}, {"\U0001faf2\U0001f3fd", "leftwards hand: Medium Skin Tone", []string{"leftwards_hand_Medium_Skin_Tone"}, "12.0", false}, + {"\U0001faf7", "leftwards pushing hand", []string{"leftwards_pushing_hand"}, "15.0", true}, + {"\U0001faf7\U0001f3ff", "leftwards pushing hand: Dark Skin Tone", []string{"leftwards_pushing_hand_Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fb", "leftwards pushing hand: Light Skin Tone", []string{"leftwards_pushing_hand_Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fe", "leftwards pushing hand: Medium-Dark Skin Tone", []string{"leftwards_pushing_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fc", "leftwards pushing hand: Medium-Light Skin Tone", []string{"leftwards_pushing_hand_Medium-Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf7\U0001f3fd", "leftwards pushing hand: Medium Skin Tone", []string{"leftwards_pushing_hand_Medium_Skin_Tone"}, "12.0", false}, {"\U0001f9b5", "leg", []string{"leg"}, "11.0", true}, {"\U0001f9b5\U0001f3ff", "leg: Dark Skin Tone", []string{"leg_Dark_Skin_Tone"}, "12.0", false}, {"\U0001f9b5\U0001f3fb", "leg: Light Skin Tone", []string{"leg_Light_Skin_Tone"}, "12.0", false}, @@ -1398,6 +1415,7 @@ var GemojiData = Gemoji{ {"\u264e", "Libra", []string{"libra"}, "", false}, {"\U0001f1f1\U0001f1fe", "flag: Libya", []string{"libya"}, "6.0", false}, {"\U0001f1f1\U0001f1ee", "flag: Liechtenstein", []string{"liechtenstein"}, "6.0", false}, + {"\U0001fa75", "light blue heart", []string{"light_blue_heart"}, "15.0", false}, {"\U0001f688", "light rail", []string{"light_rail"}, "6.0", false}, {"\U0001f517", "link", []string{"link"}, "6.0", false}, {"\U0001f981", "lion", []string{"lion"}, "8.0", false}, @@ -1695,6 +1713,7 @@ var GemojiData = Gemoji{ {"\U0001f570\ufe0f", "mantelpiece clock", []string{"mantelpiece_clock"}, "7.0", false}, {"\U0001f9bd", "manual wheelchair", []string{"manual_wheelchair"}, "12.0", false}, {"\U0001f341", "maple leaf", []string{"maple_leaf"}, "6.0", false}, + {"\U0001fa87", "maracas", []string{"maracas"}, "15.0", false}, {"\U0001f1f2\U0001f1ed", "flag: Marshall Islands", []string{"marshall_islands"}, "6.0", false}, {"\U0001f94b", "martial arts uniform", []string{"martial_arts_uniform"}, "9.0", false}, {"\U0001f1f2\U0001f1f6", "flag: Martinique", []string{"martinique"}, "6.0", false}, @@ -1799,6 +1818,7 @@ var GemojiData = Gemoji{ {"\U0001f1f2\U0001f1f8", "flag: Montserrat", []string{"montserrat"}, "6.0", false}, {"\U0001f314", "waxing gibbous moon", []string{"moon", "waxing_gibbous_moon"}, "6.0", false}, {"\U0001f96e", "moon cake", []string{"moon_cake"}, "11.0", false}, + {"\U0001face", "moose", []string{"moose"}, "15.0", false}, {"\U0001f1f2\U0001f1e6", "flag: Morocco", []string{"morocco"}, "6.0", false}, {"\U0001f393", "graduation cap", []string{"mortar_board"}, "6.0", false}, {"\U0001f54c", "mosque", []string{"mosque"}, "8.0", false}, @@ -2076,6 +2096,7 @@ var GemojiData = Gemoji{ {"\U0001f6f3\ufe0f", "passenger ship", []string{"passenger_ship"}, "7.0", false}, {"\U0001f6c2", "passport control", []string{"passport_control"}, "6.0", false}, {"\u23f8\ufe0f", "pause button", []string{"pause_button"}, "7.0", false}, + {"\U0001fadb", "pea pod", []string{"pea_pod"}, "15.0", false}, {"\u262e\ufe0f", "peace symbol", []string{"peace_symbol"}, "", false}, {"\U0001f351", "peach", []string{"peach"}, "6.0", false}, {"\U0001f99a", "peacock", []string{"peacock"}, "11.0", false}, @@ -2085,7 +2106,12 @@ var GemojiData = Gemoji{ {"\u270f\ufe0f", "pencil", []string{"pencil2"}, "", false}, {"\U0001f427", "penguin", []string{"penguin"}, "6.0", false}, {"\U0001f614", "pensive face", []string{"pensive"}, "6.0", false}, - {"\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands", []string{"people_holding_hands"}, "12.0", false}, + {"\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands", []string{"people_holding_hands"}, "12.0", true}, + {"\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Dark Skin Tone", []string{"people_holding_hands_Dark_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Light Skin Tone", []string{"people_holding_hands_Light_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Dark Skin Tone", []string{"people_holding_hands_Medium-Dark_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Light Skin Tone", []string{"people_holding_hands_Medium-Light_Skin_Tone"}, "12.0", false}, + {"\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium Skin Tone", []string{"people_holding_hands_Medium_Skin_Tone"}, "12.0", false}, {"\U0001fac2", "people hugging", []string{"people_hugging"}, "13.0", false}, {"\U0001f3ad", "performing arts", []string{"performing_arts"}, "6.0", false}, {"\U0001f623", "persevering face", []string{"persevere"}, "6.0", false}, @@ -2194,6 +2220,7 @@ var GemojiData = Gemoji{ {"\U0001f90f\U0001f3fd", "pinching hand: Medium Skin Tone", []string{"pinching_hand_Medium_Skin_Tone"}, "12.0", false}, {"\U0001f34d", "pineapple", []string{"pineapple"}, "6.0", false}, {"\U0001f3d3", "ping pong", []string{"ping_pong"}, "8.0", false}, + {"\U0001fa77", "pink heart", []string{"pink_heart"}, "15.0", false}, {"\U0001f3f4\u200d\u2620\ufe0f", "pirate flag", []string{"pirate_flag"}, "11.0", false}, {"\u2653", "Pisces", []string{"pisces"}, "", false}, {"\U0001f1f5\U0001f1f3", "flag: Pitcairn Islands", []string{"pitcairn_islands"}, "6.0", false}, @@ -2346,7 +2373,7 @@ var GemojiData = Gemoji{ {"\U0001f4fb", "radio", []string{"radio"}, "6.0", false}, {"\U0001f518", "radio button", []string{"radio_button"}, "6.0", false}, {"\u2622\ufe0f", "radioactive", []string{"radioactive"}, "", false}, - {"\U0001f621", "pouting face", []string{"rage", "pout"}, "6.0", false}, + {"\U0001f621", "enraged face", []string{"rage", "pout"}, "6.0", false}, {"\U0001f683", "railway car", []string{"railway_car"}, "6.0", false}, {"\U0001f6e4\ufe0f", "railway track", []string{"railway_track"}, "7.0", false}, {"\U0001f308", "rainbow", []string{"rainbow"}, "6.0", false}, @@ -2434,6 +2461,12 @@ var GemojiData = Gemoji{ {"\U0001faf1\U0001f3fe", "rightwards hand: Medium-Dark Skin Tone", []string{"rightwards_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, {"\U0001faf1\U0001f3fc", "rightwards hand: Medium-Light Skin Tone", []string{"rightwards_hand_Medium-Light_Skin_Tone"}, "12.0", false}, {"\U0001faf1\U0001f3fd", "rightwards hand: Medium Skin Tone", []string{"rightwards_hand_Medium_Skin_Tone"}, "12.0", false}, + {"\U0001faf8", "rightwards pushing hand", []string{"rightwards_pushing_hand"}, "15.0", true}, + {"\U0001faf8\U0001f3ff", "rightwards pushing hand: Dark Skin Tone", []string{"rightwards_pushing_hand_Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fb", "rightwards pushing hand: Light Skin Tone", []string{"rightwards_pushing_hand_Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fe", "rightwards pushing hand: Medium-Dark Skin Tone", []string{"rightwards_pushing_hand_Medium-Dark_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fc", "rightwards pushing hand: Medium-Light Skin Tone", []string{"rightwards_pushing_hand_Medium-Light_Skin_Tone"}, "12.0", false}, + {"\U0001faf8\U0001f3fd", "rightwards pushing hand: Medium Skin Tone", []string{"rightwards_pushing_hand_Medium_Skin_Tone"}, "12.0", false}, {"\U0001f48d", "ring", []string{"ring"}, "6.0", false}, {"\U0001f6df", "ring buoy", []string{"ring_buoy"}, "14.0", false}, {"\U0001fa90", "ringed planet", []string{"ringed_planet"}, "12.0", false}, @@ -2566,6 +2599,7 @@ var GemojiData = Gemoji{ {"7\ufe0f\u20e3", "keycap: 7", []string{"seven"}, "", false}, {"\U0001faa1", "sewing needle", []string{"sewing_needle"}, "13.0", false}, {"\U0001f1f8\U0001f1e8", "flag: Seychelles", []string{"seychelles"}, "6.0", false}, + {"\U0001fae8", "shaking face", []string{"shaking_face"}, "15.0", false}, {"\U0001f958", "shallow pan of food", []string{"shallow_pan_of_food"}, "", false}, {"\u2618\ufe0f", "shamrock", []string{"shamrock"}, "4.1", false}, {"\U0001f988", "shark", []string{"shark"}, "9.0", false}, @@ -3125,7 +3159,9 @@ var GemojiData = Gemoji{ {"\U0001f32c\ufe0f", "wind face", []string{"wind_face"}, "7.0", false}, {"\U0001fa9f", "window", []string{"window"}, "13.0", false}, {"\U0001f377", "wine glass", []string{"wine_glass"}, "6.0", false}, + {"\U0001fabd", "wing", []string{"wing"}, "15.0", false}, {"\U0001f609", "winking face", []string{"wink"}, "6.0", false}, + {"\U0001f6dc", "wireless", []string{"wireless"}, "15.0", false}, {"\U0001f43a", "wolf", []string{"wolf"}, "6.0", false}, {"\U0001f469", "woman", []string{"woman"}, "6.0", true}, {"\U0001f469\U0001f3ff", "woman: Dark Skin Tone", []string{"woman_Dark_Skin_Tone"}, "12.0", false}, @@ -3364,5 +3400,5 @@ var GemojiData = Gemoji{ {"\U0001f9df", "zombie", []string{"zombie"}, "11.0", false}, {"\U0001f9df\u200d\u2642\ufe0f", "man zombie", []string{"zombie_man"}, "11.0", false}, {"\U0001f9df\u200d\u2640\ufe0f", "woman zombie", []string{"zombie_woman"}, "11.0", false}, - {"\U0001f4a4", "zzz", []string{"zzz"}, "6.0", false}, + {"\U0001f4a4", "ZZZ", []string{"zzz"}, "6.0", false}, } diff --git a/modules/eventsource/event.go b/modules/eventsource/event.go index d5d2f323afec1..ebcca5090344c 100644 --- a/modules/eventsource/event.go +++ b/modules/eventsource/event.go @@ -15,7 +15,7 @@ import ( func wrapNewlines(w io.Writer, prefix, value []byte) (sum int64, err error) { if len(value) == 0 { - return + return 0, nil } var n int last := 0 @@ -23,24 +23,24 @@ func wrapNewlines(w io.Writer, prefix, value []byte) (sum int64, err error) { n, err = w.Write(prefix) sum += int64(n) if err != nil { - return + return sum, err } n, err = w.Write(value[last : last+j+1]) sum += int64(n) if err != nil { - return + return sum, err } last += j + 1 } n, err = w.Write(prefix) sum += int64(n) if err != nil { - return + return sum, err } n, err = w.Write(value[last:]) sum += int64(n) if err != nil { - return + return sum, err } n, err = w.Write([]byte("\n")) sum += int64(n) @@ -51,8 +51,8 @@ func wrapNewlines(w io.Writer, prefix, value []byte) (sum int64, err error) { type Event struct { // Name represents the value of the event: tag in the stream Name string - // Data is either JSONified []byte or interface{} that can be JSONd - Data interface{} + // Data is either JSONified []byte or any that can be JSONd + Data any // ID represents the ID of an event ID string // Retry tells the receiver only to attempt to reconnect to the source after this time diff --git a/modules/git/command.go b/modules/git/command.go index ac013d4ea1c7c..c38fd04696671 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -301,6 +301,8 @@ func (c *Command) Run(opts *RunOpts) error { } defer finished() + startTime := time.Now() + cmd := exec.CommandContext(ctx, c.prog, c.args...) if opts.Env == nil { cmd.Env = os.Environ() @@ -327,7 +329,13 @@ func (c *Command) Run(opts *RunOpts) error { } } - if err := cmd.Wait(); err != nil && ctx.Err() != context.DeadlineExceeded { + err := cmd.Wait() + elapsed := time.Since(startTime) + if elapsed > time.Second { + log.Debug("slow git.Command.Run: %s (%s)", c, elapsed) + } + + if err != nil && ctx.Err() != context.DeadlineExceeded { return err } diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go index 20db5691ebe42..c61d27993cd78 100644 --- a/modules/git/commit_info_gogit.go +++ b/modules/git/commit_info_gogit.go @@ -177,7 +177,7 @@ func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, c cgobje refSha := c.ID().String() // We do a tree traversal with nodes sorted by commit time - heap := binaryheap.NewWith(func(a, b interface{}) int { + heap := binaryheap.NewWith(func(a, b any) int { if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { return 1 } diff --git a/modules/git/foreachref/parser_test.go b/modules/git/foreachref/parser_test.go index 5468318ca80cf..7a37ced3565cf 100644 --- a/modules/git/foreachref/parser_test.go +++ b/modules/git/foreachref/parser_test.go @@ -217,7 +217,7 @@ func TestParser(t *testing.T) { } } -func pretty(v interface{}) string { +func pretty(v any) string { data, err := json.MarshalIndent(v, "", " ") if err != nil { // shouldn't happen diff --git a/modules/git/git.go b/modules/git/git.go index f9c0ed669f490..c9d174e118113 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -114,7 +114,7 @@ func VersionInfo() string { return "(git not found)" } format := "%s" - args := []interface{}{gitVersion.Original()} + args := []any{gitVersion.Original()} // Since git wire protocol has been released from git v2.18 if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { format += ", Wire Protocol %s Enabled" @@ -188,7 +188,6 @@ func InitFull(ctx context.Context) (err error) { if CheckGitVersionAtLeast("2.9") == nil { globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") } - SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil if setting.LFS.StartServer { diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go index 984561b2c6729..20bc79608557a 100644 --- a/modules/git/last_commit_cache.go +++ b/modules/git/last_commit_cache.go @@ -15,9 +15,9 @@ import ( // Cache represents a caching interface type Cache interface { // Put puts value into cache with key and expire time. - Put(key string, val interface{}, timeout int64) error + Put(key string, val any, timeout int64) error // Get gets cached value by given key. - Get(key string) interface{} + Get(key string) any } func getCacheKey(repoPath, commitID, entryPath string) string { diff --git a/modules/git/utils.go b/modules/git/utils.go index b44363820dcfb..0d67412707e5b 100644 --- a/modules/git/utils.go +++ b/modules/git/utils.go @@ -15,17 +15,17 @@ import ( // ObjectCache provides thread-safe cache operations. type ObjectCache struct { lock sync.RWMutex - cache map[string]interface{} + cache map[string]any } func newObjectCache() *ObjectCache { return &ObjectCache{ - cache: make(map[string]interface{}, 10), + cache: make(map[string]any, 10), } } // Set add obj to cache -func (oc *ObjectCache) Set(id string, obj interface{}) { +func (oc *ObjectCache) Set(id string, obj any) { oc.lock.Lock() defer oc.lock.Unlock() @@ -33,7 +33,7 @@ func (oc *ObjectCache) Set(id string, obj interface{}) { } // Get get cached obj by id -func (oc *ObjectCache) Get(id string) (interface{}, bool) { +func (oc *ObjectCache) Get(id string) (any, bool) { oc.lock.RLock() defer oc.lock.RUnlock() diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 3604c0a3f5804..068de21076fc7 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -283,7 +283,7 @@ func (g *Manager) Err() error { } // Value allows the manager to be viewed as a context.Context done at Terminate -func (g *Manager) Value(key interface{}) interface{} { +func (g *Manager) Value(key any) any { return g.managerCtx.Value(key) } diff --git a/modules/html/html.go b/modules/html/html.go index 3219b939f40a8..6cb6b847ef19c 100644 --- a/modules/html/html.go +++ b/modules/html/html.go @@ -5,7 +5,7 @@ package html // ParseSizeAndClass get size and class from string with default values // If present, "others" expects the new size first and then the classes to use -func ParseSizeAndClass(defaultSize int, defaultClass string, others ...interface{}) (int, string) { +func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int, string) { if len(others) == 0 { return defaultSize, defaultClass } diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index 001ac06415375..b57b3218322c7 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -70,11 +70,11 @@ func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { // HandleGenericETagTimeCache handles ETag-based caching with Last-Modified caching for a HTTP request. // It returns true if the request was handled. -func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified time.Time) (handled bool) { +func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) (handled bool) { if len(etag) > 0 { w.Header().Set("Etag", etag) } - if !lastModified.IsZero() { + if lastModified != nil && !lastModified.IsZero() { w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat)) } @@ -84,7 +84,7 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s return true } } - if !lastModified.IsZero() { + if lastModified != nil && !lastModified.IsZero() { ifModifiedSince := req.Header.Get("If-Modified-Since") if ifModifiedSince != "" { t, err := time.Parse(http.TimeFormat, ifModifiedSince) diff --git a/modules/httplib/request.go b/modules/httplib/request.go index e904d77e14977..880d7ad3cbe36 100644 --- a/modules/httplib/request.go +++ b/modules/httplib/request.go @@ -101,7 +101,7 @@ func (r *Request) Param(key, value string) *Request { // Body adds request raw body. // it supports string and []byte. -func (r *Request) Body(data interface{}) *Request { +func (r *Request) Body(data any) *Request { switch t := data.(type) { case string: bf := bytes.NewBufferString(t) diff --git a/modules/httplib/serve.go b/modules/httplib/serve.go index 12d68c2d656eb..a193ed901cf99 100644 --- a/modules/httplib/serve.go +++ b/modules/httplib/serve.go @@ -206,7 +206,7 @@ func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath strin _, _ = io.CopyN(w, reader, partialLength) // just like http.ServeContent, not necessary to handle the error } -func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath string, modTime time.Time, reader io.ReadSeeker) { +func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath string, modTime *time.Time, reader io.ReadSeeker) { buf := make([]byte, mimeDetectionBufferLen) n, err := util.ReadAtMost(reader, buf) if err != nil { @@ -221,5 +221,8 @@ func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath s buf = buf[:n] } setServeHeadersByFile(r, w, filePath, buf) - http.ServeContent(w, r, path.Base(filePath), modTime, reader) + if modTime == nil { + modTime = &time.Time{} + } + http.ServeContent(w, r, path.Base(filePath), *modTime, reader) } diff --git a/modules/httplib/serve_test.go b/modules/httplib/serve_test.go index fed4611d21795..c2229dffe96e8 100644 --- a/modules/httplib/serve_test.go +++ b/modules/httplib/serve_test.go @@ -11,7 +11,6 @@ import ( "os" "strings" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -78,7 +77,7 @@ func TestServeContentByReadSeeker(t *testing.T) { defer seekReader.Close() w := httptest.NewRecorder() - ServeContentByReadSeeker(r, w, "test", time.Time{}, seekReader) + ServeContentByReadSeeker(r, w, "test", nil, seekReader) assert.Equal(t, expectedStatusCode, w.Code) if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length")) diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index 33cc4e02b5149..1e34226e8df14 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -51,7 +51,7 @@ func numericEqualityQuery(value int64, field string) *query.NumericRangeQuery { } func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error { - return m.AddCustomTokenFilter(unicodeNormalizeName, map[string]interface{}{ + return m.AddCustomTokenFilter(unicodeNormalizeName, map[string]any{ "type": unicodenorm.Name, "form": unicodenorm.NFC, }) @@ -101,7 +101,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) { mapping := bleve.NewIndexMapping() if err := addUnicodeNormalizeTokenFilter(mapping); err != nil { return nil, err - } else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]interface{}{ + } else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]any{ "type": analyzer_custom.Name, "char_filters": []string{}, "tokenizer": unicode.Name, diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 88054585cd28f..e7e3429a39af6 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -133,7 +133,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro elastic.NewBulkIndexRequest(). Index(b.inner.VersionedIndexName()). Id(id). - Doc(map[string]interface{}{ + Doc(map[string]any{ "repo_id": repo.ID, "content": string(charset.ToUTF8DropErrors(fileContents)), "commit_id": sha, @@ -234,7 +234,7 @@ func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int) } repoID, fileName := internal.ParseIndexerID(hit.Id) - res := make(map[string]interface{}) + res := make(map[string]any) if err := json.Unmarshal(hit.Source, &res); err != nil { return 0, nil, nil, err } @@ -285,7 +285,7 @@ func (b *Indexer) Search(ctx context.Context, repoIDs []int64, language, keyword query := elastic.NewBoolQuery() query = query.Must(kwQuery) if len(repoIDs) > 0 { - repoStrs := make([]interface{}, 0, len(repoIDs)) + repoStrs := make([]any, 0, len(repoIDs)) for _, repoID := range repoIDs { repoStrs = append(repoStrs, repoID) } diff --git a/modules/indexer/internal/bleve/batch.go b/modules/indexer/internal/bleve/batch.go index 77675147b2cda..ed5ef0773c8cf 100644 --- a/modules/indexer/internal/bleve/batch.go +++ b/modules/indexer/internal/bleve/batch.go @@ -27,7 +27,7 @@ func NewFlushingBatch(index bleve.Index, maxBatchSize int) *FlushingBatch { } // Index add a new index to batch -func (b *FlushingBatch) Index(id string, data interface{}) error { +func (b *FlushingBatch) Index(id string, data any) error { if err := b.batch.Index(id, data); err != nil { return err } diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index bb0bc4b04a414..c368a67ab5863 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -23,7 +23,7 @@ import ( const ( issueIndexerAnalyzer = "issueIndexer" issueIndexerDocType = "issueIndexerDocType" - issueIndexerLatestVersion = 2 + issueIndexerLatestVersion = 3 ) // numericEqualityQuery a numeric equality query for the given value and field @@ -45,7 +45,7 @@ func newMatchPhraseQuery(matchPhrase, field, analyzer string) *query.MatchPhrase const unicodeNormalizeName = "unicodeNormalize" func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error { - return m.AddCustomTokenFilter(unicodeNormalizeName, map[string]interface{}{ + return m.AddCustomTokenFilter(unicodeNormalizeName, map[string]any{ "type": unicodenorm.Name, "form": unicodenorm.NFC, }) @@ -67,19 +67,20 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) { docMapping := bleve.NewDocumentMapping() numericFieldMapping := bleve.NewNumericFieldMapping() + numericFieldMapping.Store = false numericFieldMapping.IncludeInAll = false - docMapping.AddFieldMappingsAt("RepoID", numericFieldMapping) + docMapping.AddFieldMappingsAt("repo_id", numericFieldMapping) textFieldMapping := bleve.NewTextFieldMapping() textFieldMapping.Store = false textFieldMapping.IncludeInAll = false - docMapping.AddFieldMappingsAt("Title", textFieldMapping) - docMapping.AddFieldMappingsAt("Content", textFieldMapping) - docMapping.AddFieldMappingsAt("Comments", textFieldMapping) + docMapping.AddFieldMappingsAt("title", textFieldMapping) + docMapping.AddFieldMappingsAt("content", textFieldMapping) + docMapping.AddFieldMappingsAt("comments", textFieldMapping) if err := addUnicodeNormalizeTokenFilter(mapping); err != nil { return nil, err - } else if err = mapping.AddCustomAnalyzer(issueIndexerAnalyzer, map[string]interface{}{ + } else if err = mapping.AddCustomAnalyzer(issueIndexerAnalyzer, map[string]any{ "type": custom.Name, "char_filters": []string{}, "tokenizer": unicode.Name, @@ -91,6 +92,7 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) { mapping.DefaultAnalyzer = issueIndexerAnalyzer mapping.AddDocumentMapping(issueIndexerDocType, docMapping) mapping.AddDocumentMapping("_all", bleve.NewDocumentDisabledMapping()) + mapping.DefaultMapping = bleve.NewDocumentDisabledMapping() // disable default mapping, avoid indexing unexpected structs return mapping, nil } @@ -116,17 +118,7 @@ func NewIndexer(indexDir string) *Indexer { func (b *Indexer) Index(_ context.Context, issues []*internal.IndexerData) error { batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize) for _, issue := range issues { - if err := batch.Index(indexer_internal.Base36(issue.ID), struct { - RepoID int64 - Title string - Content string - Comments []string - }{ - RepoID: issue.RepoID, - Title: issue.Title, - Content: issue.Content, - Comments: issue.Comments, - }); err != nil { + if err := batch.Index(indexer_internal.Base36(issue.ID), (*IndexerData)(issue)); err != nil { return err } } @@ -146,10 +138,10 @@ func (b *Indexer) Delete(_ context.Context, ids ...int64) error { // Search searches for issues by given conditions. // Returns the matching issue IDs -func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*internal.SearchResult, error) { +func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int, state string) (*internal.SearchResult, error) { var repoQueriesP []*query.NumericRangeQuery for _, repoID := range repoIDs { - repoQueriesP = append(repoQueriesP, numericEqualityQuery(repoID, "RepoID")) + repoQueriesP = append(repoQueriesP, numericEqualityQuery(repoID, "repo_id")) } repoQueries := make([]query.Query, len(repoQueriesP)) for i, v := range repoQueriesP { @@ -159,9 +151,9 @@ func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, l indexerQuery := bleve.NewConjunctionQuery( bleve.NewDisjunctionQuery(repoQueries...), bleve.NewDisjunctionQuery( - newMatchPhraseQuery(keyword, "Title", issueIndexerAnalyzer), - newMatchPhraseQuery(keyword, "Content", issueIndexerAnalyzer), - newMatchPhraseQuery(keyword, "Comments", issueIndexerAnalyzer), + newMatchPhraseQuery(keyword, "title", issueIndexerAnalyzer), + newMatchPhraseQuery(keyword, "content", issueIndexerAnalyzer), + newMatchPhraseQuery(keyword, "comments", issueIndexerAnalyzer), )) search := bleve.NewSearchRequestOptions(indexerQuery, limit, start, false) search.SortBy([]string{"-_score"}) diff --git a/modules/indexer/issues/bleve/bleve_test.go b/modules/indexer/issues/bleve/bleve_test.go index f890f8eb488fe..0eb136d22b276 100644 --- a/modules/indexer/issues/bleve/bleve_test.go +++ b/modules/indexer/issues/bleve/bleve_test.go @@ -77,7 +77,7 @@ func TestBleveIndexAndSearch(t *testing.T) { } for _, kw := range keywords { - res, err := indexer.Search(context.TODO(), kw.Keyword, []int64{2}, 10, 0) + res, err := indexer.Search(context.TODO(), kw.Keyword, []int64{2}, 10, 0, "") assert.NoError(t, err) ids := make([]int64, 0, len(res.Hits)) diff --git a/modules/indexer/issues/db/db.go b/modules/indexer/issues/db/db.go index 17ed426b384ba..b054b9d800edb 100644 --- a/modules/indexer/issues/db/db.go +++ b/modules/indexer/issues/db/db.go @@ -36,7 +36,7 @@ func (i *Indexer) Delete(_ context.Context, _ ...int64) error { } // Search searches for issues -func (i *Indexer) Search(ctx context.Context, kw string, repoIDs []int64, limit, start int) (*internal.SearchResult, error) { +func (i *Indexer) Search(ctx context.Context, kw string, repoIDs []int64, limit, start int, state string) (*internal.SearchResult, error) { total, ids, err := issues_model.SearchIssueIDsByKeyword(ctx, kw, repoIDs, limit, start) if err != nil { return nil, err diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 33a7dfc21e0c7..cfd3628c18507 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -76,7 +76,7 @@ func (b *Indexer) Index(ctx context.Context, issues []*internal.IndexerData) err _, err := b.inner.Client.Index(). Index(b.inner.VersionedIndexName()). Id(fmt.Sprintf("%d", issue.ID)). - BodyJson(map[string]interface{}{ + BodyJson(map[string]any{ "id": issue.ID, "repo_id": issue.RepoID, "title": issue.Title, @@ -93,7 +93,7 @@ func (b *Indexer) Index(ctx context.Context, issues []*internal.IndexerData) err elastic.NewBulkIndexRequest(). Index(b.inner.VersionedIndexName()). Id(fmt.Sprintf("%d", issue.ID)). - Doc(map[string]interface{}{ + Doc(map[string]any{ "id": issue.ID, "repo_id": issue.RepoID, "title": issue.Title, @@ -140,12 +140,12 @@ func (b *Indexer) Delete(ctx context.Context, ids ...int64) error { // Search searches for issues by given conditions. // Returns the matching issue IDs -func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*internal.SearchResult, error) { +func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int, state string) (*internal.SearchResult, error) { kwQuery := elastic.NewMultiMatchQuery(keyword, "title", "content", "comments") query := elastic.NewBoolQuery() query = query.Must(kwQuery) if len(repoIDs) > 0 { - repoStrs := make([]interface{}, 0, len(repoIDs)) + repoStrs := make([]any, 0, len(repoIDs)) for _, repoID := range repoIDs { repoStrs = append(repoStrs, repoID) } diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 9e2f13371e4a3..fe5c5d8f26d30 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -242,12 +242,18 @@ func UpdateIssueIndexer(issue *issues_model.Issue) { comments = append(comments, comment.Content) } } + issueType := "issue" + if issue.IsPull { + issueType = "pull" + } indexerData := &internal.IndexerData{ - ID: issue.ID, - RepoID: issue.RepoID, - Title: issue.Title, - Content: issue.Content, - Comments: comments, + ID: issue.ID, + RepoID: issue.RepoID, + State: string(issue.State()), + IssueType: issueType, + Title: issue.Title, + Content: issue.Content, + Comments: comments, } log.Debug("Adding to channel: %v", indexerData) if err := issueIndexerQueue.Push(indexerData); err != nil { @@ -278,10 +284,10 @@ func DeleteRepoIssueIndexer(ctx context.Context, repo *repo_model.Repository) { // SearchIssuesByKeyword search issue ids by keywords and repo id // WARNNING: You have to ensure user have permission to visit repoIDs' issues -func SearchIssuesByKeyword(ctx context.Context, repoIDs []int64, keyword string) ([]int64, error) { +func SearchIssuesByKeyword(ctx context.Context, repoIDs []int64, keyword, state string) ([]int64, error) { var issueIDs []int64 indexer := *globalIndexer.Load() - res, err := indexer.Search(ctx, keyword, repoIDs, 50, 0) + res, err := indexer.Search(ctx, keyword, repoIDs, 50, 0, state) if err != nil { return nil, err } diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index 5962a4ee9cb76..757eb2f3d9338 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -50,19 +50,19 @@ func TestBleveSearchIssues(t *testing.T) { time.Sleep(5 * time.Second) - ids, err := SearchIssuesByKeyword(context.TODO(), []int64{1}, "issue2") + ids, err := SearchIssuesByKeyword(context.TODO(), []int64{1}, "issue2", "") assert.NoError(t, err) assert.EqualValues(t, []int64{2}, ids) - ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "first") + ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "first", "") assert.NoError(t, err) assert.EqualValues(t, []int64{1}, ids) - ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "for") + ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "for", "") assert.NoError(t, err) assert.ElementsMatch(t, []int64{1, 2, 3, 5, 11}, ids) - ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "good") + ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "good", "") assert.NoError(t, err) assert.EqualValues(t, []int64{1}, ids) } @@ -73,19 +73,19 @@ func TestDBSearchIssues(t *testing.T) { setting.Indexer.IssueType = "db" InitIssueIndexer(true) - ids, err := SearchIssuesByKeyword(context.TODO(), []int64{1}, "issue2") + ids, err := SearchIssuesByKeyword(context.TODO(), []int64{1}, "issue2", "") assert.NoError(t, err) assert.EqualValues(t, []int64{2}, ids) - ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "first") + ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "first", "") assert.NoError(t, err) assert.EqualValues(t, []int64{1}, ids) - ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "for") + ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "for", "") assert.NoError(t, err) assert.ElementsMatch(t, []int64{1, 2, 3, 5, 11}, ids) - ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "good") + ids, err = SearchIssuesByKeyword(context.TODO(), []int64{1}, "good", "") assert.NoError(t, err) assert.EqualValues(t, []int64{1}, ids) } diff --git a/modules/indexer/issues/internal/indexer.go b/modules/indexer/issues/internal/indexer.go index 553c8a573cdcb..b96517bb80db9 100644 --- a/modules/indexer/issues/internal/indexer.go +++ b/modules/indexer/issues/internal/indexer.go @@ -15,7 +15,7 @@ type Indexer interface { internal.Indexer Index(ctx context.Context, issue []*IndexerData) error Delete(ctx context.Context, ids ...int64) error - Search(ctx context.Context, kw string, repoIDs []int64, limit, start int) (*SearchResult, error) + Search(ctx context.Context, kw string, repoIDs []int64, limit, start int, state string) (*SearchResult, error) } // NewDummyIndexer returns a dummy indexer @@ -37,6 +37,6 @@ func (d *dummyIndexer) Delete(ctx context.Context, ids ...int64) error { return fmt.Errorf("indexer is not ready") } -func (d *dummyIndexer) Search(ctx context.Context, kw string, repoIDs []int64, limit, start int) (*SearchResult, error) { +func (d *dummyIndexer) Search(ctx context.Context, kw string, repoIDs []int64, limit, start int, state string) (*SearchResult, error) { return nil, fmt.Errorf("indexer is not ready") } diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index 8c206fc1cfcdc..2b52d32302a06 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -5,13 +5,15 @@ package internal // IndexerData data stored in the issue indexer type IndexerData struct { - ID int64 `json:"id"` - RepoID int64 `json:"repo_id"` - Title string `json:"title"` - Content string `json:"content"` - Comments []string `json:"comments"` - IsDelete bool `json:"is_delete"` - IDs []int64 `json:"ids"` + ID int64 `json:"id"` + RepoID int64 `json:"repo_id"` + State string `json:"state"` // open, closed, all + IssueType string `json:"type"` // issue or pull + Title string `json:"title"` + Content string `json:"content"` + Comments []string `json:"comments"` + IsDelete bool `json:"is_delete"` + IDs []int64 `json:"ids"` } // Match represents on search result diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 877c04f1dcb2d..2ea06b576c0b8 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -16,7 +16,7 @@ import ( ) const ( - issueIndexerLatestVersion = 0 + issueIndexerLatestVersion = 1 ) var _ internal.Indexer = &Indexer{} @@ -70,12 +70,19 @@ func (b *Indexer) Delete(_ context.Context, ids ...int64) error { // Search searches for issues by given conditions. // Returns the matching issue IDs -func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*internal.SearchResult, error) { +func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int, state string) (*internal.SearchResult, error) { repoFilters := make([]string, 0, len(repoIDs)) for _, repoID := range repoIDs { repoFilters = append(repoFilters, "repo_id = "+strconv.FormatInt(repoID, 10)) } filter := strings.Join(repoFilters, " OR ") + if state == "open" || state == "closed" { + if filter != "" { + filter = "(" + filter + ") AND state = " + state + } else { + filter = "state = " + state + } + } searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(keyword, &meilisearch.SearchRequest{ Filter: filter, Limit: int64(limit), @@ -88,7 +95,7 @@ func (b *Indexer) Search(ctx context.Context, keyword string, repoIDs []int64, l hits := make([]internal.Match, 0, len(searchRes.Hits)) for _, hit := range searchRes.Hits { hits = append(hits, internal.Match{ - ID: int64(hit.(map[string]interface{})["id"].(float64)), + ID: int64(hit.(map[string]any)["id"].(float64)), }) } return &internal.SearchResult{ diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go index 0f19d87e8d5dc..4e813fc91f786 100644 --- a/modules/issue/template/template.go +++ b/modules/issue/template/template.go @@ -151,7 +151,7 @@ func validateOptions(field *api.IssueFormField, idx int) error { } position := newErrorPosition(idx, field.Type) - options, ok := field.Attributes["options"].([]interface{}) + options, ok := field.Attributes["options"].([]any) if !ok || len(options) == 0 { return position.Errorf("'options' is required and should be a array") } @@ -164,7 +164,7 @@ func validateOptions(field *api.IssueFormField, idx int) error { return position.Errorf("should be a string") } case api.IssueFormFieldTypeCheckboxes: - opt, ok := option.(map[string]interface{}) + opt, ok := option.(map[string]any) if !ok { return position.Errorf("should be a dictionary") } @@ -182,7 +182,7 @@ func validateOptions(field *api.IssueFormField, idx int) error { return nil } -func validateStringItem(position errorPosition, m map[string]interface{}, required bool, names ...string) error { +func validateStringItem(position errorPosition, m map[string]any, required bool, names ...string) error { for _, name := range names { v, ok := m[name] if !ok { @@ -202,7 +202,7 @@ func validateStringItem(position errorPosition, m map[string]interface{}, requir return nil } -func validateBoolItem(position errorPosition, m map[string]interface{}, names ...string) error { +func validateBoolItem(position errorPosition, m map[string]any, names ...string) error { for _, name := range names { v, ok := m[name] if !ok { @@ -217,7 +217,7 @@ func validateBoolItem(position errorPosition, m map[string]interface{}, names .. type errorPosition string -func (p errorPosition) Errorf(format string, a ...interface{}) error { +func (p errorPosition) Errorf(format string, a ...any) error { return fmt.Errorf(string(p)+": "+format, a...) } @@ -332,7 +332,7 @@ func (f *valuedField) Value() string { } func (f *valuedField) Options() []*valuedOption { - if options, ok := f.Attributes["options"].([]interface{}); ok { + if options, ok := f.Attributes["options"].([]any); ok { ret := make([]*valuedOption, 0, len(options)) for i, option := range options { ret = append(ret, &valuedOption{ @@ -348,7 +348,7 @@ func (f *valuedField) Options() []*valuedOption { type valuedOption struct { index int - data interface{} + data any field *valuedField } @@ -359,7 +359,7 @@ func (o *valuedOption) Label() string { return label } case api.IssueFormFieldTypeCheckboxes: - if vs, ok := o.data.(map[string]interface{}); ok { + if vs, ok := o.data.(map[string]any); ok { if v, ok := vs["label"].(string); ok { return v } diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go index 0cdddd0c85f9d..06e6b70d35f64 100644 --- a/modules/issue/template/template_test.go +++ b/modules/issue/template/template_test.go @@ -387,34 +387,34 @@ body: { Type: "markdown", ID: "id1", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "value": "Value of the markdown", }, }, { Type: "textarea", ID: "id2", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "label": "Label of textarea", "description": "Description of textarea", "placeholder": "Placeholder of textarea", "value": "Value of textarea", "render": "bash", }, - Validations: map[string]interface{}{ + Validations: map[string]any{ "required": true, }, }, { Type: "input", ID: "id3", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "label": "Label of input", "description": "Description of input", "placeholder": "Placeholder of input", "value": "Value of input", }, - Validations: map[string]interface{}{ + Validations: map[string]any{ "required": true, "is_number": true, "regex": "[a-zA-Z0-9]+", @@ -423,30 +423,30 @@ body: { Type: "dropdown", ID: "id4", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "label": "Label of dropdown", "description": "Description of dropdown", "multiple": true, - "options": []interface{}{ + "options": []any{ "Option 1 of dropdown", "Option 2 of dropdown", "Option 3 of dropdown", }, }, - Validations: map[string]interface{}{ + Validations: map[string]any{ "required": true, }, }, { Type: "checkboxes", ID: "id5", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "label": "Label of checkboxes", "description": "Description of checkboxes", - "options": []interface{}{ - map[string]interface{}{"label": "Option 1 of checkboxes", "required": true}, - map[string]interface{}{"label": "Option 2 of checkboxes", "required": false}, - map[string]interface{}{"label": "Option 3 of checkboxes", "required": true}, + "options": []any{ + map[string]any{"label": "Option 1 of checkboxes", "required": true}, + map[string]any{"label": "Option 2 of checkboxes", "required": false}, + map[string]any{"label": "Option 3 of checkboxes", "required": true}, }, }, }, @@ -479,7 +479,7 @@ body: { Type: "markdown", ID: "id1", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "value": "Value of the markdown", }, }, @@ -512,7 +512,7 @@ body: { Type: "markdown", ID: "id1", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "value": "Value of the markdown", }, }, @@ -545,7 +545,7 @@ body: { Type: "markdown", ID: "id1", - Attributes: map[string]interface{}{ + Attributes: map[string]any{ "value": "Value of the markdown", }, }, diff --git a/modules/json/json.go b/modules/json/json.go index 5f8c474e05264..34568c75c6e4b 100644 --- a/modules/json/json.go +++ b/modules/json/json.go @@ -15,18 +15,18 @@ import ( // Encoder represents an encoder for json type Encoder interface { - Encode(v interface{}) error + Encode(v any) error } // Decoder represents a decoder for json type Decoder interface { - Decode(v interface{}) error + Decode(v any) error } // Interface represents an interface to handle json data type Interface interface { - Marshal(v interface{}) ([]byte, error) - Unmarshal(data []byte, v interface{}) error + Marshal(v any) ([]byte, error) + Unmarshal(data []byte, v any) error NewEncoder(writer io.Writer) Encoder NewDecoder(reader io.Reader) Decoder Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error @@ -44,12 +44,12 @@ var ( type StdJSON struct{} // Marshal implements Interface -func (StdJSON) Marshal(v interface{}) ([]byte, error) { +func (StdJSON) Marshal(v any) ([]byte, error) { return json.Marshal(v) } // Unmarshal implements Interface -func (StdJSON) Unmarshal(data []byte, v interface{}) error { +func (StdJSON) Unmarshal(data []byte, v any) error { return json.Unmarshal(data, v) } @@ -74,12 +74,12 @@ type JSONiter struct { } // Marshal implements Interface -func (j JSONiter) Marshal(v interface{}) ([]byte, error) { +func (j JSONiter) Marshal(v any) ([]byte, error) { return j.API.Marshal(v) } // Unmarshal implements Interface -func (j JSONiter) Unmarshal(data []byte, v interface{}) error { +func (j JSONiter) Unmarshal(data []byte, v any) error { return j.API.Unmarshal(data, v) } @@ -99,12 +99,12 @@ func (j JSONiter) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) e } // Marshal converts object as bytes -func Marshal(v interface{}) ([]byte, error) { +func Marshal(v any) ([]byte, error) { return DefaultJSONHandler.Marshal(v) } // Unmarshal decodes object from bytes -func Unmarshal(data []byte, v interface{}) error { +func Unmarshal(data []byte, v any) error { return DefaultJSONHandler.Unmarshal(data, v) } @@ -124,7 +124,7 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { } // MarshalIndent copied from encoding/json -func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { +func MarshalIndent(v any, prefix, indent string) ([]byte, error) { b, err := Marshal(v) if err != nil { return nil, err @@ -144,7 +144,7 @@ func Valid(data []byte) bool { // UnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's // possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe. -func UnmarshalHandleDoubleEncode(bs []byte, v interface{}) error { +func UnmarshalHandleDoubleEncode(bs []byte, v any) error { err := json.Unmarshal(bs, v) if err != nil { ok := true diff --git a/modules/log/logger_global.go b/modules/log/logger_global.go index 5ccef34b5b756..994acfedbb3a1 100644 --- a/modules/log/logger_global.go +++ b/modules/log/logger_global.go @@ -79,5 +79,5 @@ func SetConsoleLogger(loggerName, writerName string, level Level) { Colorize: CanColorStdout, WriterOption: WriterConsoleOption{}, }) - GetManager().GetLogger(loggerName).RemoveAllWriters().AddWriters(writer) + GetManager().GetLogger(loggerName).ReplaceAllWriters(writer) } diff --git a/modules/log/logger_impl.go b/modules/log/logger_impl.go index 903d8cefc2a5a..d38c6516ed5ce 100644 --- a/modules/log/logger_impl.go +++ b/modules/log/logger_impl.go @@ -96,7 +96,10 @@ func (l *LoggerImpl) removeWriterInternal(w EventWriter) { func (l *LoggerImpl) AddWriters(writer ...EventWriter) { l.eventWriterMu.Lock() defer l.eventWriterMu.Unlock() + l.addWritersInternal(writer...) +} +func (l *LoggerImpl) addWritersInternal(writer ...EventWriter) { for _, w := range writer { if old, ok := l.eventWriters[w.GetWriterName()]; ok { l.removeWriterInternal(old) @@ -126,8 +129,8 @@ func (l *LoggerImpl) RemoveWriter(modeName string) error { return nil } -// RemoveAllWriters removes all writers from the logger, non-shared writers are closed and flushed -func (l *LoggerImpl) RemoveAllWriters() *LoggerImpl { +// ReplaceAllWriters replaces all writers from the logger, non-shared writers are closed and flushed +func (l *LoggerImpl) ReplaceAllWriters(writer ...EventWriter) { l.eventWriterMu.Lock() defer l.eventWriterMu.Unlock() @@ -135,8 +138,7 @@ func (l *LoggerImpl) RemoveAllWriters() *LoggerImpl { l.removeWriterInternal(w) } l.eventWriters = map[string]EventWriter{} - l.syncLevelInternal() - return l + l.addWritersInternal(writer...) } // DumpWriters dumps the writers as a JSON map, it's used for debugging and display purposes. @@ -161,7 +163,7 @@ func (l *LoggerImpl) DumpWriters() map[string]any { // Close closes the logger, non-shared writers are closed and flushed func (l *LoggerImpl) Close() { - l.RemoveAllWriters() + l.ReplaceAllWriters() l.ctxCancel() } @@ -233,7 +235,6 @@ func NewLoggerWithWriters(ctx context.Context, name string, writer ...EventWrite l.ctx, l.ctxCancel = newProcessTypedContext(ctx, "Logger: "+name) l.LevelLogger = BaseLoggerToGeneralLogger(l) l.eventWriters = map[string]EventWriter{} - l.syncLevelInternal() l.AddWriters(writer...) return l } diff --git a/modules/log/manager_test.go b/modules/log/manager_test.go index aa01f79980c59..b8fbf846133c5 100644 --- a/modules/log/manager_test.go +++ b/modules/log/manager_test.go @@ -23,7 +23,7 @@ func TestSharedWorker(t *testing.T) { loggerTest := m.GetLogger("test") loggerTest.AddWriters(w) loggerTest.Info("msg-1") - loggerTest.RemoveAllWriters() // the shared writer is not closed here + loggerTest.ReplaceAllWriters() // the shared writer is not closed here loggerTest.Info("never seen") // the shared writer can still be used later diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 7e04b03531d87..00ffe45c285b3 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -120,7 +120,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { isExternal = true } - links := make([]interface{}, len(indices)) + links := make([]any, len(indices)) for i, index := range indices { links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker) } @@ -204,7 +204,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) { // alphanumeric: render inputs with valid mentions test := func(s, expectedFmt string, names ...string) { - links := make([]interface{}, len(names)) + links := make([]any, len(names)) for i, name := range names { links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) } @@ -226,7 +226,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) { test := func(s, expectedFmt, pattern string, ids, names []string) { metas := regexpMetas metas["regexp"] = pattern - links := make([]interface{}, len(ids)) + links := make([]any, len(ids)) for i, id := range ids { links[i] = link(util.URLJoin("https://someurl.com/someUser/someRepo/", id), "ref-issue ref-external-issue", names[i]) } diff --git a/modules/markup/markdown/meta.go b/modules/markup/markdown/meta.go index bbefbd380c255..e76b253ecd263 100644 --- a/modules/markup/markdown/meta.go +++ b/modules/markup/markdown/meta.go @@ -55,14 +55,14 @@ func isYAMLSeparator(line []byte) bool { // ExtractMetadata consumes a markdown file, parses YAML frontmatter, // and returns the frontmatter metadata separated from the markdown content -func ExtractMetadata(contents string, out interface{}) (string, error) { +func ExtractMetadata(contents string, out any) (string, error) { body, err := ExtractMetadataBytes([]byte(contents), out) return string(body), err } // ExtractMetadata consumes a markdown file, parses YAML frontmatter, // and returns the frontmatter metadata separated from the markdown content -func ExtractMetadataBytes(contents []byte, out interface{}) ([]byte, error) { +func ExtractMetadataBytes(contents []byte, out any) ([]byte, error) { var front, body []byte start, end := 0, len(contents) diff --git a/modules/migration/comment.go b/modules/migration/comment.go index 92ce30e302023..e041758467d46 100644 --- a/modules/migration/comment.go +++ b/modules/migration/comment.go @@ -24,7 +24,7 @@ type Comment struct { Updated time.Time Content string Reactions []*Reaction - Meta map[string]interface{} `yaml:"meta,omitempty"` // see models/issues/comment.go for fields in Comment struct + Meta map[string]any `yaml:"meta,omitempty"` // see models/issues/comment.go for fields in Comment struct } // GetExternalName ExternalUserMigrated interface diff --git a/modules/migration/downloader.go b/modules/migration/downloader.go index ebd3672d63699..08dbbc29a9958 100644 --- a/modules/migration/downloader.go +++ b/modules/migration/downloader.go @@ -34,4 +34,4 @@ type DownloaderFactory interface { } // DownloaderContext has opaque information only relevant to a given downloader -type DownloaderContext interface{} +type DownloaderContext any diff --git a/modules/migration/file_format.go b/modules/migration/file_format.go index 04e5d76981336..e8b6891ca118f 100644 --- a/modules/migration/file_format.go +++ b/modules/migration/file_format.go @@ -17,7 +17,7 @@ import ( ) // Load project data from file, with optional validation -func Load(filename string, data interface{}, validation bool) error { +func Load(filename string, data any, validation bool) error { isJSON := strings.HasSuffix(filename, ".json") bs, err := os.ReadFile(filename) @@ -34,7 +34,7 @@ func Load(filename string, data interface{}, validation bool) error { return unmarshal(bs, data, isJSON) } -func unmarshal(bs []byte, data interface{}, isJSON bool) error { +func unmarshal(bs []byte, data any, isJSON bool) error { if isJSON { return json.Unmarshal(bs, data) } @@ -47,8 +47,8 @@ func getSchema(filename string) (*jsonschema.Schema, error) { return c.Compile(filename) } -func validate(bs []byte, datatype interface{}, isJSON bool) error { - var v interface{} +func validate(bs []byte, datatype any, isJSON bool) error { + var v any err := unmarshal(bs, &v, isJSON) if err != nil { return err @@ -81,11 +81,11 @@ func validate(bs []byte, datatype interface{}, isJSON bool) error { return err } -func toStringKeys(val interface{}) (interface{}, error) { +func toStringKeys(val any) (any, error) { var err error switch val := val.(type) { - case map[string]interface{}: - m := make(map[string]interface{}) + case map[string]any: + m := make(map[string]any) for k, v := range val { m[k], err = toStringKeys(v) if err != nil { @@ -93,8 +93,8 @@ func toStringKeys(val interface{}) (interface{}, error) { } } return m, nil - case []interface{}: - l := make([]interface{}, len(val)) + case []any: + l := make([]any, len(val)) for i, v := range val { l[i], err = toStringKeys(v) if err != nil { diff --git a/modules/migration/messenger.go b/modules/migration/messenger.go index 1fd5456259ea4..924aac976943c 100644 --- a/modules/migration/messenger.go +++ b/modules/migration/messenger.go @@ -4,7 +4,7 @@ package migration // Messenger is a formatting function similar to i18n.Tr -type Messenger func(key string, args ...interface{}) +type Messenger func(key string, args ...any) // NilMessenger represents an empty formatting function -func NilMessenger(string, ...interface{}) {} +func NilMessenger(string, ...any) {} diff --git a/modules/nosql/manager_leveldb.go b/modules/nosql/manager_leveldb.go index 078a8028116f8..4d2c90debc66b 100644 --- a/modules/nosql/manager_leveldb.go +++ b/modules/nosql/manager_leveldb.go @@ -54,7 +54,7 @@ func (m *Manager) GetLevelDB(connection string) (db *leveldb.DB, err error) { // Because we want associate any goroutines created by this call to the main nosqldb context we need to // wrap this in a goroutine labelled with the nosqldb context done := make(chan struct{}) - var recovered interface{} + var recovered any go func() { defer func() { recovered = recover() diff --git a/modules/nosql/manager_redis.go b/modules/nosql/manager_redis.go index 7066863b89f8f..3c82651541f2c 100644 --- a/modules/nosql/manager_redis.go +++ b/modules/nosql/manager_redis.go @@ -47,7 +47,7 @@ func (m *Manager) GetRedisClient(connection string) (client redis.UniversalClien // Because we want associate any goroutines created by this call to the main nosqldb context we need to // wrap this in a goroutine labelled with the nosqldb context done := make(chan struct{}) - var recovered interface{} + var recovered any go func() { defer func() { recovered = recover() diff --git a/modules/packages/composer/metadata.go b/modules/packages/composer/metadata.go index 36b0b8e421974..1d0f025648896 100644 --- a/modules/packages/composer/metadata.go +++ b/modules/packages/composer/metadata.go @@ -38,18 +38,18 @@ type Package struct { // Metadata represents the metadata of a Composer package type Metadata struct { - Description string `json:"description,omitempty"` - Keywords []string `json:"keywords,omitempty"` - Homepage string `json:"homepage,omitempty"` - License Licenses `json:"license,omitempty"` - Authors []Author `json:"authors,omitempty"` - Autoload map[string]interface{} `json:"autoload,omitempty"` - AutoloadDev map[string]interface{} `json:"autoload-dev,omitempty"` - Extra map[string]interface{} `json:"extra,omitempty"` - Require map[string]string `json:"require,omitempty"` - RequireDev map[string]string `json:"require-dev,omitempty"` - Suggest map[string]string `json:"suggest,omitempty"` - Provide map[string]string `json:"provide,omitempty"` + Description string `json:"description,omitempty"` + Keywords []string `json:"keywords,omitempty"` + Homepage string `json:"homepage,omitempty"` + License Licenses `json:"license,omitempty"` + Authors []Author `json:"authors,omitempty"` + Autoload map[string]any `json:"autoload,omitempty"` + AutoloadDev map[string]any `json:"autoload-dev,omitempty"` + Extra map[string]any `json:"extra,omitempty"` + Require map[string]string `json:"require,omitempty"` + RequireDev map[string]string `json:"require-dev,omitempty"` + Suggest map[string]string `json:"suggest,omitempty"` + Provide map[string]string `json:"provide,omitempty"` } // Licenses represents the licenses of a Composer package diff --git a/modules/packages/content_store.go b/modules/packages/content_store.go index 1181fa4d528be..da93e6cf6bcbb 100644 --- a/modules/packages/content_store.go +++ b/modules/packages/content_store.go @@ -5,9 +5,11 @@ package packages import ( "io" + "net/url" "path" "strings" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" ) @@ -31,6 +33,14 @@ func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) { return s.store.Open(KeyToRelativePath(key)) } +func (s *ContentStore) ShouldServeDirect() bool { + return setting.Packages.Storage.MinioConfig.ServeDirect +} + +func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string) (*url.URL, error) { + return s.store.URL(KeyToRelativePath(key), filename) +} + // FIXME: Workaround to be removed in v1.20 // https://github.com/go-gitea/gitea/issues/19586 func (s *ContentStore) Has(key BlobHash256Key) error { diff --git a/modules/packages/helm/metadata.go b/modules/packages/helm/metadata.go index fdbd9003b84c2..421fc5e7259ca 100644 --- a/modules/packages/helm/metadata.go +++ b/modules/packages/helm/metadata.go @@ -55,14 +55,14 @@ type Maintainer struct { } type Dependency struct { - Name string `json:"name" yaml:"name"` - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Repository string `json:"repository" yaml:"repository"` - Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` - Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` - ImportValues []interface{} `json:"import_values,omitempty" yaml:"import-values,omitempty"` - Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` + Name string `json:"name" yaml:"name"` + Version string `json:"version,omitempty" yaml:"version,omitempty"` + Repository string `json:"repository" yaml:"repository"` + Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + ImportValues []any `json:"import_values,omitempty" yaml:"import-values,omitempty"` + Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` } // ParseChartArchive parses the metadata of a Helm archive diff --git a/modules/packages/pub/metadata.go b/modules/packages/pub/metadata.go index 13a066afacc9d..afb464e462057 100644 --- a/modules/packages/pub/metadata.go +++ b/modules/packages/pub/metadata.go @@ -38,12 +38,12 @@ type Package struct { // Metadata represents the metadata of a Pub package type Metadata struct { - Description string `json:"description,omitempty"` - ProjectURL string `json:"project_url,omitempty"` - RepositoryURL string `json:"repository_url,omitempty"` - DocumentationURL string `json:"documentation_url,omitempty"` - Readme string `json:"readme,omitempty"` - Pubspec interface{} `json:"pubspec"` + Description string `json:"description,omitempty"` + ProjectURL string `json:"project_url,omitempty"` + RepositoryURL string `json:"repository_url,omitempty"` + DocumentationURL string `json:"documentation_url,omitempty"` + Readme string `json:"readme,omitempty"` + Pubspec any `json:"pubspec"` } type pubspecPackage struct { @@ -134,7 +134,7 @@ func ParsePubspecMetadata(r io.Reader) (*Package, error) { p.Repository = "" } - var pubspec interface{} + var pubspec any if err := yaml.Unmarshal(buf, &pubspec); err != nil { return nil, err } diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go index efb2ba34a4921..8878dcf9732f5 100644 --- a/modules/packages/rubygems/marshal.go +++ b/modules/packages/rubygems/marshal.go @@ -40,19 +40,19 @@ var ( // RubyUserMarshal is a Ruby object that has a marshal_load function. type RubyUserMarshal struct { Name string - Value interface{} + Value any } // RubyUserDef is a Ruby object that has a _load function. type RubyUserDef struct { Name string - Value interface{} + Value any } // RubyObject is a default Ruby object. type RubyObject struct { Name string - Member map[string]interface{} + Member map[string]any } // MarshalEncoder mimics Rubys Marshal class. @@ -71,7 +71,7 @@ func NewMarshalEncoder(w io.Writer) *MarshalEncoder { } // Encode encodes the given type -func (e *MarshalEncoder) Encode(v interface{}) error { +func (e *MarshalEncoder) Encode(v any) error { if _, err := e.w.Write([]byte{majorVersion, minorVersion}); err != nil { return err } @@ -83,7 +83,7 @@ func (e *MarshalEncoder) Encode(v interface{}) error { return e.w.Flush() } -func (e *MarshalEncoder) marshal(v interface{}) error { +func (e *MarshalEncoder) marshal(v any) error { if v == nil { return e.marshalNil() } diff --git a/modules/packages/rubygems/marshal_test.go b/modules/packages/rubygems/marshal_test.go index 501757bfeda67..6d2354cd876ca 100644 --- a/modules/packages/rubygems/marshal_test.go +++ b/modules/packages/rubygems/marshal_test.go @@ -12,7 +12,7 @@ import ( func TestMinimalEncoder(t *testing.T) { cases := []struct { - Value interface{} + Value any Expected []byte Error error }{ @@ -73,7 +73,7 @@ func TestMinimalEncoder(t *testing.T) { { Value: &RubyObject{ Name: "Test", - Member: map[string]interface{}{ + Member: map[string]any{ "test": 4, }, }, diff --git a/modules/packages/rubygems/metadata.go b/modules/packages/rubygems/metadata.go index adc1c05808740..8a9794860ebfb 100644 --- a/modules/packages/rubygems/metadata.go +++ b/modules/packages/rubygems/metadata.go @@ -65,12 +65,12 @@ type gemspec struct { Version struct { Version string `yaml:"version"` } `yaml:"version"` - Platform string `yaml:"platform"` - Authors []string `yaml:"authors"` - Autorequire interface{} `yaml:"autorequire"` - Bindir string `yaml:"bindir"` - CertChain []interface{} `yaml:"cert_chain"` - Date string `yaml:"date"` + Platform string `yaml:"platform"` + Authors []string `yaml:"authors"` + Autorequire any `yaml:"autorequire"` + Bindir string `yaml:"bindir"` + CertChain []any `yaml:"cert_chain"` + Date string `yaml:"date"` Dependencies []struct { Name string `yaml:"name"` Requirement requirement `yaml:"requirement"` @@ -78,34 +78,34 @@ type gemspec struct { Prerelease bool `yaml:"prerelease"` VersionRequirements requirement `yaml:"version_requirements"` } `yaml:"dependencies"` - Description string `yaml:"description"` - Executables []string `yaml:"executables"` - Extensions []interface{} `yaml:"extensions"` - ExtraRdocFiles []string `yaml:"extra_rdoc_files"` - Files []string `yaml:"files"` - Homepage string `yaml:"homepage"` - Licenses []string `yaml:"licenses"` + Description string `yaml:"description"` + Executables []string `yaml:"executables"` + Extensions []any `yaml:"extensions"` + ExtraRdocFiles []string `yaml:"extra_rdoc_files"` + Files []string `yaml:"files"` + Homepage string `yaml:"homepage"` + Licenses []string `yaml:"licenses"` Metadata struct { BugTrackerURI string `yaml:"bug_tracker_uri"` ChangelogURI string `yaml:"changelog_uri"` DocumentationURI string `yaml:"documentation_uri"` SourceCodeURI string `yaml:"source_code_uri"` } `yaml:"metadata"` - PostInstallMessage interface{} `yaml:"post_install_message"` - RdocOptions []interface{} `yaml:"rdoc_options"` - RequirePaths []string `yaml:"require_paths"` - RequiredRubyVersion requirement `yaml:"required_ruby_version"` - RequiredRubygemsVersion requirement `yaml:"required_rubygems_version"` - Requirements []interface{} `yaml:"requirements"` - RubygemsVersion string `yaml:"rubygems_version"` - SigningKey interface{} `yaml:"signing_key"` - SpecificationVersion int `yaml:"specification_version"` - Summary string `yaml:"summary"` - TestFiles []interface{} `yaml:"test_files"` + PostInstallMessage any `yaml:"post_install_message"` + RdocOptions []any `yaml:"rdoc_options"` + RequirePaths []string `yaml:"require_paths"` + RequiredRubyVersion requirement `yaml:"required_ruby_version"` + RequiredRubygemsVersion requirement `yaml:"required_rubygems_version"` + Requirements []any `yaml:"requirements"` + RubygemsVersion string `yaml:"rubygems_version"` + SigningKey any `yaml:"signing_key"` + SpecificationVersion int `yaml:"specification_version"` + Summary string `yaml:"summary"` + TestFiles []any `yaml:"test_files"` } type requirement struct { - Requirements [][]interface{} `yaml:"requirements"` + Requirements [][]any `yaml:"requirements"` } // AsVersionRequirement converts into []VersionRequirement @@ -119,7 +119,7 @@ func (r requirement) AsVersionRequirement() []VersionRequirement { if !ok { continue } - vm, ok := req[1].(map[string]interface{}) + vm, ok := req[1].(map[string]any) if !ok { continue } diff --git a/modules/private/manager.go b/modules/private/manager.go index 382986bf1db11..6055e553bd0f0 100644 --- a/modules/private/manager.go +++ b/modules/private/manager.go @@ -85,11 +85,11 @@ type LoggerOptions struct { Logger string Writer string Mode string - Config map[string]interface{} + Config map[string]any } // AddLogger adds a logger -func AddLogger(ctx context.Context, logger, writer, mode string, config map[string]interface{}) ResponseExtra { +func AddLogger(ctx context.Context, logger, writer, mode string, config map[string]any) ResponseExtra { reqURL := setting.LocalURL + "api/internal/manager/add-logger" req := newInternalRequest(ctx, reqURL, "POST", LoggerOptions{ Logger: logger, diff --git a/modules/process/context.go b/modules/process/context.go index f1bc582ff069c..26a80ebd6207c 100644 --- a/modules/process/context.go +++ b/modules/process/context.go @@ -24,7 +24,7 @@ func (c *Context) GetParent() *Context { } // Value is part of the interface for context.Context. We mostly defer to the internal context - but we return this in response to the ProcessContextKey -func (c *Context) Value(key interface{}) interface{} { +func (c *Context) Value(key any) any { if key == ProcessContextKey { return c } @@ -32,7 +32,7 @@ func (c *Context) Value(key interface{}) interface{} { } // ProcessContextKey is the key under which process contexts are stored -var ProcessContextKey interface{} = "process-context" +var ProcessContextKey any = "process-context" // GetContext will return a process context if one exists func GetContext(ctx context.Context) *Context { diff --git a/modules/process/manager.go b/modules/process/manager.go index 56908c0408668..9c21f6215210f 100644 --- a/modules/process/manager.go +++ b/modules/process/manager.go @@ -211,7 +211,7 @@ func (pm *Manager) nextPID() (start time.Time, pid IDType) { pid = IDType(strconv.FormatInt(start.Unix(), 16)) if pm.next == 1 { - return + return start, pid } pid = IDType(string(pid) + "-" + strconv.FormatInt(pm.next, 10)) return start, pid diff --git a/modules/repository/branch.go b/modules/repository/branch.go new file mode 100644 index 0000000000000..bffadd62f4d92 --- /dev/null +++ b/modules/repository/branch.go @@ -0,0 +1,135 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "context" + + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/timeutil" +) + +// SyncRepoBranches synchronizes branch table with repository branches +func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) { + repo, err := repo_model.GetRepositoryByID(ctx, repoID) + if err != nil { + return 0, err + } + + log.Debug("SyncRepoBranches: in Repo[%d:%s]", repo.ID, repo.FullName()) + + gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) + if err != nil { + log.Error("OpenRepository[%s]: %w", repo.RepoPath(), err) + return 0, err + } + defer gitRepo.Close() + + return SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID) +} + +func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) { + allBranches := container.Set[string]{} + { + branches, _, err := gitRepo.GetBranchNames(0, 0) + if err != nil { + return 0, err + } + log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches) + for _, branch := range branches { + allBranches.Add(branch) + } + } + + dbBranches := make(map[string]*git_model.Branch) + { + branches, err := git_model.FindBranches(ctx, git_model.FindBranchOptions{ + ListOptions: db.ListOptions{ + ListAll: true, + }, + RepoID: repo.ID, + }) + if err != nil { + return 0, err + } + for _, branch := range branches { + dbBranches[branch.Name] = branch + } + } + + var toAdd []*git_model.Branch + var toUpdate []*git_model.Branch + var toRemove []int64 + for branch := range allBranches { + dbb := dbBranches[branch] + commit, err := gitRepo.GetBranchCommit(branch) + if err != nil { + return 0, err + } + if dbb == nil { + toAdd = append(toAdd, &git_model.Branch{ + RepoID: repo.ID, + Name: branch, + CommitID: commit.ID.String(), + CommitMessage: commit.Summary(), + PusherID: doerID, + CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), + }) + } else if commit.ID.String() != dbb.CommitID { + toUpdate = append(toUpdate, &git_model.Branch{ + ID: dbb.ID, + RepoID: repo.ID, + Name: branch, + CommitID: commit.ID.String(), + CommitMessage: commit.Summary(), + PusherID: doerID, + CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), + }) + } + } + + for _, dbBranch := range dbBranches { + if !allBranches.Contains(dbBranch.Name) && !dbBranch.IsDeleted { + toRemove = append(toRemove, dbBranch.ID) + } + } + + log.Trace("SyncRepoBranches[%s]: toAdd: %v, toUpdate: %v, toRemove: %v", repo.FullName(), toAdd, toUpdate, toRemove) + + if len(toAdd) == 0 && len(toRemove) == 0 && len(toUpdate) == 0 { + return int64(len(allBranches)), nil + } + + if err := db.WithTx(ctx, func(subCtx context.Context) error { + if len(toAdd) > 0 { + if err := git_model.AddBranches(subCtx, toAdd); err != nil { + return err + } + } + + for _, b := range toUpdate { + if _, err := db.GetEngine(subCtx).ID(b.ID). + Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted"). + Update(b); err != nil { + return err + } + } + + if len(toRemove) > 0 { + if err := git_model.DeleteBranches(subCtx, repo.ID, doerID, toRemove); err != nil { + return err + } + } + + return nil + }); err != nil { + return 0, err + } + return int64(len(allBranches)), nil +} diff --git a/modules/repository/create.go b/modules/repository/create.go index 0558d7f1c0068..e8a1b8ba2bf81 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -330,7 +330,7 @@ func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { return fmt.Errorf("updateSize: GetLFSMetaObjects: %w", err) } - return repo_model.UpdateRepoSize(ctx, repo.ID, size+lfsSize) + return repo_model.UpdateRepoSize(ctx, repo.ID, size, lfsSize) } // CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... diff --git a/modules/repository/init.go b/modules/repository/init.go index f079f72b77116..84648f45ebf4f 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -351,6 +351,12 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } + + if !repo.IsEmpty { + if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil { + return fmt.Errorf("SyncRepoBranches: %w", err) + } + } } if err = UpdateRepository(ctx, repo, false); err != nil { diff --git a/modules/repository/repo.go b/modules/repository/repo.go index bcb43f15e1d1f..6a11315cc4043 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -151,6 +151,10 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } } + if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil { + return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err) + } + if !opts.Releases { // note: this will greatly improve release (tag) sync // for pull-mirrors with many tags @@ -169,7 +173,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } diff --git a/modules/session/db.go b/modules/session/db.go index 6fdfb81e03189..f86f7d1e9cf9e 100644 --- a/modules/session/db.go +++ b/modules/session/db.go @@ -17,11 +17,11 @@ import ( type DBStore struct { sid string lock sync.RWMutex - data map[interface{}]interface{} + data map[any]any } // NewDBStore creates and returns a DB session store. -func NewDBStore(sid string, kv map[interface{}]interface{}) *DBStore { +func NewDBStore(sid string, kv map[any]any) *DBStore { return &DBStore{ sid: sid, data: kv, @@ -29,7 +29,7 @@ func NewDBStore(sid string, kv map[interface{}]interface{}) *DBStore { } // Set sets value to given key in session. -func (s *DBStore) Set(key, val interface{}) error { +func (s *DBStore) Set(key, val any) error { s.lock.Lock() defer s.lock.Unlock() @@ -38,7 +38,7 @@ func (s *DBStore) Set(key, val interface{}) error { } // Get gets value by given key in session. -func (s *DBStore) Get(key interface{}) interface{} { +func (s *DBStore) Get(key any) any { s.lock.RLock() defer s.lock.RUnlock() @@ -46,7 +46,7 @@ func (s *DBStore) Get(key interface{}) interface{} { } // Delete delete a key from session. -func (s *DBStore) Delete(key interface{}) error { +func (s *DBStore) Delete(key any) error { s.lock.Lock() defer s.lock.Unlock() @@ -79,7 +79,7 @@ func (s *DBStore) Flush() error { s.lock.Lock() defer s.lock.Unlock() - s.data = make(map[interface{}]interface{}) + s.data = make(map[any]any) return nil } @@ -102,9 +102,9 @@ func (p *DBProvider) Read(sid string) (session.RawStore, error) { return nil, err } - var kv map[interface{}]interface{} + var kv map[any]any if len(s.Data) == 0 || s.Expiry.Add(p.maxLifetime) <= timeutil.TimeStampNow() { - kv = make(map[interface{}]interface{}) + kv = make(map[any]any) } else { kv, err = session.DecodeGob(s.Data) if err != nil { @@ -136,9 +136,9 @@ func (p *DBProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err err return nil, err } - var kv map[interface{}]interface{} + var kv map[any]any if len(s.Data) == 0 || s.Expiry.Add(p.maxLifetime) <= timeutil.TimeStampNow() { - kv = make(map[interface{}]interface{}) + kv = make(map[any]any) } else { kv, err = session.DecodeGob(s.Data) if err != nil { diff --git a/modules/session/redis.go b/modules/session/redis.go index 322470743a510..d89d8bc6e2100 100644 --- a/modules/session/redis.go +++ b/modules/session/redis.go @@ -35,11 +35,11 @@ type RedisStore struct { prefix, sid string duration time.Duration lock sync.RWMutex - data map[interface{}]interface{} + data map[any]any } // NewRedisStore creates and returns a redis session store. -func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { +func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[any]any) *RedisStore { return &RedisStore{ c: c, prefix: prefix, @@ -50,7 +50,7 @@ func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duratio } // Set sets value to given key in session. -func (s *RedisStore) Set(key, val interface{}) error { +func (s *RedisStore) Set(key, val any) error { s.lock.Lock() defer s.lock.Unlock() @@ -59,7 +59,7 @@ func (s *RedisStore) Set(key, val interface{}) error { } // Get gets value by given key in session. -func (s *RedisStore) Get(key interface{}) interface{} { +func (s *RedisStore) Get(key any) any { s.lock.RLock() defer s.lock.RUnlock() @@ -67,7 +67,7 @@ func (s *RedisStore) Get(key interface{}) interface{} { } // Delete delete a key from session. -func (s *RedisStore) Delete(key interface{}) error { +func (s *RedisStore) Delete(key any) error { s.lock.Lock() defer s.lock.Unlock() @@ -100,7 +100,7 @@ func (s *RedisStore) Flush() error { s.lock.Lock() defer s.lock.Unlock() - s.data = make(map[interface{}]interface{}) + s.data = make(map[any]any) return nil } @@ -141,13 +141,13 @@ func (p *RedisProvider) Read(sid string) (session.RawStore, error) { } } - var kv map[interface{}]interface{} + var kv map[any]any kvs, err := p.c.Get(graceful.GetManager().HammerContext(), psid).Result() if err != nil { return nil, err } if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) + kv = make(map[any]any) } else { kv, err = session.DecodeGob([]byte(kvs)) if err != nil { @@ -197,9 +197,9 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err return nil, err } - var kv map[interface{}]interface{} + var kv map[any]any if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) + kv = make(map[any]any) } else { kv, err = session.DecodeGob([]byte(kvs)) if err != nil { diff --git a/modules/session/store.go b/modules/session/store.go index 7b0c550ace397..4fa4d2848f1b8 100644 --- a/modules/session/store.go +++ b/modules/session/store.go @@ -11,9 +11,9 @@ import ( // Store represents a session store type Store interface { - Get(interface{}) interface{} - Set(interface{}, interface{}) error - Delete(interface{}) error + Get(any) any + Set(any, any) error + Delete(any) error } // RegenerateSession regenerates the underlying session and returns the new store diff --git a/modules/session/virtual.go b/modules/session/virtual.go index fcbc44249dc78..80352b6e721de 100644 --- a/modules/session/virtual.go +++ b/modules/session/virtual.go @@ -62,7 +62,7 @@ func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) { if o.provider.Exist(sid) { return o.provider.Read(sid) } - kv := make(map[interface{}]interface{}) + kv := make(map[any]any) kv["_old_uid"] = "0" return NewVirtualStore(o, sid, kv), nil } @@ -107,12 +107,12 @@ type VirtualStore struct { p *VirtualSessionProvider sid string lock sync.RWMutex - data map[interface{}]interface{} + data map[any]any released bool } // NewVirtualStore creates and returns a virtual session store. -func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore { +func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[any]any) *VirtualStore { return &VirtualStore{ p: p, sid: sid, @@ -121,7 +121,7 @@ func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]i } // Set sets value to given key in session. -func (s *VirtualStore) Set(key, val interface{}) error { +func (s *VirtualStore) Set(key, val any) error { s.lock.Lock() defer s.lock.Unlock() @@ -130,7 +130,7 @@ func (s *VirtualStore) Set(key, val interface{}) error { } // Get gets value by given key in session. -func (s *VirtualStore) Get(key interface{}) interface{} { +func (s *VirtualStore) Get(key any) any { s.lock.RLock() defer s.lock.RUnlock() @@ -138,7 +138,7 @@ func (s *VirtualStore) Get(key interface{}) interface{} { } // Delete delete a key from session. -func (s *VirtualStore) Delete(key interface{}) error { +func (s *VirtualStore) Delete(key any) error { s.lock.Lock() defer s.lock.Unlock() @@ -192,6 +192,6 @@ func (s *VirtualStore) Flush() error { s.lock.Lock() defer s.lock.Unlock() - s.data = make(map[interface{}]interface{}) + s.data = make(map[any]any) return nil } diff --git a/modules/setting/actions.go b/modules/setting/actions.go index 1c8075cd6cc55..a13330dcd18a3 100644 --- a/modules/setting/actions.go +++ b/modules/setting/actions.go @@ -5,6 +5,9 @@ package setting import ( "fmt" + "strings" + + "code.gitea.io/gitea/modules/log" ) // Actions settings @@ -13,13 +16,36 @@ var ( LogStorage *Storage // how the created logs should be stored ArtifactStorage *Storage // how the created artifacts should be stored Enabled bool - DefaultActionsURL string `ini:"DEFAULT_ACTIONS_URL"` + DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"` }{ Enabled: false, - DefaultActionsURL: "https://gitea.com", + DefaultActionsURL: defaultActionsURLGitHub, } ) +type defaultActionsURL string + +func (url defaultActionsURL) URL() string { + switch url { + case defaultActionsURLGitHub: + return "https://github.com" + case defaultActionsURLSelf: + return strings.TrimSuffix(AppURL, "/") + default: + // This should never happen, but just in case, use GitHub as fallback + return "https://github.com" + } +} + +const ( + defaultActionsURLGitHub = "github" // https://github.com + defaultActionsURLSelf = "self" // the root URL of the self-hosted Gitea instance + // DefaultActionsURL only supports GitHub and the self-hosted Gitea. + // It's intentionally not supported more, so please be cautious before adding more like "gitea" or "gitlab". + // If you get some trouble with `uses: username/action_name@version` in your workflow, + // please consider to use `uses: https://the_url_you_want_to_use/username/action_name@version` instead. +) + func loadActionsFrom(rootCfg ConfigProvider) error { sec := rootCfg.Section("actions") err := sec.MapTo(&Actions) @@ -27,6 +53,19 @@ func loadActionsFrom(rootCfg ConfigProvider) error { return fmt.Errorf("failed to map Actions settings: %v", err) } + if urls := string(Actions.DefaultActionsURL); urls != defaultActionsURLGitHub && urls != defaultActionsURLSelf { + url := strings.Split(urls, ",")[0] + if strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://") { + log.Error("[actions] DEFAULT_ACTIONS_URL does not support %q as custom URL any longer, fallback to %q", + urls, + defaultActionsURLGitHub, + ) + Actions.DefaultActionsURL = defaultActionsURLGitHub + } else { + return fmt.Errorf("unsupported [actions] DEFAULT_ACTIONS_URL: %q", urls) + } + } + // don't support to read configuration from [actions] Actions.LogStorage, err = getStorage(rootCfg, "actions_log", "", nil) if err != nil { diff --git a/modules/setting/actions_test.go b/modules/setting/actions_test.go index a1cc8fe333b0f..3645a3f5dadc5 100644 --- a/modules/setting/actions_test.go +++ b/modules/setting/actions_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_getStorageInheritNameSectionTypeForActions(t *testing.T) { @@ -95,3 +96,86 @@ STORAGE_TYPE = minio assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) } + +func Test_getDefaultActionsURLForActions(t *testing.T) { + oldActions := Actions + oldAppURL := AppURL + defer func() { + Actions = oldActions + AppURL = oldAppURL + }() + + AppURL = "http://test_get_default_actions_url_for_actions:3000/" + + tests := []struct { + name string + iniStr string + wantErr assert.ErrorAssertionFunc + wantURL string + }{ + { + name: "default", + iniStr: ` +[actions] +`, + wantErr: assert.NoError, + wantURL: "https://github.com", + }, + { + name: "github", + iniStr: ` +[actions] +DEFAULT_ACTIONS_URL = github +`, + wantErr: assert.NoError, + wantURL: "https://github.com", + }, + { + name: "self", + iniStr: ` +[actions] +DEFAULT_ACTIONS_URL = self +`, + wantErr: assert.NoError, + wantURL: "http://test_get_default_actions_url_for_actions:3000", + }, + { + name: "custom url", + iniStr: ` +[actions] +DEFAULT_ACTIONS_URL = https://gitea.com +`, + wantErr: assert.NoError, + wantURL: "https://github.com", + }, + { + name: "custom urls", + iniStr: ` +[actions] +DEFAULT_ACTIONS_URL = https://gitea.com,https://github.com +`, + wantErr: assert.NoError, + wantURL: "https://github.com", + }, + { + name: "invalid", + iniStr: ` +[actions] +DEFAULT_ACTIONS_URL = gitea +`, + wantErr: assert.Error, + wantURL: "https://github.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg, err := NewConfigProviderFromData(tt.iniStr) + require.NoError(t, err) + if !tt.wantErr(t, loadActionsFrom(cfg)) { + return + } + assert.EqualValues(t, tt.wantURL, Actions.DefaultActionsURL.URL()) + }) + } +} diff --git a/modules/setting/cron.go b/modules/setting/cron.go index 45bae4dde37a9..7c4cc44288597 100644 --- a/modules/setting/cron.go +++ b/modules/setting/cron.go @@ -6,11 +6,11 @@ package setting import "reflect" // GetCronSettings maps the cron subsection to the provided config -func GetCronSettings(name string, config interface{}) (interface{}, error) { +func GetCronSettings(name string, config any) (any, error) { return getCronSettings(CfgProvider, name, config) } -func getCronSettings(rootCfg ConfigProvider, name string, config interface{}) (interface{}, error) { +func getCronSettings(rootCfg ConfigProvider, name string, config any) (any, error) { if err := rootCfg.Section("cron." + name).MapTo(config); err != nil { return config, err } diff --git a/modules/setting/log.go b/modules/setting/log.go index af64ea8d85373..66206f8f4b22a 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -244,7 +244,7 @@ func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, logger eventWriters = append(eventWriters, eventWriter) } - manager.GetLogger(loggerName).RemoveAllWriters().AddWriters(eventWriters...) + manager.GetLogger(loggerName).ReplaceAllWriters(eventWriters...) } func InitSQLLoggersForCli(level log.Level) { diff --git a/modules/setting/log_test.go b/modules/setting/log_test.go index c07651f5488fa..87b14f0b1d8d7 100644 --- a/modules/setting/log_test.go +++ b/modules/setting/log_test.go @@ -30,7 +30,7 @@ func initLoggersByConfig(t *testing.T, config string) (*log.LoggerManager, func( return manager, manager.Close } -func toJSON(v interface{}) string { +func toJSON(v any) string { b, _ := json.MarshalIndent(v, "", "\t") return string(b) } diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 9113d72e8e86f..78a9462de9a65 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -116,6 +116,10 @@ func loadOAuth2From(rootCfg ConfigProvider) { return } + if !OAuth2.Enable { + return + } + OAuth2.JWTSecretBase64 = loadSecret(rootCfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET") if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) { diff --git a/modules/setting/ssh.go b/modules/setting/ssh.go index a5a9da0b3676b..bbb7f5ab6cdad 100644 --- a/modules/setting/ssh.go +++ b/modules/setting/ssh.go @@ -173,7 +173,7 @@ func loadSSHFrom(rootCfg ConfigProvider) { } } - SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(true) + SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(false) SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true) SSH.AuthorizedPrincipalsBackup = false diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 81774fb9cfada..e2ce09d07a516 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -173,7 +173,7 @@ func (m minioFileInfo) Mode() os.FileMode { return os.ModePerm } -func (m minioFileInfo) Sys() interface{} { +func (m minioFileInfo) Sys() any { return nil } diff --git a/modules/storage/storage.go b/modules/storage/storage.go index c3396f0c7fbd3..8f970b5dfccc9 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -20,7 +20,7 @@ var ErrURLNotSupported = errors.New("url method not supported") // ErrInvalidConfiguration is called when there is invalid configuration for a storage type ErrInvalidConfiguration struct { - cfg interface{} + cfg any err error } diff --git a/modules/structs/issue.go b/modules/structs/issue.go index a9fb6c6e797b2..1aec5cc6b86c8 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -140,10 +140,10 @@ const ( // IssueFormField represents a form field // swagger:model type IssueFormField struct { - Type IssueFormFieldType `json:"type" yaml:"type"` - ID string `json:"id" yaml:"id"` - Attributes map[string]interface{} `json:"attributes" yaml:"attributes"` - Validations map[string]interface{} `json:"validations" yaml:"validations"` + Type IssueFormFieldType `json:"type" yaml:"type"` + ID string `json:"id" yaml:"id"` + Attributes map[string]any `json:"attributes" yaml:"attributes"` + Validations map[string]any `json:"validations" yaml:"validations"` } // IssueTemplate represents an issue template for a repository diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 3b43f74c7994f..94992de72ebc2 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -380,3 +380,9 @@ type NewIssuePinsAllowed struct { Issues bool `json:"issues"` PullRequests bool `json:"pull_requests"` } + +// UpdateRepoAvatarUserOption options when updating the repo avatar +type UpdateRepoAvatarOption struct { + // image must be base64 encoded + Image string `json:"image" binding:"Required"` +} diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index 48fcebe6f877d..eb4f1c7dca57e 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -166,7 +166,7 @@ type FilesResponse struct { // FileDeleteResponse contains information about a repo's file that was deleted type FileDeleteResponse struct { - Content interface{} `json:"content"` // to be set to nil + Content any `json:"content"` // to be set to nil Commit *FileCommitResponse `json:"commit"` Verification *PayloadCommitVerification `json:"verification"` } diff --git a/modules/structs/repo_watch.go b/modules/structs/repo_watch.go index 2e89a9eefcf59..0d0b7c4ae0483 100644 --- a/modules/structs/repo_watch.go +++ b/modules/structs/repo_watch.go @@ -9,10 +9,10 @@ import ( // WatchInfo represents an API watch status of one repository type WatchInfo struct { - Subscribed bool `json:"subscribed"` - Ignored bool `json:"ignored"` - Reason interface{} `json:"reason"` - CreatedAt time.Time `json:"created_at"` - URL string `json:"url"` - RepositoryURL string `json:"repository_url"` + Subscribed bool `json:"subscribed"` + Ignored bool `json:"ignored"` + Reason any `json:"reason"` + CreatedAt time.Time `json:"created_at"` + URL string `json:"url"` + RepositoryURL string `json:"repository_url"` } diff --git a/modules/structs/user.go b/modules/structs/user.go index f68b92ac069e2..0df67894b0397 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -102,3 +102,9 @@ type RenameUserOption struct { // unique: true NewName string `json:"new_username" binding:"Required"` } + +// UpdateUserAvatarUserOption options when updating the user avatar +type UpdateUserAvatarOption struct { + // image must be base64 encoded + Image string `json:"image" binding:"Required"` +} diff --git a/modules/svg/svg.go b/modules/svg/svg.go index 6db5c7109b2f8..0e2f6a00346aa 100644 --- a/modules/svg/svg.go +++ b/modules/svg/svg.go @@ -49,7 +49,7 @@ func Init() error { } // RenderHTML renders icons - arguments icon name (string), size (int), class (string) -func RenderHTML(icon string, others ...interface{}) template.HTML { +func RenderHTML(icon string, others ...any) template.HTML { size, class := html.ParseSizeAndClass(defaultSize, "", others...) if svgStr, ok := SVGs[icon]; ok { diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 2f2ef44049794..2b918f42c099e 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -27,7 +27,7 @@ import ( // NewFuncMap returns functions for injecting to templates func NewFuncMap() template.FuncMap { - return map[string]interface{}{ + return map[string]any{ "DumpVar": dumpVar, // ----------------------------------------------------------------- @@ -142,8 +142,8 @@ func NewFuncMap() template.FuncMap { "DefaultTheme": func() string { return setting.UI.DefaultTheme }, - "NotificationSettings": func() map[string]interface{} { - return map[string]interface{}{ + "NotificationSettings": func() map[string]any { + return map[string]any{ "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond), "TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond), "MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond), diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go index e0cbc49ff4401..d470435b63d56 100644 --- a/modules/templates/htmlrenderer.go +++ b/modules/templates/htmlrenderer.go @@ -39,7 +39,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, name string, data interface{}) error { +func (h *HTMLRender) HTML(w io.Writer, status int, name string, data any) error { if respWriter, ok := w.(http.ResponseWriter); ok { if respWriter.Header().Get("Content-Type") == "" { respWriter.Header().Set("Content-Type", "text/html; charset=utf-8") diff --git a/modules/templates/scopedtmpl/scopedtmpl.go b/modules/templates/scopedtmpl/scopedtmpl.go index a8b67ad0f908c..2722ba97a28ac 100644 --- a/modules/templates/scopedtmpl/scopedtmpl.go +++ b/modules/templates/scopedtmpl/scopedtmpl.go @@ -15,7 +15,7 @@ import ( ) type TemplateExecutor interface { - Execute(wr io.Writer, data interface{}) error + Execute(wr io.Writer, data any) error } type ScopedTemplate struct { diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go index 3badc97cb9fa2..9f8f8f87a9d6b 100644 --- a/modules/templates/util_avatar.go +++ b/modules/templates/util_avatar.go @@ -30,7 +30,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML { } // Avatar renders user avatars. args: user, size (int), class (string) -func Avatar(ctx context.Context, item interface{}, others ...interface{}) template.HTML { +func Avatar(ctx context.Context, item any, others ...any) template.HTML { size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) switch t := item.(type) { @@ -55,13 +55,13 @@ func Avatar(ctx context.Context, item interface{}, others ...interface{}) templa } // AvatarByAction renders user avatars from action. args: action, size (int), class (string) -func AvatarByAction(ctx context.Context, action *activities_model.Action, others ...interface{}) template.HTML { +func AvatarByAction(ctx context.Context, action *activities_model.Action, others ...any) template.HTML { action.LoadActUser(ctx) return Avatar(ctx, action.ActUser, others...) } // RepoAvatar renders repo avatars. args: repo, size(int), class (string) -func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML { +func RepoAvatar(repo *repo_model.Repository, others ...any) template.HTML { size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) src := repo.RelAvatarLink() @@ -72,7 +72,7 @@ func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTM } // AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string) -func AvatarByEmail(ctx context.Context, email, name string, others ...interface{}) template.HTML { +func AvatarByEmail(ctx context.Context, email, name string, others ...any) template.HTML { size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) src := avatars.GenerateEmailAvatarFastLink(ctx, email, size*setting.Avatar.RenderedSizeFactor) diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go index d11251fcdf653..9cdabeb3aceed 100644 --- a/modules/templates/util_misc.go +++ b/modules/templates/util_misc.go @@ -174,7 +174,7 @@ func FilenameIsImage(filename string) bool { return strings.HasPrefix(mimeType, "image/") } -func TabSizeClass(ec interface{}, filename string) string { +func TabSizeClass(ec any, filename string) string { var ( value *editorconfig.Editorconfig ok bool diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index cf8af32fc69ef..9e7095e116c8a 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -154,7 +154,7 @@ func (tr *mockRender) TemplateLookup(tmpl string) (templates.TemplateExecutor, e return nil, nil } -func (tr *mockRender) HTML(w io.Writer, status int, _ string, _ interface{}) error { +func (tr *mockRender) HTML(w io.Writer, status int, _ string, _ any) error { if resp, ok := w.(http.ResponseWriter); ok { resp.WriteHeader(status) } diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go index 6a0cee4a2998b..4215567c00488 100644 --- a/modules/testlogger/testlogger.go +++ b/modules/testlogger/testlogger.go @@ -137,7 +137,7 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() { } // Printf takes a format and args and prints the string to os.Stdout -func Printf(format string, args ...interface{}) { +func Printf(format string, args ...any) { if log.CanColorStdout { for i := 0; i < len(args); i++ { args[i] = log.NewColoredValue(args[i]) diff --git a/modules/translation/i18n/format.go b/modules/translation/i18n/format.go index 637209ad807e6..e5e221831f5ec 100644 --- a/modules/translation/i18n/format.go +++ b/modules/translation/i18n/format.go @@ -9,12 +9,12 @@ import ( ) // Format formats provided arguments for a given translated message -func Format(format string, args ...interface{}) (msg string, err error) { +func Format(format string, args ...any) (msg string, err error) { if len(args) == 0 { return format, nil } - fmtArgs := make([]interface{}, 0, len(args)) + fmtArgs := make([]any, 0, len(args)) for _, arg := range args { val := reflect.ValueOf(arg) if val.Kind() == reflect.Slice { diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index 4048c9acd2d22..42475545b34a9 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -11,7 +11,7 @@ var DefaultLocales = NewLocaleStore() type Locale interface { // Tr translates a given key and arguments for a language - Tr(trKey string, trArgs ...interface{}) string + Tr(trKey string, trArgs ...any) string // Has reports if a locale has a translation for a given key Has(trKey string) bool } @@ -21,7 +21,7 @@ type LocaleStore interface { io.Closer // Tr translates a given key and arguments for a language - Tr(lang, trKey string, trArgs ...interface{}) string + Tr(lang, trKey string, trArgs ...any) string // Has reports if a locale has a translation for a given key Has(lang, trKey string) bool // SetDefaultLang sets the default language to fall back to diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go index aa784e866fce4..f5a951a79f15c 100644 --- a/modules/translation/i18n/localestore.go +++ b/modules/translation/i18n/localestore.go @@ -86,7 +86,7 @@ func (store *localeStore) SetDefaultLang(lang string) { } // Tr translates content to target language. fall back to default language. -func (store *localeStore) Tr(lang, trKey string, trArgs ...interface{}) string { +func (store *localeStore) Tr(lang, trKey string, trArgs ...any) string { l, _ := store.Locale(lang) return l.Tr(trKey, trArgs...) @@ -119,7 +119,7 @@ func (store *localeStore) Close() error { } // Tr translates content to locale language. fall back to default language. -func (l *locale) Tr(trKey string, trArgs ...interface{}) string { +func (l *locale) Tr(trKey string, trArgs ...any) string { format := trKey idx, ok := l.store.trKeyToIdxMap[trKey] diff --git a/modules/translation/mock.go b/modules/translation/mock.go index 6ce66166aa13d..2d0cb173246ba 100644 --- a/modules/translation/mock.go +++ b/modules/translation/mock.go @@ -14,11 +14,11 @@ func (l MockLocale) Language() string { return "en" } -func (l MockLocale) Tr(s string, _ ...interface{}) string { +func (l MockLocale) Tr(s string, _ ...any) string { return s } -func (l MockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string { +func (l MockLocale) TrN(_cnt any, key1, _keyN string, _args ...any) string { return key1 } diff --git a/modules/util/error.go b/modules/util/error.go index e67b9977f080c..0f3597147ceaa 100644 --- a/modules/util/error.go +++ b/modules/util/error.go @@ -37,7 +37,7 @@ func (w SilentWrap) Unwrap() error { } // NewSilentWrapErrorf returns an error that formats as the given text but unwraps as the provided error -func NewSilentWrapErrorf(unwrap error, message string, args ...interface{}) error { +func NewSilentWrapErrorf(unwrap error, message string, args ...any) error { if len(args) == 0 { return SilentWrap{Message: message, Err: unwrap} } @@ -45,21 +45,21 @@ func NewSilentWrapErrorf(unwrap error, message string, args ...interface{}) erro } // NewInvalidArgumentErrorf returns an error that formats as the given text but unwraps as an ErrInvalidArgument -func NewInvalidArgumentErrorf(message string, args ...interface{}) error { +func NewInvalidArgumentErrorf(message string, args ...any) error { return NewSilentWrapErrorf(ErrInvalidArgument, message, args...) } // NewPermissionDeniedErrorf returns an error that formats as the given text but unwraps as an ErrPermissionDenied -func NewPermissionDeniedErrorf(message string, args ...interface{}) error { +func NewPermissionDeniedErrorf(message string, args ...any) error { return NewSilentWrapErrorf(ErrPermissionDenied, message, args...) } // NewAlreadyExistErrorf returns an error that formats as the given text but unwraps as an ErrAlreadyExist -func NewAlreadyExistErrorf(message string, args ...interface{}) error { +func NewAlreadyExistErrorf(message string, args ...any) error { return NewSilentWrapErrorf(ErrAlreadyExist, message, args...) } // NewNotExistErrorf returns an error that formats as the given text but unwraps as an ErrNotExist -func NewNotExistErrorf(message string, args ...interface{}) error { +func NewNotExistErrorf(message string, args ...any) error { return NewSilentWrapErrorf(ErrNotExist, message, args...) } diff --git a/modules/util/pack.go b/modules/util/pack.go index 315d9f5066ff9..7fc074a2b037f 100644 --- a/modules/util/pack.go +++ b/modules/util/pack.go @@ -9,7 +9,7 @@ import ( ) // PackData uses gob to encode the given data in sequence -func PackData(data ...interface{}) ([]byte, error) { +func PackData(data ...any) ([]byte, error) { var buf bytes.Buffer enc := gob.NewEncoder(&buf) for _, datum := range data { @@ -21,7 +21,7 @@ func PackData(data ...interface{}) ([]byte, error) { } // UnpackData uses gob to decode the given data in sequence -func UnpackData(buf []byte, data ...interface{}) error { +func UnpackData(buf []byte, data ...any) error { r := bytes.NewReader(buf) enc := gob.NewDecoder(r) for _, datum := range data { diff --git a/modules/util/paginate.go b/modules/util/paginate.go index f2721df1adba4..87f31b76ed268 100644 --- a/modules/util/paginate.go +++ b/modules/util/paginate.go @@ -7,7 +7,7 @@ import "reflect" // PaginateSlice cut a slice as per pagination options // if page = 0 it do not paginate -func PaginateSlice(list interface{}, page, pageSize int) interface{} { +func PaginateSlice(list any, page, pageSize int) any { if page <= 0 || pageSize <= 0 { return list } diff --git a/modules/util/rotatingfilewriter/writer.go b/modules/util/rotatingfilewriter/writer.go index 5243bfe353861..c595f49c49b5e 100644 --- a/modules/util/rotatingfilewriter/writer.go +++ b/modules/util/rotatingfilewriter/writer.go @@ -39,10 +39,10 @@ type RotatingFileWriter struct { cancelReleaseReopen func() } -var ErrorPrintf func(format string, args ...interface{}) +var ErrorPrintf func(format string, args ...any) // errorf tries to print error messages. Since this writer could be used by a logger system, this is the last chance to show the error in some cases -func errorf(format string, args ...interface{}) { +func errorf(format string, args ...any) { if ErrorPrintf != nil { ErrorPrintf("rotatingfilewriter: "+format+"\n", args...) } diff --git a/modules/util/util.go b/modules/util/util.go index 148817bd47047..9d5e6c1e89303 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -174,7 +174,7 @@ func ToTitleCaseNoLower(s string) string { } // ToInt64 transform a given int into int64. -func ToInt64(number interface{}) (int64, error) { +func ToInt64(number any) (int64, error) { var value int64 switch v := number.(type) { case int: @@ -216,7 +216,7 @@ func ToInt64(number interface{}) (int64, error) { } // ToFloat64 transform a given int into float64. -func ToFloat64(number interface{}) (float64, error) { +func ToFloat64(number any) (float64, error) { var value float64 switch v := number.(type) { case int: @@ -256,3 +256,8 @@ func ToFloat64(number interface{}) (float64, error) { } return value, nil } + +// ToPointer returns the pointer of a copy of any given value +func ToPointer[T any](val T) *T { + return &val +} diff --git a/modules/util/util_test.go b/modules/util/util_test.go index 8cceafa2f66d7..c5830ce01cb22 100644 --- a/modules/util/util_test.go +++ b/modules/util/util_test.go @@ -224,3 +224,12 @@ func TestToTitleCase(t *testing.T) { assert.Equal(t, ToTitleCase(`foo bar baz`), `Foo Bar Baz`) assert.Equal(t, ToTitleCase(`FOO BAR BAZ`), `Foo Bar Baz`) } + +func TestToPointer(t *testing.T) { + assert.Equal(t, "abc", *ToPointer("abc")) + assert.Equal(t, 123, *ToPointer(123)) + abc := "abc" + assert.False(t, &abc == ToPointer(abc)) + val123 := 123 + assert.False(t, &val123 == ToPointer(val123)) +} diff --git a/modules/validation/binding.go b/modules/validation/binding.go index 1f904979ffd9c..cb0a5063e509b 100644 --- a/modules/validation/binding.go +++ b/modules/validation/binding.go @@ -46,7 +46,7 @@ func addGitRefNameBindingRule() { IsMatch: func(rule string) bool { return strings.HasPrefix(rule, "GitRefName") }, - IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) if !git.IsValidRefPattern(str) { @@ -64,7 +64,7 @@ func addValidURLBindingRule() { IsMatch: func(rule string) bool { return strings.HasPrefix(rule, "ValidUrl") }, - IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) if len(str) != 0 && !IsValidURL(str) { errs.Add([]string{name}, binding.ERR_URL, "Url") @@ -82,7 +82,7 @@ func addValidSiteURLBindingRule() { IsMatch: func(rule string) bool { return strings.HasPrefix(rule, "ValidSiteUrl") }, - IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) if len(str) != 0 && !IsValidSiteURL(str) { errs.Add([]string{name}, binding.ERR_URL, "Url") @@ -103,7 +103,7 @@ func addGlobPatternRule() { }) } -func globPatternValidator(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { +func globPatternValidator(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) if len(str) != 0 { @@ -125,7 +125,7 @@ func addRegexPatternRule() { }) } -func regexPatternValidator(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { +func regexPatternValidator(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) if _, err := regexp.Compile(str); err != nil { @@ -141,7 +141,7 @@ func addGlobOrRegexPatternRule() { IsMatch: func(rule string) bool { return rule == "GlobOrRegexPattern" }, - IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := strings.TrimSpace(fmt.Sprintf("%v", val)) if len(str) >= 2 && strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") { @@ -157,7 +157,7 @@ func addUsernamePatternRule() { IsMatch: func(rule string) bool { return rule == "Username" }, - IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { str := fmt.Sprintf("%v", val) if !IsValidUsername(str) { errs.Add([]string{name}, ErrUsername, "invalid username") @@ -173,7 +173,7 @@ func addValidGroupTeamMapRule() { IsMatch: func(rule string) bool { return strings.HasPrefix(rule, "ValidGroupTeamMap") }, - IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) { _, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val)) if err != nil { errs.Add([]string{name}, ErrInvalidGroupTeamMap, err.Error()) diff --git a/modules/validation/binding_test.go b/modules/validation/binding_test.go index 9ca93784a85c5..01ff4e34356a0 100644 --- a/modules/validation/binding_test.go +++ b/modules/validation/binding_test.go @@ -20,7 +20,7 @@ const ( type ( validationTestCase struct { description string - data interface{} + data any expectedErrors binding.Errors } diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 8b74a864d9571..d9bcdf3b2ad12 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -25,7 +25,7 @@ func init() { } // AssignForm assign form values back to the template data. -func AssignForm(form interface{}, data map[string]interface{}) { +func AssignForm(form any, data map[string]any) { typ := reflect.TypeOf(form) val := reflect.ValueOf(form) @@ -79,7 +79,7 @@ func GetInclude(field reflect.StructField) string { } // Validate validate TODO: -func Validate(errs binding.Errors, data map[string]interface{}, f Form, l translation.Locale) binding.Errors { +func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Locale) binding.Errors { if errs.Len() == 0 { return errs } diff --git a/modules/web/route.go b/modules/web/route.go index db511aeb185e9..8685062a8e5f9 100644 --- a/modules/web/route.go +++ b/modules/web/route.go @@ -25,12 +25,12 @@ func Bind[T any](_ T) http.HandlerFunc { } // SetForm set the form object -func SetForm(dataStore middleware.ContextDataStore, obj interface{}) { +func SetForm(dataStore middleware.ContextDataStore, obj any) { dataStore.GetData()["__form"] = obj } // GetForm returns the validate form information -func GetForm(dataStore middleware.ContextDataStore) interface{} { +func GetForm(dataStore middleware.ContextDataStore) any { return dataStore.GetData()["__form"] } @@ -38,7 +38,7 @@ func GetForm(dataStore middleware.ContextDataStore) interface{} { type Route struct { R chi.Router curGroupPrefix string - curMiddlewares []interface{} + curMiddlewares []any } // NewRoute creates a new route @@ -48,14 +48,14 @@ func NewRoute() *Route { } // Use supports two middlewares -func (r *Route) Use(middlewares ...interface{}) { +func (r *Route) Use(middlewares ...any) { for _, m := range middlewares { r.R.Use(toHandlerProvider(m)) } } // Group mounts a sub-Router along a `pattern` string. -func (r *Route) Group(pattern string, fn func(), middlewares ...interface{}) { +func (r *Route) Group(pattern string, fn func(), middlewares ...any) { previousGroupPrefix := r.curGroupPrefix previousMiddlewares := r.curMiddlewares r.curGroupPrefix += pattern @@ -111,53 +111,53 @@ func (r *Route) Mount(pattern string, subR *Route) { } // Any delegate requests for all methods -func (r *Route) Any(pattern string, h ...interface{}) { +func (r *Route) Any(pattern string, h ...any) { middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h) r.R.With(middlewares...).HandleFunc(r.getPattern(pattern), handlerFunc) } // RouteMethods delegate special methods, it is an alias of "Methods", while the "pattern" is the first parameter -func (r *Route) RouteMethods(pattern, methods string, h ...interface{}) { +func (r *Route) RouteMethods(pattern, methods string, h ...any) { r.Methods(methods, pattern, h) } // Delete delegate delete method -func (r *Route) Delete(pattern string, h ...interface{}) { +func (r *Route) Delete(pattern string, h ...any) { r.Methods("DELETE", pattern, h) } // Get delegate get method -func (r *Route) Get(pattern string, h ...interface{}) { +func (r *Route) Get(pattern string, h ...any) { r.Methods("GET", pattern, h) } // GetOptions delegate get and options method -func (r *Route) GetOptions(pattern string, h ...interface{}) { +func (r *Route) GetOptions(pattern string, h ...any) { r.Methods("GET,OPTIONS", pattern, h) } // PostOptions delegate post and options method -func (r *Route) PostOptions(pattern string, h ...interface{}) { +func (r *Route) PostOptions(pattern string, h ...any) { r.Methods("POST,OPTIONS", pattern, h) } // Head delegate head method -func (r *Route) Head(pattern string, h ...interface{}) { +func (r *Route) Head(pattern string, h ...any) { r.Methods("HEAD", pattern, h) } // Post delegate post method -func (r *Route) Post(pattern string, h ...interface{}) { +func (r *Route) Post(pattern string, h ...any) { r.Methods("POST", pattern, h) } // Put delegate put method -func (r *Route) Put(pattern string, h ...interface{}) { +func (r *Route) Put(pattern string, h ...any) { r.Methods("PUT", pattern, h) } // Patch delegate patch method -func (r *Route) Patch(pattern string, h ...interface{}) { +func (r *Route) Patch(pattern string, h ...any) { r.Methods("PATCH", pattern, h) } @@ -172,7 +172,7 @@ func (r *Route) NotFound(h http.HandlerFunc) { } // Combo delegates requests to Combo -func (r *Route) Combo(pattern string, h ...interface{}) *Combo { +func (r *Route) Combo(pattern string, h ...any) *Combo { return &Combo{r, pattern, h} } @@ -180,35 +180,35 @@ func (r *Route) Combo(pattern string, h ...interface{}) *Combo { type Combo struct { r *Route pattern string - h []interface{} + h []any } // Get delegates Get method -func (c *Combo) Get(h ...interface{}) *Combo { +func (c *Combo) Get(h ...any) *Combo { c.r.Get(c.pattern, append(c.h, h...)...) return c } // Post delegates Post method -func (c *Combo) Post(h ...interface{}) *Combo { +func (c *Combo) Post(h ...any) *Combo { c.r.Post(c.pattern, append(c.h, h...)...) return c } // Delete delegates Delete method -func (c *Combo) Delete(h ...interface{}) *Combo { +func (c *Combo) Delete(h ...any) *Combo { c.r.Delete(c.pattern, append(c.h, h...)...) return c } // Put delegates Put method -func (c *Combo) Put(h ...interface{}) *Combo { +func (c *Combo) Put(h ...any) *Combo { c.r.Put(c.pattern, append(c.h, h...)...) return c } // Patch delegates Patch method -func (c *Combo) Patch(h ...interface{}) *Combo { +func (c *Combo) Patch(h ...any) *Combo { c.r.Patch(c.pattern, append(c.h, h...)...) return c } diff --git a/modules/web/routing/context.go b/modules/web/routing/context.go index 0d5e7655431c2..c5e85a415b1eb 100644 --- a/modules/web/routing/context.go +++ b/modules/web/routing/context.go @@ -37,7 +37,7 @@ func MarkLongPolling(resp http.ResponseWriter, req *http.Request) { } // UpdatePanicError updates a context's error info, a panic may be recovered by other middlewares, but we still need to know that. -func UpdatePanicError(ctx context.Context, err interface{}) { +func UpdatePanicError(ctx context.Context, err any) { record, ok := ctx.Value(contextKey).(*requestRecord) if !ok { return diff --git a/modules/web/routing/funcinfo.go b/modules/web/routing/funcinfo.go index 499bc2c7aad9a..f4e9731a630d4 100644 --- a/modules/web/routing/funcinfo.go +++ b/modules/web/routing/funcinfo.go @@ -35,7 +35,7 @@ func (info *FuncInfo) String() string { } // GetFuncInfo returns the FuncInfo for a provided function and friendlyname -func GetFuncInfo(fn interface{}, friendlyName ...string) *FuncInfo { +func GetFuncInfo(fn any, friendlyName ...string) *FuncInfo { // ptr represents the memory position of the function passed in as v. // This will be used as program counter in FuncForPC below ptr := reflect.ValueOf(fn).Pointer() diff --git a/modules/web/routing/requestrecord.go b/modules/web/routing/requestrecord.go index 34a2d33893018..cc61fc4d34839 100644 --- a/modules/web/routing/requestrecord.go +++ b/modules/web/routing/requestrecord.go @@ -24,5 +24,5 @@ type requestRecord struct { // mutable fields isLongPolling bool funcInfo *FuncInfo - panicError interface{} + panicError any } diff --git a/options/license/Asterisk-exception b/options/license/Asterisk-exception new file mode 100644 index 0000000000000..88253f12d36e9 --- /dev/null +++ b/options/license/Asterisk-exception @@ -0,0 +1,5 @@ +In addition, when this program is distributed with Asterisk in any +form that would qualify as a 'combined work' or as a 'derivative work' +(but not mere aggregation), you can redistribute and/or modify the +combination under the terms of the license provided with that copy +of Asterisk, instead of the license terms granted here. diff --git a/options/license/Boehm-GC b/options/license/Boehm-GC new file mode 100644 index 0000000000000..95427c0b59062 --- /dev/null +++ b/options/license/Boehm-GC @@ -0,0 +1,12 @@ +Copyright (c) ... + +THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED +OR IMPLIED. ANY USE IS AT YOUR OWN RISK. + +Permission is hereby granted to use or copy this program +for any purpose, provided the above notices are retained on all copies. +Permission to modify the code and to distribute modified code is granted, +provided the above notices are retained, and a notice that the code was +modified is included with the above copyright notice. + +A few files have other copyright holders. diff --git a/options/license/Inner-Net-2.0 b/options/license/Inner-Net-2.0 new file mode 100644 index 0000000000000..f8db440f2ae09 --- /dev/null +++ b/options/license/Inner-Net-2.0 @@ -0,0 +1,34 @@ +The Inner Net License, Version 2.00 + +The author(s) grant permission for redistribution and use in source and +binary forms, with or without modification, of the software and documentation +provided that the following conditions are met: + +0. If you receive a version of the software that is specifically labelled + as not being for redistribution (check the version message and/or README), + you are not permitted to redistribute that version of the software in any + way or form. +1. All terms of the all other applicable copyrights and licenses must be + followed. +2. Redistributions of source code must retain the authors' copyright + notice(s), this list of conditions, and the following disclaimer. +3. Redistributions in binary form must reproduce the authors' copyright + notice(s), this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. +4. [The copyright holder has authorized the removal of this clause.] +5. Neither the name(s) of the author(s) nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +If these license terms cause you a real problem, contact the author. diff --git a/options/license/LLGPL b/options/license/LLGPL new file mode 100644 index 0000000000000..889d0b92e5baa --- /dev/null +++ b/options/license/LLGPL @@ -0,0 +1,56 @@ +Preamble to the Gnu Lesser General Public License + +Copyright (c) 2016 Franz Inc., Berkeley, CA 94704 + +The concept of the GNU Lesser General Public License version 2.1 ("LGPL") +has been adopted to govern the use and distribution of above-mentioned +application. However, the LGPL uses terminology that is more appropriate +for a program written in C than one written in Lisp. Nevertheless, the +LGPL can still be applied to a Lisp program if certain clarifications +are made. This document details those clarifications. Accordingly, the +license for the open-source Lisp applications consists of this document +plus the LGPL. Wherever there is a conflict between this document and +the LGPL, this document takes precedence over the LGPL. + +A "Library" in Lisp is a collection of Lisp functions, data and foreign +modules. The form of the Library can be Lisp source code (for processing +by an interpreter) or object code (usually the result of compilation of +source code or built with some other mechanisms). Foreign modules are +object code in a form that can be linked into a Lisp executable. When +we speak of functions we do so in the most general way to include, in +addition, methods and unnamed functions. Lisp "data" is also a general +term that includes the data structures resulting from defining Lisp +classes. A Lisp application may include the same set of Lisp objects +as does a Library, but this does not mean that the application is +necessarily a "work based on the Library" it contains. + +The Library consists of everything in the distribution file set before +any modifications are made to the files. If any of the functions or +classes in the Library are redefined in other files, then those +redefinitions ARE considered a work based on the Library. If additional +methods are added to generic functions in the Library, those additional +methods are NOT considered a work based on the Library. If Library classes +are subclassed, these subclasses are NOT considered a work based on the Library. +If the Library is modified to explicitly call other functions that are neither +part of Lisp itself nor an available add-on module to Lisp, then the functions +called by the modified Library ARE considered a work based on the Library. +The goal is to ensure that the Library will compile and run without getting +undefined function errors. + +It is permitted to add proprietary source code to the Library, but it must +be done in a way such that the Library will still run without that proprietary +code present. Section 5 of the LGPL distinguishes between the case of a +library being dynamically linked at runtime and one being statically linked +at build time. Section 5 of the LGPL states that the former results in an +executable that is a "work that uses the Library." Section 5 of the LGPL +states that the latter results in one that is a "derivative of the Library", +which is therefore covered by the LGPL. Since Lisp only offers one choice, +which is to link the Library into an executable at build time, we declare that, +for the purpose applying the LGPL to the Library, an executable that results +from linking a "work that uses the Library" with the Library is considered a +"work that uses the Library" and is therefore NOT covered by the LGPL. + +Because of this declaration, section 6 of LGPL is not applicable to the Library. +However, in connection with each distribution of this executable, you must also +deliver, in accordance with the terms and conditions of the LGPL, the source code +of Library (or your derivative thereof) that is incorporated into this executable. diff --git a/options/license/Linux-man-pages-one-para b/options/license/Linux-man-pages-1-para similarity index 100% rename from options/license/Linux-man-pages-one-para rename to options/license/Linux-man-pages-1-para diff --git a/options/license/Linux-man-pages-copyleft-2-para b/options/license/Linux-man-pages-copyleft-2-para new file mode 100644 index 0000000000000..b0871675b3c45 --- /dev/null +++ b/options/license/Linux-man-pages-copyleft-2-para @@ -0,0 +1,8 @@ +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. diff --git a/options/license/Linux-man-pages-copyleft-var b/options/license/Linux-man-pages-copyleft-var new file mode 100644 index 0000000000000..1742303553b29 --- /dev/null +++ b/options/license/Linux-man-pages-copyleft-var @@ -0,0 +1,16 @@ +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission +notice are preserved on all copies. + +Permission is granted to copy and distribute modified versions of +this manual under the conditions for verbatim copying, provided +that the entire resulting derived work is distributed under the +terms of a permission notice identical to this one. + +Since the Linux kernel and libraries are constantly changing, this +manual page may be incorrect or out-of-date. The author(s) assume +no responsibility for errors or omissions, or for damages resulting +from the use of the information contained herein. + +Formatted or processed versions of this manual, if unaccompanied by +the source, must acknowledge the copyright and authors of this work. diff --git a/options/license/OPL-UK-3.0 b/options/license/OPL-UK-3.0 new file mode 100644 index 0000000000000..ee8ca4dd81c62 --- /dev/null +++ b/options/license/OPL-UK-3.0 @@ -0,0 +1,114 @@ +United Kingdom Open Parliament Licence v3.0 + +Open Parliament Licence + +You are encouraged to use and re-use the information that +is available under this licence freely and flexibly, with +only a few conditions. Using information under this licence + +Use of copyright and database right material made +available under this licence (the ‘information’) indicates +your acceptance of the terms and conditions below. + +The Licensor grants you a worldwide, royalty-free, +perpetual, non-exclusive licence to use the +information subject to the conditions below. + +This licence does not affect your freedom under +fair dealing or fair use or any other copyright +or database right exceptions and limitations. + +You are free to: + * copy, publish, distribute and transmit the information + * adapt the information + * exploit the information commercially and non-commercially, + for example, by combining it with other information, + or by including it in your own product or application + +You must (where you do any of the above): + * acknowledge the source of the information in your + product or application by including the following + attribution statement and, where possible, provide a + link to this licence: Contains Parliamentary information + licensed under the Open Parliament Licence v3.0. + +These are important conditions of this licence and +if you fail to comply with them the rights granted to +you under this licence, or any similar licence granted +by the Licensor, will end automatically. + +Exemptions + +This licence does not cover the use of: + * personal data in the information; + * information that has neither been published nor disclosed + under information access legislation (including the + Freedom of Information Acts for the UK and Scotland) by or + with the consent of the Licensor; + * the Royal Arms and the Crowned Portcullis; + * third party rights the Licensor is not authorised to license; + * information subject to other intellectual property rights, + including patents, trademarks, and design rights + +Non-endorsment + +This licence does not grant you any right to use the +information in a way that suggests any official status or +that the Licensor endorses you or your use of the Information. + +No warranty + +The information is licensed ‘as is’ and the +Licensor excludes all representations, warranties, +obligations and liabilities in relation to the +information to the maximum extent permitted by law. +The Licensor is not liable for any errors or omissions in +the information and shall not be liable for any loss, injury +or damage of any kind caused by its use. The Licensor does +not guarantee the continued supply of the information. + +Governing law + +This licence is governed by the laws of England and Wales. + +Definitions + +In this licence, the terms below have the following meanings: + +‘Information’ means information protected by copyright +or by database right (for example, literary and +artistic works, content, data and source code) +offered for use under the terms of this licence. + +‘Information Provider’ means either House of Parliament. + +‘Licensor’ means— +(a) in relation to copyright, the Speaker of the House of +Commons and the Clerk of the Parliaments representing +the House of Commons and House of Lords respectively, and +(b) in relation to database right, the Corporate +Officer of the House of Commons and the Corporate +Officer of the House of Lords respectively. + +‘Use’ means doing any act which is restricted by copyright +or database right, whether in the original medium or in any +other medium, and includes without limitation distributing, +copying, adapting and modifying as may be technically +necessary to use it in a different mode or format. + +‘You’ means the natural or legal person, or body of persons +corporate or incorporate, acquiring rights under this licence. + +About the Open Parliament Licence + +This is version 3.0 of the Open Parliament Licence. The +Licensor may, from time to time, issue new versions of the +Open Parliament Licence. However, you may continue to use +information licensed under this version should you wish to do so. + +The information licensed under the Open Parliament +Licence includes Parliamentary information in which +Crown copyright subsists. Further context, best practice +and guidance relating to the re-use of public sector +information can be found in the UK Government Licensing +Framework section on The National Archives website. diff --git a/options/license/dtoa b/options/license/dtoa new file mode 100644 index 0000000000000..6de2b084fc6c5 --- /dev/null +++ b/options/license/dtoa @@ -0,0 +1,14 @@ +The author of this software is David M. Gay. + +Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + +Permission to use, copy, modify, and distribute this software for any +purpose without fee is hereby granted, provided that this entire notice +is included in all copies of any software which is or includes a copy +or modification of this software and in all copies of the supporting +documentation for such software. + +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY +REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index da550884c7983..0559b1f12f11c 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -383,7 +383,7 @@ hi_user_x=Ahoj %s, activate_account=Prosíme, aktivujte si váš účet activate_account.title=%s, prosím aktivujte si váš účet -activate_account.text_1=Ahoj %[1]s, děkujeme za registraci na %[2]! +activate_account.text_1=Ahoj %[1]s, děkujeme za registraci na %[2]s! activate_account.text_2=Pro aktivaci vašeho účtu do %s klikněte na následující odkaz: activate_email=Ověřte vaši e-mailovou adresu @@ -402,8 +402,8 @@ reset_password.text=Klikněte prosím na následující odkaz pro obnovení vaš register_success=Registrace byla úspěšná -issue_assigned.pull=@%[1]s vás přiřadil/a k požadavku na natažení %[2]v repozitáři %[3]s. -issue_assigned.issue=@%[1]s vás přiřadil/a k úkolu %[2]v repozitáři %[3]s. +issue_assigned.pull=@%[1]s vás přiřadil/a k požadavku na natažení %[2]s repozitáři %[3]s. +issue_assigned.issue=@%[1]s vás přiřadil/a k úkolu %[2]s repozitáři %[3]s. issue.x_mentioned_you=@%s vás zmínil/a: issue.action.force_push=%[1]s vynutil/a nahrání %[2]s z %[3]s do %[4]s. @@ -2310,7 +2310,7 @@ members.member_role=Role člena: members.owner=Vlastník members.member=Člen members.remove=Smazat -members.remove.detail=Odstranit %[1]s z %[2]? +members.remove.detail=Odstranit %[1]s z %[2]s? members.leave=Opustit members.leave.detail=Opustit %s? members.invite_desc=Přidat nového člena do %s: diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 10f83b34aad5e..0cffa24938123 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -19,6 +19,7 @@ active_stopwatch=Aktive Zeiterfassung create_new=Erstellen… user_profile_and_more=Profil und Einstellungen… signed_in_as=Angemeldet als +enable_javascript=Diese Website benötigt JavaScript. toc=Inhaltsverzeichnis licenses=Lizenzen return_to_gitea=Zurück zu Gitea @@ -92,6 +93,7 @@ copy_url=URL kopieren copy_branch=Branchenname kopieren copy_success=Kopiert! copy_error=Kopieren fehlgeschlagen +copy_type_unsupported=Dieser Dateityp kann nicht kopiert werden write=Verfassen preview=Vorschau @@ -104,22 +106,42 @@ error=Fehler error404=Die Seite, die du gerade versuchst aufzurufen, existiert entweder nicht oder du bist nicht berechtigt, diese anzusehen. never=Niemals +unknown=Unbekannt rss_feed=RSS Feed +artifacts=Artefakte +concept_system_global=Global concept_code_repository=Repository concept_user_organization=Organisation +show_log_seconds=Sekunden anzeigen [aria] +footer.software=Über die Software +footer.links=Links [heatmap] +less=Weniger +more=Mehr [editor] +buttons.heading.tooltip=Titel hinzufügen +buttons.quote.tooltip=Text zitieren +buttons.code.tooltip=Code hinzufügen +buttons.link.tooltip=Link hinzufügen +buttons.list.ordered.tooltip=Nummerierte Liste hinzufügen +buttons.list.task.tooltip=Aufgabenliste hinzufügen +buttons.mention.tooltip=Benutzer oder Team erwähnen +buttons.ref.tooltip=Issue oder Pull-Request referenzieren +buttons.enable_monospace_font=Monospace-Schrift aktivieren +buttons.disable_monospace_font=Monospace-Schrift deaktivieren [filter] +string.asc=A–Z +string.desc=Z–A [error] occurred=Ein Fehler ist aufgetreten @@ -228,6 +250,7 @@ install_btn_confirm=Gitea installieren test_git_failed=Fehler beim Test des „git“-Befehls: %v sqlite3_not_available=Diese Gitea-Version unterstützt SQLite3 nicht. Bitte lade die offizielle binäre Version von %s herunter (nicht die „gobuild“-Version). invalid_db_setting=Datenbankeinstellungen sind ungültig: %v +invalid_db_table=Die Datenbanktabelle "%s" ist ungültig: %v invalid_repo_path=Repository-Verzeichnis ist ungültig: %v invalid_app_data_path=Der App-Daten-Pfad ist ungültig: %v run_user_not_match=Der „Ausführen als“-Benutzername ist nicht der aktuelle Benutzername: %s -> %s @@ -245,6 +268,7 @@ default_enable_timetracking_popup=Zeiterfassung standardmäßig für neue Reposi no_reply_address=Versteckte E-Mail-Domain no_reply_address_helper=Domain-Name für Benutzer mit einer versteckten Emailadresse. Zum Beispiel wird der Benutzername „Joe“ in Git als „joe@noreply.example.org“ protokolliert, wenn die versteckte E-Mail-Domain „noreply.example.org“ festgelegt ist. password_algorithm=Passwort Hashing Algorithmus +invalid_password_algorithm=Ungültiger Passwort-Hash-Algorithmus [home] uname_holder=E-Mail-Adresse oder Benutzername @@ -279,6 +303,7 @@ users=Benutzer organizations=Organisationen search=Suche code=Code +search.type.tooltip=Suchmodus search.fuzzy=Ähnlich search.match=Genau code_search_unavailable=Derzeit ist die Code-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. @@ -545,6 +570,7 @@ location=Standort update_theme=Theme ändern update_profile=Profil aktualisieren update_language=Sprache aktualisieren +update_language_not_found=Sprache "%s" ist nicht verfügbar. update_language_success=Sprache wurde aktualisiert. update_profile_success=Dein Profil wurde aktualisiert. change_username=Dein Benutzername wurde geändert. @@ -655,6 +681,7 @@ gpg_token_help=Du kannst eine Signatur wie folgt generieren: gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig gpg_token_signature=GPG Textsignatur (armored signature) key_signature_gpg_placeholder=Beginnt mit '-----BEGIN PGP SIGNATURE-----' +verify_gpg_key_success=GPG-Schlüssel "%s" wurde verifiziert. ssh_key_verified=Verifizierter Schlüssel ssh_key_verified_long=Der Schlüssel wurde mit einem Token verifiziert. Er kann verwendet werden, um Commits zu verifizieren, die mit irgendeiner für diesen Nutzer aktivierten E-Mail-Adresse und irgendeiner Identität dieses Schlüssels übereinstimmen. ssh_key_verify=Verifizieren @@ -975,6 +1002,7 @@ download_archive=Repository herunterladen no_desc=Keine Beschreibung quick_guide=Kurzanleitung clone_this_repo=Dieses Repository klonen +cite_this_repo=Dieses Repository zitieren 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. @@ -993,6 +1021,7 @@ issues=Issues pulls=Pull-Requests project_board=Projekte packages=Pakete +actions=Actions labels=Label org_labels_desc=Labels der Organisationsebene, die mit allen Repositories in dieser Organisation verwendet werden können org_labels_desc_manage=verwalten @@ -1012,6 +1041,7 @@ file_view_rendered=Ansicht rendern file_view_raw=Originalformat anzeigen file_permalink=Permalink file_too_large=Die Datei ist zu groß zum Anzeigen. +invisible_runes_header=`Diese Datei enthält unsichtbare Unicode-Zeichen!` ambiguous_character=`%[1]c [U+%04[1]X] kann mit %[2]c [U+%04[2]X] verwechselt werden` escape_control_characters=Escapen @@ -1032,6 +1062,7 @@ download_file=Datei herunterladen normal_view=Normale Ansicht line=zeile lines=Zeilen +from_comment=(Kommentar) editor.add_file=Datei hinzufügen editor.new_file=Neue Datei @@ -1046,6 +1077,7 @@ editor.must_be_on_a_branch=Du musst dich in einem Branch befinden, um Änderunge editor.fork_before_edit=Du musst dieses Repository forken, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen. editor.delete_this_file=Datei löschen editor.must_have_write_access=Du benötigst Schreibzugriff, um Änderungen an dieser Datei vorzuschlagen oder vorzunehmen. +editor.file_delete_success=Datei "%s" wurde gelöscht. editor.name_your_file=Dateinamen eingeben… editor.filename_help=Füge einen Ordner hinzu, indem du seinen Namen und anschließend '/' eingibst. Entferne einen Ordner indem du die Zurücktaste am Anfang des Feldes drückst. editor.or=oder @@ -1681,6 +1713,7 @@ activity.git_stats_deletion_n=%d Löschungen search=Suchen search.search_repo=Repository durchsuchen +search.type.tooltip=Suchmodus search.fuzzy=Ähnlich search.match=Genau search.results=Suchergebnisse für „%s“ in %s @@ -2158,6 +2191,8 @@ branch.delete_html=Branch löschen branch.delete_desc=Das Löschen eines Branches ist permanent. Es KANN NICHT rückgängig gemacht werden. Fortfahren? branch.create_branch=Erstelle Branch %s branch.deleted_by=Von %s gelöscht +branch.restore=Branch "%s" wiederherstellen +branch.download=Branch "%s" herunterladen branch.included_desc=Dieser Branch ist im Standard-Branch enthalten branch.included=Enthalten branch.create_new_branch=Branch aus Branch erstellen: @@ -2165,12 +2200,15 @@ branch.confirm_create_branch=Branch erstellen branch.confirm_rename_branch=Branch umbennen 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. tag.create_tag=Tag %s erstellen tag.create_tag_operation=Tag erstellen tag.confirm_create_tag=Tag erstellen +tag.create_tag_from=Neuen Tag von "%s" erstellen +tag.create_success=Tag "%s" wurde erstellt. topic.manage_topics=Themen verwalten topic.done=Fertig @@ -2407,6 +2445,7 @@ users.created=Registriert am users.last_login=Letzte Anmeldung users.never_login=Hat sich noch nie eingeloggt users.send_register_notify=Benutzer-Registrierungsbenachrichtigung senden +users.new_success=Der Account "%s" wurde erstellt. users.edit=Bearbeiten users.auth_source=Authentifizierungsquelle users.local=Lokal @@ -2770,7 +2809,7 @@ monitor.queue.review=Konfiguration überprüfen monitor.queue.review_add=Worker hinzufügen/prüfen monitor.queue.settings.title=Pool-Einstellungen monitor.queue.settings.maxnumberworkers=Maximale Anzahl an Workern -monitor.queue.settings.maxnumberworkers.placeholder=Derzeit %[1]v +monitor.queue.settings.maxnumberworkers.placeholder=Derzeit %[1]d monitor.queue.settings.maxnumberworkers.error=Die Anzahl der Worker muss eine Zahl sein monitor.queue.settings.submit=Einstellungen aktualisieren monitor.queue.settings.changed=Einstellungen aktualisiert @@ -2979,6 +3018,7 @@ settings.delete.description=Das Löschen eines Pakets ist dauerhaft und kann nic settings.delete.notice=Du bist dabei, %s (%s) zu löschen. Dieser Vorgang ist unwiderruflich. Bist du sicher? settings.delete.success=Das Paket wurde gelöscht. settings.delete.error=Löschen des Pakets fehlgeschlagen. +owner.settings.cargo.initialize=Index initialisieren owner.settings.cleanuprules.enabled=Aktiviert [secrets] @@ -2986,8 +3026,10 @@ value=Wert name=Name [actions] +actions=Actions +status.unknown=Unbekannt runners.id=ID runners.name=Name @@ -2997,10 +3039,16 @@ runners.labels=Labels runners.task_list.run=Ausführen runners.task_list.repository=Repository runners.task_list.commit=Commit +runners.status.unspecified=Unbekannt +runners.status.idle=Inaktiv runners.status.active=Aktiv +runners.status.offline=Offline runners.version=Version +runs.all_workflows=Alle Workflows runs.commit=Commit +runs.pushed_by=Gepushed von +runs.no_matching_runner_helper=Kein passender Runner: %s [projects] diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 288181afcdf0c..e7bd6a9884c37 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -79,6 +79,8 @@ milestones=Ορόσημα ok=OK cancel=Ακύρωση +rerun=Επανεκτέλεση +rerun_all=Επανεκτέλεση όλων save=Αποθήκευση add=Προσθήκη add_all=Προσθήκη Όλων @@ -113,11 +115,19 @@ unknown=Άγνωστη rss_feed=Ροή RSS +pin=Στήριξη +unpin=Άφεση +artifacts=Αντικείμενα +concept_system_global=Γενικό +concept_user_individual=Ατομικό concept_code_repository=Αποθετήριο concept_user_organization=Οργανισμός +show_timestamps=Εμφάνιση χρονοσημάνσεων +show_log_seconds=Εμφάνιση δευτερολέπτων +show_full_screen=Εμφάνιση πλήρους οθόνης [aria] navbar=Γραμμή Πλοήγησης @@ -314,6 +324,7 @@ repos=Αποθετήρια users=Χρήστες organizations=Οργανισμοί search=Αναζήτηση +go_to=Μετάβαση σε code=Κώδικας search.type.tooltip=Τύπος αναζήτησης search.fuzzy=Fuzzy @@ -408,7 +419,7 @@ hi_user_x=Γειά σου %s, activate_account=Παρακαλώ ενεργοποιήστε το λογαριασμό σας activate_account.title=%s, παρακαλώ ενεργοποιήστε το λογαριασμό σας -activate_account.text_1=Γεια σας %[1]s, ευχαριστούμε για την εγγραφή στο %[2]! +activate_account.text_1=Γεια σας %[1]s, ευχαριστούμε για την εγγραφή στο %[2]s! activate_account.text_2=Παρακαλούμε κάντε κλικ στον παρακάτω σύνδεσμο για να ενεργοποιήσετε το λογαριασμό σας μέσα σε %s: activate_email=Επιβεβαιώστε τη διεύθυνση email σας @@ -462,7 +473,7 @@ repo.collaborator.added.subject=%s σας πρόσθεσε στο %s repo.collaborator.added.text=Έχετε προστεθεί ως συνεργάτης του αποθετηρίου: team_invite.subject=%[1]s σας προσκάλεσε να συμμετέχετε στον οργανισμό %[2]s -team_invite.text_1=%[1]s σας προσκάλεσε να συμμετέχετε στην ομάδα %[2]s στον οργανισμός %[3]. +team_invite.text_1=%[1]s σας προσκάλεσε να συμμετέχετε στην ομάδα %[2]s στον οργανισμός %[3]s. team_invite.text_2=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να συμμετάσχετε στην ομάδα: team_invite.text_3=Σημείωση: Αυτή η πρόσκληση προοριζόταν για %[1]s. Αν δεν περιμένατε αυτή την πρόσκληση, μπορείτε να αγνοήσετε αυτό το email. @@ -517,6 +528,7 @@ lang_select_error=Επιλέξτε μια γλώσσα από τη λίστα. username_been_taken=Το όνομα χρήστη χρησιμοποιείται ήδη. username_change_not_local_user=Δεν επιτρέπεται στους μη τοπικούς χρήστες να αλλάξουν το όνομα χρήστη τους. +username_has_not_been_changed=Το όνομα χρήστη δεν άλλαξε repo_name_been_taken=Το όνομα του αποθετηρίου χρησιμοποιείται ήδη. repository_force_private=Η επιλογή Μόνο Ιδιωτικά είναι ενεργοποιημένη: τα ιδιωτικά αποθετήρια δεν μπορούν να δημοσιευθούν. repository_files_already_exist=Αρχεία υπάρχουν ήδη για αυτό το αποθετήριο. Επικοινωνήστε με το διαχειριστή του συστήματος. @@ -565,6 +577,7 @@ target_branch_not_exist=Ο κλάδος προορισμού δεν υπάρχε [user] change_avatar=Αλλαγή του avatar σας… +joined_on=Εγγράφηκε την %s repositories=Αποθετήρια activity=Δημόσια Δραστηριότητα followers=Ακόλουθοι @@ -760,6 +773,8 @@ ssh_principal_deletion_desc=Η διαγραφή μιας αρχής πιστοπ 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=Καμία πρόσφατη δραστηριότητα @@ -791,6 +806,13 @@ access_token_deletion_cancel_action=Άκυρο access_token_deletion_confirm_action=Διαγραφή access_token_deletion_desc=Η διαγραφή ενός διακριτικού θα ανακαλέσει οριστικά την πρόσβαση στο λογαριασμό σας για εφαρμογές που το χρησιμοποιούν. Συνέχεια; delete_token_success=Το διακριτικό έχει διαγραφεί. Οι εφαρμογές που το χρησιμοποιούν δεν έχουν πλέον πρόσβαση στο λογαριασμό σας. +repo_and_org_access=Πρόσβαση στο Αποθετήριο και Οργανισμό +permissions_public_only=Δημόσια μόνο +permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περιορισμένα) +select_permissions=Επιλέξτε δικαιώματα +scoped_token_desc=Το επιλεγμένο διακριτικό περιορίζει την ταυτοποίηση μόνο στις αντίστοιχες διαδρομές API. Συμβουλευτείτε την τεκμηρίωση για περισσότερες πληροφορίες. +at_least_one_permission=Πρέπει να επιλέξετε τουλάχιστον ένα δικαίωμα για να δημιουργήσετε ένα διακριτικό +permissions_list=Δικαιώματα: manage_oauth2_applications=Διαχείριση Εφαρμογών Oauth2 edit_oauth2_application=Επεξεργασία Εφαρμογής Oauth2 @@ -804,6 +826,7 @@ create_oauth2_application_success=Δημιουργήσατε επιτυχώς μ update_oauth2_application_success=Ενημερώσατε επιτυχώς την εφαρμογή OAuth2. oauth2_application_name=Όνομα Εφαρμογής oauth2_confidential_client=Εμπιστευτικός Πελάτης. Επιλέξτε το για εφαρμογές που διατηρούν το μυστικό κωδικό κρυφό, όπως πχ οι εφαρμογές ιστού. Μην επιλέγετε για εγγενείς εφαρμογές, συμπεριλαμβανομένων εφαρμογών επιφάνειας εργασίας και εφαρμογών για κινητά. +oauth2_redirect_uris=URI Ανακατεύθυνσης. Χρησιμοποιήστε μια νέα γραμμή για κάθε URI. save_application=Αποθήκευση oauth2_client_id=Ταυτότητα Πελάτη oauth2_client_secret=Μυστικό Πελάτη @@ -949,6 +972,7 @@ mirror_password_blank_placeholder=(Μη ορισμένο) mirror_password_help=Αλλάξτε το όνομα χρήστη για να διαγράψετε έναν αποθηκευμένο κωδικό πρόσβασης. watchers=Παρατηρητές stargazers=Θαυμαστές +stars_remove_warning=Αυτό θα αφαιρέσει όλα τα αστέρια από αυτό το αποθετήριο. forks=Forks reactions_more=και %d περισσότερα unit_disabled=Ο διαχειριστής του ιστότοπου έχει απενεργοποιήσει αυτήν την ενότητα αποθετηρίου. @@ -992,6 +1016,7 @@ template.one_item=Πρέπει να επιλέξετε τουλάχιστον έ template.invalid=Πρέπει να επιλέξετε ένα πρότυπο αποθετήριο archive.title=Αυτό το αποθετήριο αρχειοθετήθηκε. Μπορείτε να δείτε αρχεία και να το κλωνοποιήσετε, αλλά δεν μπορείτε να γράψετε ή να ανοίξετε ζητήματα/pull-requests. +archive.title_date=Αυτό το αποθετήριο έχει αρχειοθετηθεί τη %s. Μπορείτε να προβάλετε αρχεία και να κλωνοποιήσετε, αλλά δεν μπορείτε να ωθήσετε ή να ανοίξετε ζητήματα και pull-requests. archive.issue.nocomment=Αυτό το αποθετήριο αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε σε ζητήματα. archive.pull.nocomment=Αυτό το repo αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε στα pull requests. @@ -1033,6 +1058,7 @@ migrated_from_fake=Μεταφέρθηκε από %[1]s migrate.migrate=Μεταφορά Από %s migrate.migrating=Γίνεται μεταφορά από %s... migrate.migrating_failed=Η μετεγρατάσταση από %s απέτυχε. +migrate.migrating_failed.error=Αποτυχία μεταφοράς: %s migrate.migrating_failed_no_addr=Η μεταφορά απέτυχε. migrate.github.description=Μεταφορά δεδομένων από το github.com ή άλλους διακομιστές GitHub. migrate.git.description=Μεταφορά μόνο του αποθετηρίου από μια οποιαδήποτε υπηρεσία Git. @@ -1049,6 +1075,8 @@ migrate.migrating_labels=Μεταφορά Σημάτων migrate.migrating_releases=Μεταφορά Κυκλοφοριών migrate.migrating_issues=Μετανάστευση Θεμάτων migrate.migrating_pulls=Μεταφορά Pull Requests +migrate.cancel_migrating_title=Ακύρωση Μεταφοράς +migrate.cancel_migrating_confirm=Θέλετε να ακυρώσετε αυτή τη μεταφορά; mirror_from=είδωλο του forked_from=forked από @@ -1178,6 +1206,8 @@ editor.filename_is_invalid=Το όνομα αρχείου δεν είναι έγ editor.branch_does_not_exist=Ο κλάδος "%s" δεν υπάρχει σε αυτό το αποθετήριο. 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=Τα περιεχόμενα του αρχείου άλλαξαν από τότε που ξεκίνησε η επεξεργασία. Κάντε κλικ εδώ για να τα δείτε ή Υποβολή Αλλαγών ξανά για να τα αντικαταστήσετε. @@ -1343,6 +1373,10 @@ issues.filter_label_exclude=`Χρησιμοποιήστε alt + github.com/gobwas/glob για τη σύνταξη του μοτίβου. Πχ: .drone.yml, /docs/**/*.txt. settings.protect_unprotected_file_patterns=Μοτίβα μη προστατευμένων αρχείων (διαχωρισμένα με ερωτηματικό ';'): @@ -2384,10 +2450,13 @@ 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=Δημιουργία νέου κλάδου @@ -2565,7 +2634,7 @@ dashboard.task.process=Εργασία: %[1]s dashboard.task.cancelled=Εργασία: %[1]s ακυρώθηκε: %[3]s dashboard.task.error=Σφάλμα στην Εργασία: %[1]s: %[3]s dashboard.task.finished=Εργασία: %[1]s που εκκινήθηκε από %[2]s τελείωσε -dashboard.task.unknown=Άγνωστη εργασία: %[1] +dashboard.task.unknown=Άγνωστη εργασία: %[1]s dashboard.cron.started=Εκκίνηση Προγραμματισμένης Εργασίας: %[1]s dashboard.cron.process=Προγραμματισμένη Εργασία: %[1]s dashboard.cron.cancelled=Προγραμματισμένη Εργασία: %s ακυρώθηκε: %[3]s @@ -2946,6 +3015,7 @@ config.mailer_sendmail_timeout=Χρονικό Όριο Sendmail config.mailer_use_dummy=Ψεύτικο config.test_email_placeholder=Email (π.χ. test@example.com) config.send_test_mail=Αποστολή Δοκιμαστικού Email +config.send_test_mail_submit=Αποστολή config.test_mail_failed=Αποτυχία αποστολής ενός δοκιμαστικού email στο"%s": %v config.test_mail_sent=Στάλθηκε ένα δοκιμαστικό email στο "%s". @@ -2985,13 +3055,16 @@ 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.get_setting_failed=Αποτυχία λήψης ρύθμισης %s config.set_setting_failed=Αποτυχία ορισμού της ρύθμισης %s +monitor.stats=Στατιστικά monitor.cron=Προγραμματισμένες Εργασίες monitor.name=Όνομα @@ -3001,6 +3074,8 @@ monitor.previous=Προηγούμενη Ώρα monitor.execute_times=Εκτελέσεις monitor.process=Εκτελούμενες Διεργασίες monitor.stacktrace=Ιχνηλατήσεις Στοίβας +monitor.processes_count=%d Διεργασίες +monitor.download_diagnosis_report=Λήψη αναφοράς διάγνωσης monitor.desc=Περιγραφή monitor.start=Ώρα Έναρξης monitor.execute_time=Χρόνος Εκτέλεσης @@ -3021,11 +3096,14 @@ monitor.queue.numberinqueue=Πλήθος Ουράς monitor.queue.review=Εξέταση Ρυθμίσεων monitor.queue.review_add=Εξέταση/Προσθήκη Εργατών monitor.queue.settings.title=Ρυθμίσεις Δεξαμενής +monitor.queue.settings.desc=Οι δεξαμενές αυξάνονται δυναμικά όταν υπάρχει φραγή της ουράς των εργατών τους. 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=Προβολή Λεπτομερειών Ειδοποίησης @@ -3135,7 +3213,7 @@ error.unit_not_allowed=Δεν σας επιτρέπεται να έχετε πρ title=Πακέτα desc=Διαχείριση πακέτων μητρώου. empty=Δεν υπάρχουν πακέτα ακόμα. -empty.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο πακέτων, ανατρέξτε στην τεκμηρίωση. +empty.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο πακέτων, ανατρέξτε στην τεκμηρίωση. empty.repo=Μήπως ανεβάσατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε στις ρυθμίσεις πακέτων και συνδέστε το σε αυτό το αποθετήριο. filter.type=Τύπος filter.type.all=Όλα @@ -3160,9 +3238,15 @@ versions=Εκδόσεις versions.view_all=Προβολή όλων dependency.id=ID dependency.version=Έκδοση +alpine.registry=Ρυθμίστε αυτό το μητρώο προσθέτοντας το url στο αρχείο /etc/apk/repositories: +alpine.registry.key=Αποθηκεύστε το δημόσιο κλειδί RSA του μητρώου στο φάκελο /etc/apk/keys/ για να επαληθεύσετε την υπογραφή ευρετηρίου: +alpine.registry.info=Επιλέξτε $branch και $repository από την παρακάτω λίστα. alpine.install=Για να εγκαταστήσετε το πακέτο, εκτελέστε την ακόλουθη εντολή: +alpine.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο Alpine, ανατρέξτε στην τεκμηρίωση. +alpine.repository=Πληροφορίες Αποθετηρίου alpine.repository.branches=Κλάδοι alpine.repository.repositories=Αποθετήρια +alpine.repository.architectures=Αρχιτεκτονικές cargo.registry=Ρυθμίστε αυτό το μητρώο στις ρυθμίσεις του Cargo (για παράδειγμα ~/.cargo/config.toml): cargo.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το Cargo, εκτελέστε την ακόλουθη εντολή: cargo.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο Cargo, ανατρέξτε στην τεκμηρίωση. @@ -3195,11 +3279,21 @@ container.layers=Στρώματα Εικόνας container.labels=Ετικέτες container.labels.key=Κλειδί container.labels.value=Τιμή +cran.registry=Ρυθμίστε αυτό το μητρώο στο αρχείο Rprofile.site: cran.install=Για να εγκαταστήσετε το πακέτο, εκτελέστε την ακόλουθη εντολή: +cran.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο CRAN, ανατρέξτε στην τεκμηρίωση. debian.registry=Ρυθμίστε αυτό το μητρώο από τη γραμμή εντολών: +debian.registry.info=Επιλέξτε $distribution και $component από την παρακάτω λίστα. debian.install=Για να εγκαταστήσετε το πακέτο, εκτελέστε την ακόλουθη εντολή: +debian.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο Debian, ανατρέξτε στην τεκμηρίωση. +debian.repository=Πληροφορίες Αποθετηρίου +debian.repository.distributions=Διανομές +debian.repository.components=Συστατικά +debian.repository.architectures=Αρχιτεκτονικές generic.download=Λήψη πακέτου από τη γραμμή εντολών: generic.documentation=Για περισσότερες πληροφορίες σχετικά με το γενικό μητρώο, ανατρέξτε στην τεκμηρίωση. +go.install=Εγκαταστήστε το πακέτο από τη γραμμή εντολών: +go.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο Go, ανατρέξτε στην τεκμηρίωση. helm.registry=Ρυθμίστε αυτό το μητρώο από τη γραμμή εντολών: helm.install=Για να εγκαταστήσετε το πακέτο, εκτελέστε την ακόλουθη εντολή: helm.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο Helm, ανατρέξτε στην τεκμηρίωση. @@ -3228,6 +3322,7 @@ pypi.install=Για να εγκαταστήσετε το πακέτο χρησι pypi.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο PyPI, ανατρέξτε στην τεκμηρίωση. rpm.registry=Ρυθμίστε αυτό το μητρώο από τη γραμμή εντολών: rpm.install=Για να εγκαταστήσετε το πακέτο, εκτελέστε την ακόλουθη εντολή: +rpm.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο RPM, ανατρέξτε στην τεκμηρίωση. rubygems.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το gem, εκτελέστε την ακόλουθη εντολή: rubygems.install2=ή προσθέστε το στο Gemfile: rubygems.dependencies.runtime=Εξαρτήσεις Εκτέλεσης @@ -3300,6 +3395,7 @@ deletion=Αφαίρεση μυστικού deletion.description=Η αφαίρεση ενός μυστικού είναι μόνιμη και δεν μπορεί να αναιρεθεί. Συνέχεια; deletion.success=Το μυστικό έχει αφαιρεθεί. deletion.failed=Αποτυχία αφαίρεσης μυστικού. +management=Διαχείριση Μυστικών [actions] actions=Δράσεις diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4d9d9bc60e66f..58fd84308d3e5 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -129,6 +129,7 @@ concept_user_organization = Organization show_timestamps = Show timestamps show_log_seconds = Show seconds show_full_screen = Show full screen +download_logs = Download logs confirm_delete_selected = Confirm to delete all selected items? @@ -223,7 +224,7 @@ repo_path_helper = Remote Git repositories will be saved to this directory. lfs_path = Git LFS Root Path lfs_path_helper = Files tracked by Git LFS will be stored in this directory. Leave empty to disable. run_user = Run As Username -run_user_helper = Enter the operating system username that Gitea runs as. Note that this user must have access to the repository root path. +run_user_helper = The operating system username that Gitea runs as. Note that this user must have access to the repository root path. domain = Server Domain domain_helper = Domain or host address for the server. ssh_port = SSH Server Port @@ -1435,6 +1436,7 @@ issues.next = Next issues.open_title = Open issues.closed_title = Closed issues.draft_title = Draft +issues.num_comments_1 = %d comment issues.num_comments = %d comments issues.commented_at = `commented %s` issues.delete_comment_confirm = Are you sure you want to delete this comment? @@ -1765,6 +1767,8 @@ pulls.auto_merge_canceled_schedule_comment = `canceled auto merging this pull re pulls.delete.title = Delete this pull request? pulls.delete.text = Do you really want to delete this pull request? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived) +pulls.recently_pushed_new_branches = You pushed on branch %[1]s %[2]s + milestones.new = New Milestone milestones.closed = Closed %s milestones.update_ago = Updated %s @@ -2649,7 +2653,7 @@ dashboard.task.finished=Task: %[1]s started by %[2]s has finished dashboard.task.unknown=Unknown task: %[1]s dashboard.cron.started=Started Cron: %[1]s dashboard.cron.process=Cron: %[1]s -dashboard.cron.cancelled=Cron: %s cancelled: %[3]s +dashboard.cron.cancelled=Cron: %[1]s cancelled: %[3]s dashboard.cron.error=Error in Cron: %s: %[3]s dashboard.cron.finished=Cron: %[1]s has finished dashboard.delete_inactive_accounts = Delete all unactivated accounts @@ -2659,6 +2663,7 @@ dashboard.delete_repo_archives.started = Delete all repository archives task sta dashboard.delete_missing_repos = Delete all repositories missing their Git files dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started. dashboard.delete_generated_repository_avatars = Delete generated repository avatars +dashboard.sync_repo_branches = Sync missed branches from git data to databases dashboard.update_mirrors = Update Mirrors dashboard.repo_health_check = Health check all repositories dashboard.check_repo_stats = Check all repository statistics @@ -2712,6 +2717,7 @@ dashboard.gc_lfs = Garbage collect LFS meta objects dashboard.stop_zombie_tasks = Stop zombie tasks dashboard.stop_endless_tasks = Stop endless tasks dashboard.cancel_abandoned_jobs = Cancel abandoned jobs +dashboard.sync_branch.started = Branches Sync started users.user_manage_panel = User Account Management users.new_account = Create User Account @@ -2797,6 +2803,7 @@ repos.stars = Stars repos.forks = Forks repos.issues = Issues repos.size = Size +repos.lfs_size = LFS Size packages.package_manage_panel = Package Management packages.total_size = Total Size: %s @@ -3227,6 +3234,7 @@ desc = Manage repository packages. empty = There are no packages yet. empty.documentation = For more information on the package registry, see the documentation. empty.repo = Did you upload a package, but it's not shown here? Go to package settings and link it to this repo. +registry.documentation = For more information on the %s registry, see the documentation. filter.type = Type filter.type.all = All filter.no_result = Your filter produced no results. @@ -3254,38 +3262,31 @@ alpine.registry = Setup this registry by adding the url in your /etc/apk/r alpine.registry.key = Download the registry public RSA key into the /etc/apk/keys/ folder to verify the index signature: alpine.registry.info = Choose $branch and $repository from the list below. alpine.install = To install the package, run the following command: -alpine.documentation = For more information on the Alpine registry, see the documentation. alpine.repository = Repository Info alpine.repository.branches = Branches alpine.repository.repositories = Repositories alpine.repository.architectures = Architectures cargo.registry = Setup this registry in the Cargo configuration file (for example ~/.cargo/config.toml): cargo.install = To install the package using Cargo, run the following command: -cargo.documentation = For more information on the Cargo registry, see the documentation. cargo.details.repository_site = Repository Site cargo.details.documentation_site = Documentation Site chef.registry = Setup this registry in your ~/.chef/config.rb file: chef.install = To install the package, run the following command: -chef.documentation = For more information on the Chef registry, see the documentation. composer.registry = Setup this registry in your ~/.composer/config.json file: composer.install = To install the package using Composer, run the following command: -composer.documentation = For more information on the Composer registry, see the documentation. composer.dependencies = Dependencies composer.dependencies.development = Development Dependencies conan.details.repository = Repository conan.registry = Setup this registry from the command line: conan.install = To install the package using Conan, run the following command: -conan.documentation = For more information on the Conan registry, see the documentation. conda.registry = Setup this registry as a Conda repository in your .condarc file: conda.install = To install the package using Conda, run the following command: -conda.documentation = For more information on the Conda registry, see the documentation. conda.details.repository_site = Repository Site conda.details.documentation_site = Documentation Site container.details.type = Image Type container.details.platform = Platform container.pull = Pull the image from the command line: container.digest = Digest: -container.documentation = For more information on the Container registry, see the documentation. container.multi_arch = OS / Arch container.layers = Image Layers container.labels = Labels @@ -3293,61 +3294,47 @@ container.labels.key = Key container.labels.value = Value cran.registry = Setup this registry in your Rprofile.site file: cran.install = To install the package, run the following command: -cran.documentation = For more information on the CRAN registry, see the documentation. debian.registry = Setup this registry from the command line: debian.registry.info = Choose $distribution and $component from the list below. debian.install = To install the package, run the following command: -debian.documentation = For more information on the Debian registry, see the documentation. debian.repository = Repository Info debian.repository.distributions = Distributions debian.repository.components = Components debian.repository.architectures = Architectures generic.download = Download package from the command line: -generic.documentation = For more information on the generic registry, see the documentation. go.install = Install the package from the command line: -go.documentation = For more information on the Go registry, see the documentation. helm.registry = Setup this registry from the command line: helm.install = To install the package, run the following command: -helm.documentation = For more information on the Helm registry, see the documentation. maven.registry = Setup this registry in your project pom.xml file: maven.install = To use the package include the following in the dependencies block in the pom.xml file: maven.install2 = Run via command line: maven.download = To download the dependency, run via command line: -maven.documentation = For more information on the Maven registry, see the documentation. nuget.registry = Setup this registry from the command line: nuget.install = To install the package using NuGet, run the following command: -nuget.documentation = For more information on the NuGet registry, see the documentation. nuget.dependency.framework = Target Framework npm.registry = Setup this registry in your project .npmrc file: npm.install = To install the package using npm, run the following command: npm.install2 = or add it to the package.json file: -npm.documentation = For more information on the npm registry, see the documentation. npm.dependencies = Dependencies npm.dependencies.development = Development Dependencies npm.dependencies.peer = Peer Dependencies npm.dependencies.optional = Optional Dependencies npm.details.tag = Tag pub.install = To install the package using Dart, run the following command: -pub.documentation = For more information on the Pub registry, see the documentation. pypi.requires = Requires Python pypi.install = To install the package using pip, run the following command: -pypi.documentation = For more information on the PyPI registry, see the documentation. rpm.registry = Setup this registry from the command line: rpm.install = To install the package, run the following command: -rpm.documentation = For more information on the RPM registry, see the documentation. rubygems.install = To install the package using gem, run the following command: rubygems.install2 = or add it to the Gemfile: rubygems.dependencies.runtime = Runtime Dependencies rubygems.dependencies.development = Development Dependencies rubygems.required.ruby = Requires Ruby version rubygems.required.rubygems = Requires RubyGem version -rubygems.documentation = For more information on the RubyGems registry, see the documentation. swift.registry = Setup this registry from the command line: swift.install = Add the package in your Package.swift file: swift.install2 = and run the following command: -swift.documentation = For more information on the Swift registry, see the documentation. vagrant.install = To add a Vagrant box, run the following command: -vagrant.documentation = For more information on the Vagrant registry, see the documentation. settings.link = Link this package to a repository settings.link.description = If you link a package with a repository, the package is listed in the repository's package list. settings.link.select = Select Repository @@ -3459,7 +3446,7 @@ runners.reset_registration_token_success = Runner registration token reset succe runs.all_workflows = All Workflows runs.commit = Commit -runs.pushed_by = Pushed by +runs.pushed_by = pushed by runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s runs.no_matching_runner_helper = No matching runner: %s runs.actor = Actor diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index a2995f5100d4f..7d9f49c1a883d 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -357,7 +357,7 @@ reset_password.text=لطفاً روی پیوند زیر کلیک کنید تا register_success=ثبت‌نام با موفقیت انجام شد -issue_assigned.pull=@%[1] به شما برای درخواست pull %[2] در ریپازیتوری %[3] محول شده. +issue_assigned.pull=@%[1]s به شما برای درخواست pull %[2]s در ریپازیتوری %[3]s محول شده. issue_assigned.issue=@%[1]s به شما واگذار شده است برای صدور %[2]s در انبار %[3]s. issue.x_mentioned_you=@%s به شما اشاره کرد: diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 2e53255aab13f..58d24fc69ca05 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -3133,7 +3133,7 @@ error.unit_not_allowed=Vous n'êtes pas autorisé à accéder à cette section d title=Paquets desc=Gérer les paquets du dépôt. empty=Il n'y pas de paquet pour le moment. -empty.documentation=Pour plus d'informations sur le registre de paquets, voir la documentation. +empty.documentation=Pour plus d'informations sur le registre de paquets, voir la documentation. empty.repo=Avez-vous téléchargé un paquet, mais il n'est pas affiché ici? Allez dans les paramètres du paquet et liez le à ce dépôt. filter.type=Type filter.type.all=Tous diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index 42ed8c5f249e2..977f22bf292f2 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -803,7 +803,7 @@ issues.action_milestone=Tímamót issues.action_milestone_no_select=Ekkert tímamót issues.action_assignee=Úthlutað að issues.opened_by=opnað %[1]s af %[3]s -issues.opened_by_fake=opnað %[1] af %[2]s +issues.opened_by_fake=opnað %[1]s af %[2]s issues.previous=Fyrri issues.next=Áfram issues.open_title=Opið diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 1818343f92fa6..224ce42d7a7bd 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -3122,7 +3122,7 @@ notices.delete_success=システム通知を削除しました。 [action] create_repo=がリポジトリ %s を作成しました -rename_repo=がリポジトリ名を %[1]s から [3]s へ変更しました +rename_repo=がリポジトリ名を %[1]s から %[3]s へ変更しました commit_repo=が %[4]s%[3]s にプッシュしました create_issue=`がイシュー %[3]s#%[2]s をオープンしました` close_issue=`がイシュー %[3]s#%[2]s をクローズしました` @@ -3213,7 +3213,7 @@ error.unit_not_allowed=このセクションへのアクセスが許可されて title=パッケージ desc=リポジトリ パッケージを管理します。 empty=パッケージはまだありません。 -empty.documentation=パッケージレジストリの詳細については、 ドキュメント を参照してください。 +empty.documentation=パッケージレジストリの詳細については、 ドキュメント を参照してください。 empty.repo=パッケージはアップロードしたけども、ここに表示されない? パッケージ設定を開いて、パッケージをこのリポジトリにリンクしてください。 filter.type=タイプ filter.type.all=すべて diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 2d5b40bd0b68f..1fb359b5226d3 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -19,6 +19,7 @@ active_stopwatch=Aktīvā laika uzskaite create_new=Izveidot… user_profile_and_more=Profils un iestatījumi… signed_in_as=Pierakstījies kā +enable_javascript=Šai lapas darbībai ir nepieciešams JavaScript. toc=Satura rādītājs licenses=Licences return_to_gitea=Atgriezties Gitea @@ -78,11 +79,14 @@ milestones=Atskaites punkti ok=Labi cancel=Atcelt +rerun=Palaist atkārtoti +rerun_all=Palaist atkārtoti visus darbus save=Saglabāt add=Pievienot add_all=Pievienot visus remove=Noņemt remove_all=Noņemt visus +remove_label_str=`Noņemt ierakstu "%s"` edit=Labot enabled=Iespējots @@ -111,11 +115,19 @@ unknown=Nezināms rss_feed=RSS barotne +pin=Piespraust +unpin=Atspraust +artifacts=Artefakti +concept_system_global=Globāls +concept_user_individual=Individuāls concept_code_repository=Repozitorijs concept_user_organization=Organizācija +show_timestamps=Rādīt laika zīmogus +show_log_seconds=Rādīt sekundes +show_full_screen=Atvērt pilnā logā [aria] navbar=Navigācijas josla @@ -124,8 +136,26 @@ footer.software=Par programmatūru footer.links=Saites [heatmap] +number_of_contributions_in_the_last_12_months=%s darbības pēdējo 12 mēnešu laikā +no_contributions=Nav aktivitātes +less=Mazāk +more=Vairāk [editor] +buttons.heading.tooltip=Pievienot virsrakstu +buttons.bold.tooltip=Izcelt tekstu +buttons.italic.tooltip=Slīpraksta teksts +buttons.quote.tooltip=Citēt tekstu +buttons.code.tooltip=Pievienot kodu +buttons.link.tooltip=Pievienot saiti +buttons.list.unordered.tooltip=Pievienot sarakstu +buttons.list.ordered.tooltip=Pievienot numurētu sarakstu +buttons.list.task.tooltip=Pievienot uzdevumu sarakstu +buttons.mention.tooltip=Pieminēt lietotāju vai komandu +buttons.ref.tooltip=Atsaukties uz problēmu vai izmaiņu pieprasījumu +buttons.switch_to_legacy.tooltip=Izmantot vēsturisko redaktoru +buttons.enable_monospace_font=Izmantot vienāda izmēra fontu +buttons.disable_monospace_font=Neizmantot vienāda izmēra fontu [filter] string.asc=A - Z @@ -228,6 +258,7 @@ openid_signup_popup=Iespējot lietotāju reģistrāciju pirms tam autorizējotie enable_captcha=Pieprasīt drošības kodu lietotāju reģistrācijā enable_captcha_popup=Lietotājam reģistrējoties, pieprasīt ievadīt drošības kodu. require_sign_in_view=Iespējot nepieciešamību autorizēties, lai aplūkotu lapas +require_sign_in_view_popup=Tikai autorizēti lietotāji var aplūkot lapas. Apmeklētāji redzēs tikai autorizācijas un reģistrācijas lapu. admin_setting_desc=Nav nepieciešams izveidot administratora kontu uzreiz, pirmais reģistrētais lietotājs saņems administratora tiesības automātiski. admin_title=Administratora konta iestatījumi admin_name=Administratora lietotāja vārds @@ -238,6 +269,7 @@ install_btn_confirm=Instalēt Gitea test_git_failed=Kļūda pārbaudot 'git' komandu: %v sqlite3_not_available=Jūsu pašreizējā versija neatbalsta SQLite3, lūdzu lejupielādējiet oficiālo bināro versiju no %s, NEVIS gobuild versiju. invalid_db_setting=Nederīgi datu bāzes iestatījumi: %v +invalid_db_table=Datubāzes tabula "%s" ir kļūdaina: %v invalid_repo_path=Nederīga repozitorija glabāšanas vieta: %v invalid_app_data_path=Lietojumprogrammas datu ceļš ir kļūdains: %v run_user_not_match=Izpildes lietotājs nav pašreizējais lietotājs: %s -> %s @@ -256,6 +288,7 @@ no_reply_address=Neatbildēt e-pasta adreses domēns no_reply_address_helper=Domēns lietotāja e-pasta adresei git žurnālos, ja lietotājs izvēlas paturēt savu e-pasta adresi privātu. Piemēram, ja lietotājs ir 'janis' un domēns 'neatbildet.piemers.lv', tad e-pasta adrese būs 'janis@neatbildet.piemers.lv'. password_algorithm=Paroles jaucējsummas algoritms invalid_password_algorithm=Kļūdaina paroles jaucējfunkcija +password_algorithm_helper=Norādiet paroles jaucējalgoritmu. Algoritmi atšķirās pēc prasībām pret resursiem un stipruma. Argon2 algoritms ir drošs, bet tam nepieciešams daudz operatīvās atmiņas, līdz ar ko tas var nebūt piemērots sistēmām ar maz pieejamajiem resursiem. enable_update_checker=Iespējot jaunu versiju paziņojumus enable_update_checker_helper=Periodiski pārbaudīt jaunu version pieejamību, izgūstot datus no gitea.io. @@ -291,6 +324,7 @@ repos=Repozitoriji users=Lietotāji organizations=Organizācijas search=Meklēt +go_to=Iet uz code=Kods search.type.tooltip=Meklēšanas veids search.fuzzy=Aptuveni @@ -302,6 +336,7 @@ repo_no_results=Netika atrasts neviens repozitorijs, kas atbilstu kritērijiem. user_no_results=Netika atrasts neviens lietotājs, kas atbilstu kritērijiem. org_no_results=Netika atrasta neviena organizācija, kas atbilstu kritērijiem. code_no_results=Netika atrasts pirmkods, kas atbilstu kritērijiem. +code_search_results=`Meklēšanas rezultāti "%s"` code_last_indexed_at=Pēdējo reizi indeksēts %s relevant_repositories_tooltip=Repozitoriju, kas ir atdalīti vai kuriem nav tēmas, ikonas un apraksta ir paslēpti. relevant_repositories=Tikai būtiskie repozitoriji tiek rādīti, pārādīt nefiltrētus rezultātus. @@ -438,13 +473,14 @@ repo.collaborator.added.subject=%s pievienoja Jūs repozitorijam %s repo.collaborator.added.text=Jūs tikāt pievienots kā līdzstrādnieks repozitorijam: team_invite.subject=%[1]s uzaicināja Jūs pievienoties organizācijai %[2]s -team_invite.text_1=%[1]s uzaicināja Jūs pievienoties komandai %[2] organizācijā %[3]s. +team_invite.text_1=%[1]s uzaicināja Jūs pievienoties komandai %[2]s organizācijā %[3]s. team_invite.text_2=Uzspiediet uz šīs saites, lai pievienoties komandai: team_invite.text_3=Piezīme: Šis uzaicinājums ir paredzēts %[1]s. Ja uzskatāt, ka tas nav domāts Jums, varat ignorēt šo e-pastu. [modal] yes=Jā no=Nē +confirm=Apstiprināt cancel=Atcelt modify=Atjaunināt @@ -479,6 +515,8 @@ size_error=` jābūt %s simbolus garam.` min_size_error=` jabūt vismaz %s simbolu garumā.` max_size_error=` jabūt ne mazāk kā %s simbolu garumā.` email_error=` nav derīga e-pasta adrese.` +url_error=`"%s" nav korekts URL.` +include_error=` ir jāsatur tekstu "%s".` glob_pattern_error=` glob šablons nav korekts: %s.` regex_pattern_error=` regulārā izteiksme nav korekta: %s.` username_error=` drīkst saturēt tikai burtus un ciparus ('0-9','a-z','A-Z'), domuzīme ('-'), apakšsvītra ('_') un punkts ('.'). Nevar sākties vai beigties ar simbolu, kas nav burts vai skaitlis, kā arī nevar būt vairāki simboli pēc kārtas, kas nav burti vai skaitļi.` @@ -490,6 +528,7 @@ lang_select_error=Izvēlieties valodu no saraksta. username_been_taken=Lietotājvārds jau ir aizņemts. username_change_not_local_user=Ne-lokālie lietotāji nevar mainīt savus lietotājvārdus. +username_has_not_been_changed=Lietotāja vārds netika mainīts repo_name_been_taken=Jau eksistē repozitorijs ar šādu nosaukumu. repository_force_private=Ir ieslēgts piespiedu privātais režīms: repozitorijus nav iespējams padarīt publiskus. repository_files_already_exist=Šī repozitorija faili jau eksistē, sazinieties ar sistēmas administratoru. @@ -503,6 +542,7 @@ team_name_been_taken=Komandas nosaukums jau ir aizņemts. team_no_units_error=Komandai ir jābūt iespējotai vismaz vienai sadaļai. email_been_used=E-pasta adrese jau ir izmantota. email_invalid=Epasta adrese nav korekta. +openid_been_used=OpenID adrese "%s" jau ir izmantota. username_password_incorrect=Nepareizs lietotājvārds vai parole. password_complexity=Parole neatbilst drošības prasībām: password_lowercase_one=Vismaz viens mazais burts @@ -524,13 +564,20 @@ invalid_ssh_key=Nav iespējams pārbaudīt SSH atslēgu: %s invalid_gpg_key=Nav iespējams pārbaudīt GPG atslēgu: %s invalid_ssh_principal=Kļūdaina identitāte: %s must_use_public_key=Atslēga, ko norādījāt ir privātā atslēga. Nekad nenodotiet savu privātu atslēgu nevienam. Izmantojiet publisko atslēgu. +unable_verify_ssh_key=SSH atslēgu nav iespējams pārbaudīt, pārliecinieties, ka tajā nav kļūdu. auth_failed=Autentifikācija neizdevās: %v +still_own_repo=Šis konts ir vismaz viena repozitorija īpašnieks, tos sākumā ir nepieciešams izdzēst vai mainīt to īpašnieku. +still_has_org=Jūsu konts ir piesaistīts vismaz vienai organizācijai, sākumā nepieciešams to pamest. +still_own_packages=Jūsu kontam pieder viena vai vairākas pakotnes, tās nepieciešams izdzēst. +org_still_own_repo=Organizācijai pieder repozitoriji, tos sākumā ir nepieciešams izdzēst vai mainīt to īpašnieku. +org_still_own_packages=Šai organizācijai pieder viena vai vārākas pakotnes, tās nepieciešams izdzēst. target_branch_not_exist=Mērķa atzars neeksistē [user] change_avatar=Mainīt profila attēlu… +joined_on=Pievienojās %s repositories=Repozitoriji activity=Publiskā aktivitāte followers=Sekotāji @@ -545,7 +592,12 @@ unfollow=Nesekot heatmap.loading=Ielādē intensitātes karti… user_bio=Biogrāfija disabled_public_activity=Šis lietotājs ir atslēdzies iespēju aplūkot tā aktivitāti. +email_visibility.limited=E-pasta adrese ir redzama visiem autentificētajiem lietotājiem +email_visibility.private=E-pasta adrese ir redzama tikai administratoriem +form.name_reserved=Lietotājvārdu "%s" nedrīkst izmantot. +form.name_pattern_not_allowed=Lietotājvārds "%s" nav atļauts. +form.name_chars_not_allowed=Lietotāja vārds "%s" satur neatļautus simbolus. [settings] profile=Profils @@ -576,6 +628,7 @@ location=Atrašanās vieta update_theme=Mainīt motīvu update_profile=Mainīt profilu update_language=Mainīt valodu +update_language_not_found=Valoda "%s" nav pieejama. update_language_success=Valoda tika nomainīta. update_profile_success=Jūsu profila informācija tika saglabāta. change_username=Lietotājvārds mainīts. @@ -586,6 +639,9 @@ cancel=Atcelt language=Valoda ui=Motīvs hidden_comment_types=Attēlojot paslēpt šauds komentārus: +hidden_comment_types_description=Komentāru veidi, kas atzīmēti, netiks rādīti problēmas lapā. Piemēram, atzīmējot "Etiķetes" netiks rādīti komentāri " pievienoja/noņēma ". +hidden_comment_types.ref_tooltip=Komentāri, kad problēmai tiek pievienota atsauce uz citu probēmu, komentāru, … +hidden_comment_types.issue_ref_tooltip=Komentāri par lietotāja izmaiņām ar problēmas saistīto atzaru/tagu comment_type_group_reference=Atsauces comment_type_group_label=Etiķetes comment_type_group_milestone=Atskaites punktus @@ -649,10 +705,12 @@ add_new_email=Pievienot jaunu e-pasta adresi add_new_openid=Pievienot jaunu OpenID vietrādi add_email=Pievienot e-pasta adresi add_openid=Pievienot OpenID vietrādi +add_email_confirmation_sent=Jauns apstiprināšanas e-pasts tika nosūtīts uz "%s". Pārbaudiet savu e-pasta kontu tuvāko %s laikā, lai apstiprinātu savu e-pasta adresi. add_email_success=Jūsu jaunā e-pasta adrese tika veiksmīgi pievienota. email_preference_set_success=E-pasta izvēle tika veiksmīgi saglabāta. add_openid_success=Jūsu jaunā OpenID adrese tika veiksmīgi pievienota. keep_email_private=Paslēpt e-pasta adresi +keep_email_private_popup=E-pasta adrese būs redzama tikai administratoriem openid_desc=Jūsu OpenID adreses ļauj autorizēties, izmantojot, Jūsu izvēlēto pakalpojumu sniedzēju. manage_ssh_keys=Pārvaldīt SSH atslēgas @@ -686,6 +744,7 @@ gpg_token_help=Parakstu ir iespējams uzģenerēt izmantojot komandu: gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig gpg_token_signature=Tekstuāls GPG paraksts key_signature_gpg_placeholder=Sākas ar '-----BEGIN PGP SIGNATURE-----' +verify_gpg_key_success=GPG atslēga "%s" veiksmīgi pārbaudīta. ssh_key_verified=Pārbaudīta atslēga ssh_key_verified_long=Atslēga tika pārbaudīta ar parakstītu talonu un var tikt izmantota, lai pārbaudītu revīzijas, kas atbilst jebkurai apstiprinātai lietotāja e-pasta adresei. ssh_key_verify=Pārbaudīt @@ -695,11 +754,15 @@ ssh_token=Talons ssh_token_help=Parakstu ir iespējams uzģenerēt izmantojot komandu: ssh_token_signature=Aizsargāts SSH paraksts key_signature_ssh_placeholder=Sākas ar '-----BEGIN SSH SIGNATURE-----' +verify_ssh_key_success=SSH atslēga "%s" veiksmīgi pārbaudīta. subkeys=Apakšatslēgas key_id=Atslēgas ID key_name=Atslēgas nosaukums key_content=Saturs principal_content=Saturs +add_key_success=SSH atslēga "%s" tika pievienota. +add_gpg_key_success=GPG atslēga "%s" tika pievienota. +add_principal_success=SSH sertifikāte identitāte "%s" tika pievienota. delete_key=Noņemt ssh_key_deletion=Noņemt SSH atslēgu gpg_key_deletion=Noņemt GPG atslēgu @@ -710,6 +773,8 @@ ssh_principal_deletion_desc=Noņemot SSH sertifikāta identitāti, ar to vairs n ssh_key_deletion_success=SSH atslēga tika izdzēsta. gpg_key_deletion_success=GPG atslēga tika izdzēsta. ssh_principal_deletion_success=Identitāte tika noņemta. +added_on=Pievienots %s +valid_until_date=Derīgs līdz %s valid_forever=Derīgs mūžīgi last_used=Pēdējo reizi izmantota no_activity=Nav nesenas aktivitātes @@ -721,6 +786,7 @@ principal_state_desc=Šī identitāte ir lietota pēdējās 7 dienās show_openid=Rādīt profilā hide_openid=Paslēpt no profila ssh_disabled=SSH atspējots +ssh_signonly=SSH ir atspējots, līdz ar to šīs atslēgas tiks izmantotas tikai revīziju parakstu pārbaudei. ssh_externally_managed=Šim lietotājam SSH atslēga tiek pāvaldīta attālināti manage_social=Pārvaldīt piesaistītos sociālos kontus social_desc=Šis ir saraksts ar Jūsu Gitea kontam piesaistītajiem sociālajiem kontiem. Drošības nolūkos, pārliecinieties, ka atpazīstat visus no tiem, jo tos var izmantot, lai pieslēgtos Jūsu Gitea kontam. @@ -740,6 +806,13 @@ access_token_deletion_cancel_action=Atcelt access_token_deletion_confirm_action=Dzēst access_token_deletion_desc=Izdzēšot talonu, tam tiks liegta piekļuve šim kontam. Šī darbība ir neatgriezeniska. Vai turpināt? delete_token_success=Piekļuves talons tika noņemts. Neaizmirstiet atjaunot informāciju lietojumprogrammās, kas izmantoja šo talonu. +repo_and_org_access=Repozitorija un organizācijas piekļuve +permissions_public_only=Tikai publiskie +permissions_access_all=Visi (publiskie, privātie un ierobežotie) +select_permissions=Norādiet tiesības +scoped_token_desc=Atzīmētie talona apgabali ierobežo autorizāciju tikai atbilstošiem API izsaukumiem. Sīkāka informācija pieejama dokumentācijā. +at_least_one_permission=Nepieciešams norādīt vismaz vienu tiesību, lai izveidotu talonu +permissions_list=Tiesības: manage_oauth2_applications=Pārvaldīt OAuth2 lietotnes edit_oauth2_application=Labot OAuth2 lietotni @@ -753,6 +826,7 @@ create_oauth2_application_success=OAuth2 lietotne veiksmīgi izveidota. update_oauth2_application_success=OAuth2 lietotne veiksmīgi atjaunināta. oauth2_application_name=Lietotnes nosaukums oauth2_confidential_client=Konfidenciāls klients. Norādiet lietotēm, kas glabā noslēpumu slepenībā, piemēram, tīmekļa lietotnēm. Nenorādiet instalējamām lietotnēm, tai skaitā darbavirsmas vai mobilajām lietotnēm. +oauth2_redirect_uris=Pārsūtīšanas URI. Norādiet katru URI savā rindā. save_application=Saglabāt oauth2_client_id=Klienta ID oauth2_client_secret=Klienta noslēpums @@ -822,7 +896,9 @@ email_notifications.andyourown=Iekļaut savus paziņojumus visibility=Lietotāja redzamība visibility.public=Publisks +visibility.public_tooltip=Redzams ikvienam visibility.limited=Ierobežota +visibility.limited_tooltip=Redzams tikai autorizētiem lietotājiem visibility.private=Privāts visibility.private_tooltip=Redzams tikai organizāciju dalībniekiem @@ -896,6 +972,7 @@ mirror_password_blank_placeholder=(nav uzstādīts) mirror_password_help=Nomainiet lietotāju, lai izdzēstu saglabāto paroli. watchers=Novērotāji stargazers=Zvaigžņdevēji +stars_remove_warning=Tiks noņemtas visas atzīmētās zvaigznes šim repozitorijam. forks=Atdalītie repozitoriji reactions_more=un vēl %d unit_disabled=Administrators ir atspējojies šo repozitorija sadaļu. @@ -910,6 +987,7 @@ delete_preexisting=Dzēst jau eksistējošos failus delete_preexisting_content=Dzēst failus direktorijā %s delete_preexisting_success=Dzēst nepārņemtos failus direktorijā %s blame_prior=Aplūkot vainīgo par izmaiņām pirms šīs revīzijas +author_search_tooltip=Tiks attēloti ne vairāk kā 30 lietotāji transfer.accept=Apstiprināt īpašnieka maiņu transfer.accept_desc=`Mainīt īpašnieku uz "%s"` @@ -938,11 +1016,14 @@ template.one_item=Norādiet vismaz vienu sagataves vienību template.invalid=Norādiet sagataves repozitoriju archive.title=Repozitorijs ir arhivēts. Tam var aplūkot failus un to var klonēt, bet nevar iesūtīt jaunas izmaiņas, kā arī atvērt jaunas problēmas/izmaiņu pieprasījumus. +archive.title_date=Šis repozitorijs tika arhivēts %s. Ir iespējams aplūkot tā failus un to konēt, bet nav iespējams iesūtīt izmaiņas, kā arī atvērt jaunas problēmas vai izmaiņu pieprasījumus. archive.issue.nocomment=Repozitorijs ir arhivēts. Problēmām nevar pievienot jaunus komentārus. archive.pull.nocomment=Repozitorijs ir arhivēts. Izmaiņu pieprasījumiem nevar pievienot jaunus komentārus. form.reach_limit_of_creation_1=Sasniegts Jums noteiktais %d repozitorija ierobežojums. form.reach_limit_of_creation_n=Sasniegts Jums noteiktais %d repozitoriju ierobežojums. +form.name_reserved=Repozitorija nosaukums "%s" ir jau rezervēts. +form.name_pattern_not_allowed=Repozitorija nosaukums "%s" nav atļauts. need_auth=Autorizācija migrate_options=Migrācijas opcijas @@ -968,6 +1049,7 @@ migrate.github_token_desc=Ir iespējams izmantot vienu vai ar komantiem atdalīt migrate.clone_local_path=vai servera lokālais ceļš migrate.permission_denied=Jums nav tiesību importēt lokālu repozitoriju. migrate.permission_denied_blocked=Nav iespējams importēt no neatļautām adresēm, prasiet administratoram pārskatīt ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS iestatījumus. +migrate.invalid_local_path=Nederīgs lokālais ceļš. Tas neeksistē vai nav direktorija. migrate.invalid_lfs_endpoint=LFS galapunkts nav korekts. migrate.failed=Migrācija neizdevās: %v migrate.migrate_items_options=Piekļuves talons ir nepieciešams, lai migrētu papildus datus @@ -976,6 +1058,7 @@ migrated_from_fake=Migrēts no %[1]s migrate.migrate=Migrēt no %s migrate.migrating=Migrācija no %s ... migrate.migrating_failed=Migrācija no %s neizdevās. +migrate.migrating_failed.error=Migrācija neizdevās: %s migrate.migrating_failed_no_addr=Migrācija neizdevās. migrate.github.description=Migrēt datus no github.com vai citām GitHub instancēm. migrate.git.description=Migrēt repozitorija datus no jebkura Git servisa. @@ -992,6 +1075,8 @@ migrate.migrating_labels=Migrē etiķetes migrate.migrating_releases=Migrē laidienus migrate.migrating_issues=Migrācijas problēmas migrate.migrating_pulls=Migrē izmaiņu pieprasījumus +migrate.cancel_migrating_title=Atcelt migrāciju +migrate.cancel_migrating_confirm=Vai patiešam vēlaties atcelt šo migrāciju? mirror_from=spogulis no forked_from=atdalīts no @@ -1042,6 +1127,7 @@ release=Laidiens releases=Laidieni tag=Tags released_this=izveidoja šo laidienu +tagged_this=izveidoja tagu revīzijai file.title=%s atzarā %s file_raw=Neapstrādāts file_history=Vēsture @@ -1076,6 +1162,7 @@ download_file=Lejupielādēt failu normal_view=Parastais skats line=rinda lines=rindas +from_comment=(komentārs) editor.add_file=Pievienot editor.new_file=Jauna datne @@ -1090,6 +1177,7 @@ editor.must_be_on_a_branch=Ir jābūt izvēlētam atzaram, lai varētu veikt vai editor.fork_before_edit=Lai varētu labot failu, ir nepieciešams atdalīt repozitoriju. editor.delete_this_file=Dzēst failu editor.must_have_write_access=Jums ir jābūt rakstīšanas tiesībām, lai varētu veikt vai piedāvāt izmaiņas šim failam. +editor.file_delete_success=Fails "%s" tika izdzēsts. editor.name_your_file=Ievadiet faila nosaukumu… editor.filename_help=Lai pievienotu direktoriju, ierakstiet tās nosaukumu un slīpsvītru ('/'). Lai noņemtu direktoriju, ielieciet kursoru pirms faila nosaukuma un nospiediet atpakaļatkāpes taustiņu. editor.or=vai @@ -1097,8 +1185,12 @@ editor.cancel_lower=Atcelt editor.commit_signed_changes=Apstiprināt parakstītu revīziju editor.commit_changes=Pabeigt revīziju editor.add_tmpl=Pievienot '' +editor.add=Pievienot %s +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. @@ -1110,15 +1202,29 @@ editor.new_branch_name=Jaunā atzara nosaukums šai revīzijai editor.new_branch_name_desc=Jaunā atzara nosaukums… editor.cancel=Atcelt editor.filename_cannot_be_empty=Faila nosaukums nevar būt tukšs. +editor.filename_is_invalid=Faila nosaukums "%s" nav korekts. +editor.branch_does_not_exist=Šajā repozitorijā neeksistē atzars "%s". +editor.branch_already_exists=Atzars "%s" šajā repozitorijā jau eksistē. +editor.directory_is_a_file=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 šeit, lai apskatītu, vai Nosūtiet izmaiņas atkārtoti, 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: editor.add_subdir=Pievienot direktoriju… +editor.unable_to_upload_files=Neizdevās augšupielādēt failus uz direktoriju "%s", kļūda: %v +editor.upload_file_is_locked=Failu "%s" ir nobloķējis %s. +editor.upload_files_to_dir=`Augšupielādēt failus uz direktoriju "%s"` +editor.cannot_commit_to_protected_branch=Nav atļauts veikt izmaiņas aizsargātam atzaram "%s". editor.no_commit_to_branch=Nevar apstiprināt revīzijas atzarā: editor.user_no_push_to_branch=Lietotājs nevar iesūtīt izmaiņas šajā atzarā editor.require_signed_commit=Atzarā var iesūtīt tikai parakstītas revīzijas @@ -1127,8 +1233,10 @@ 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. commits.nothing_to_compare=Atzari ir vienādi. commits.search=Meklēt revīzijas… +commits.search.tooltip=Jūs varat izmantot atslēgas vārdus "author:", "committer:", "after:" vai "before:", piemēram, "revert author:Alice before:2019-01-13". commits.find=Meklēt commits.search_all=Visi atzari commits.author=Autors @@ -1161,12 +1269,14 @@ projects.create=Izveidot projektu projects.title=Nosaukums projects.new=Jauns projekts projects.new_subheader=Koordinē, seko un atjauno savu darbu centralizēti, lai projekts būtu izsekojams un vienmēr laikā. +projects.create_success=Projekts "%s" tika izveidots. projects.deletion=Dzēst projektu projects.deletion_desc=Dzēšot projektu no tā tiks atsaistītās visas tam piesaistītās problēmas. Vai turpināt? projects.deletion_success=Šis projekts tika izdzēsts. projects.edit=Labot projektu projects.edit_subheader=Projekti organizē problēmas un ļauj izsekot to progresam. projects.modify=Mainīt projektu +projects.edit_success=Projekta "%s" izmaiņas tika saglabātas. projects.type.none=Nav projects.type.basic_kanban=`Vienkāršots "Kanban"` projects.type.bug_triage=Kļūdu šķirošana @@ -1180,6 +1290,8 @@ projects.column.new_submit=Izveidot kolonnu projects.column.new=Jauna kolonna projects.column.set_default=Izvēlēties kā noklusēto projects.column.set_default_desc=Izvēlēties šo kolonnu kā noklusēto nekategorizētām problēmām un izmaiņu pieteikumiem +projects.column.unset_default=Atiestatīt noklusēto +projects.column.unset_default_desc=Noņemt šo kolonnu kā noklusēto projects.column.delete=Dzēst kolonnu projects.column.deletion_desc=Dzēšot projekta kolonnu visas tam piesaistītās problēmas tiks pārliktas kā nekategorizētas. Vai turpināt? projects.column.color=Krāsa @@ -1222,6 +1334,7 @@ issues.choose.blank=Noklusējuma issues.choose.blank_about=Izveidot problēmu ar noklusējuma sagatavi. issues.choose.ignore_invalid_templates=Kļūdainās sagataves tika izlaistas issues.choose.invalid_templates=%v ķļūdaina sagatave(s) atrastas +issues.choose.invalid_config=Problēmu konfigurācija satur kļūdas: issues.no_ref=Nav norādīts atzars/tags issues.create=Pieteikt problēmu issues.new_label=Jauna etiķete @@ -1232,6 +1345,7 @@ issues.label_templates.title=Ielādēt sākotnēji noteiktu etiķešu kopu issues.label_templates.info=Nav izveidota neviena etiķete. Jūs varat noklikšķināt uz "Jauna etiķete" augstāk, lai to izveidotu vai izmantot zemāk piedāvātās etiķetes: issues.label_templates.helper=Izvēlieties etiķešu kopu issues.label_templates.use=Izmantot etiķešu kopu +issues.label_templates.fail_to_load_file=Neizdevās ielādēt etiķetes sagataves failu "%s": %v issues.add_label=pievienoja %s etiķeti %s issues.add_labels=pievienoja %s etiķetes %s issues.remove_label=noņēma %s etiķeti %s @@ -1259,6 +1373,10 @@ issues.filter_label_exclude=`Izmantojiet alt + peles klikšķ issues.filter_label_no_select=Visas etiķetes issues.filter_label_select_no_label=Nav etiķešu issues.filter_milestone=Atskaites punkts +issues.filter_milestone_all=Visi atskaites punkti +issues.filter_milestone_none=Nav atskaites punkta +issues.filter_milestone_open=Atvērtie atskaites punkti +issues.filter_milestone_closed=Aizvērtie atskaites punkti issues.filter_project=Projektus issues.filter_project_all=Visi projekti issues.filter_project_none=Nav projektu @@ -1309,7 +1427,7 @@ issues.open_title=Atvērta issues.closed_title=Slēgta issues.draft_title=Melnraksts issues.num_comments=%d komentāri -issues.commented_at=` komentēja %s` +issues.commented_at=`komentēja %s` issues.delete_comment_confirm=Vai patiešām vēlaties dzēst šo komentāru? issues.context.copy_link=Kopēt saiti issues.context.quote_reply=Atbildēt citējot @@ -1317,6 +1435,9 @@ issues.context.reference_issue=Atsaukties uz šo jaunā problēmā issues.context.edit=Labot issues.context.delete=Dzēst issues.no_content=Vēl nav satura. +issues.close=Slēgt problēmu +issues.comment_pull_merged_at=saplidināta revīzija %[1]s atzarā %[2]s %[3]s +issues.comment_manually_pull_merged_at=manuāli saplidināta revīzija %[1]s atzarā %[2]s %[3]s issues.close_comment_issue=Komentēt un aizvērt issues.reopen_issue=Atvērt atkārtoti issues.reopen_comment_issue=Komentēt un atvērt atkārtoti @@ -1367,6 +1488,10 @@ issues.attachment.open_tab=`Noklikšķiniet, lai apskatītos "%s" jaunā logā` issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"` issues.subscribe=Abonēt issues.unsubscribe=Atrakstīties +issues.unpin_issue=Atspraust problēmu +issues.max_pinned=Nevar piespraust vairāk problēmas +issues.pin_comment=piesprauda šo %s +issues.unpin_comment=atsprauda šo %s issues.lock=Slēgt komentēšanu issues.unlock=Atļaut komentēšanu issues.lock.unknown_reason=Neizdevās slēgt problēmas komentēšanu. @@ -1432,6 +1557,9 @@ issues.due_date_invalid=Datums līdz nav korekts. Izmantojiet formātu 'gggg-mm- issues.dependency.title=Atkarības issues.dependency.issue_no_dependencies=Nav atkarību. issues.dependency.pr_no_dependencies=Nav atkarību. +issues.dependency.no_permission_1=Nav tiesību nolasīt %d atkarību +issues.dependency.no_permission_n=Nav tiesību nolasīt %d atkarības +issues.dependency.no_permission.can_remove=Nav tiesību nolasīt šo atkarību, bet iespējams to noņemt issues.dependency.add=Pievienot atkarību… issues.dependency.cancel=Atcelt issues.dependency.remove=Noņemt @@ -1443,6 +1571,7 @@ issues.dependency.issue_closing_blockedby=Šīs problēmas aizvēršanu bloķē issues.dependency.issue_close_blocks=Šī problēma bloķē sekojošu problēmu aizvēršanu issues.dependency.pr_close_blocks=Šis izmaiņu pieprasījums bloķē sekojošu problēmu aizvēršanu issues.dependency.issue_close_blocked=Nepieciešams aizvērt visas problēmas, kas bloķē šo problēmu, lai to varētu aizērt. +issues.dependency.issue_batch_close_blocked=Nav iespējams aizvērt vairākas atzīmētās problēmas, jo problēmai #%d ir atvērtas atkarības issues.dependency.pr_close_blocked=Nepieciešams aizvērt visas problēmas, kas bloķē šo izmaiņu pieprasījumu, lai to varētu sapludināt. issues.dependency.blocks_short=Bloķē issues.dependency.blocked_by_short=Atkarīgs no @@ -1470,6 +1599,7 @@ issues.review.add_review_request=pieprasīja recenziju no %s %s issues.review.remove_review_request=noņema recenzijas pieprasījumu no %s %s issues.review.remove_review_request_self=atteicās recenzēt %s issues.review.pending=Nav iesūtīts +issues.review.pending.tooltip=Šis komentārs nav redzams citiem lietotājiem. Lai padarītu neiesūtītos komentārus pieejamus citiem, nospiediet "%s" -> "%s/%s/%s" lapas augšpusē. issues.review.review=Recenzija issues.review.reviewers=Recenzenti issues.review.outdated=Novecojis @@ -1504,6 +1634,8 @@ pulls.compare_changes_desc=Izvēlieties atzaru, kurā sapludināt izmaiņas un a pulls.has_viewed_file=Skatīts pulls.has_changed_since_last_review=Mainīts kopš pēdējās recenzijas pulls.viewed_files_label=%[1]d no %[2]d failiem apskatīts +pulls.expand_files=Izvērst visus failus +pulls.collapse_files=Savērst visus failus pulls.compare_base=pamata pulls.compare_compare=salīdzināmais pulls.switch_comparison_type=Mainīt salīdzināšanas tipu @@ -1523,7 +1655,10 @@ pulls.tab_files=Izmainītie faili pulls.reopen_to_merge=Atkārtoti atveriet izmaiņu pieprasījumu, lai veiktu sapludināšanu. pulls.cant_reopen_deleted_branch=Šo izmaiņu pieprasīju nevar atkāroti atvērt, jo atzars ir izdzēsts. pulls.merged=Sapludināts +pulls.merged_success=Izmaiņu pieprasījums vieksmīgi sapludināts un aizvērts +pulls.closed=Izmaiņu pieprasījums aizvērts pulls.manually_merged=Manuāli sapludināts +pulls.merged_info_text=Atzaru %s tagad var dzēst. pulls.is_closed=Izmaiņu pieprasījums tika aizvērts. pulls.title_wip_desc=`Sāciet virsrakstu ar %s, lai ierobežotu, ka izmaiņu pieprasījums netīšām tiktu sapludināts.` pulls.cannot_merge_work_in_progress=Šis izmaiņu pieprasījums ir atzīmēts, ka pie tā vēl notiek izstrāde. @@ -1594,6 +1729,7 @@ pulls.update_branch_rebase=Atjaunot atzaru, izmantojot, pārbāzēšanu pulls.update_branch_success=Atzara atjaunināšana veiksmīgi pabeigta pulls.update_not_allowed=Jums nav tiesību veikt atzara atjaunošanu pulls.outdated_with_base_branch=Atzars ir novecojis salīdzinot ar bāzes atzaru +pulls.close=Aizvērt izmaiņu pieprasījumu pulls.closed_at=`aizvēra šo izmaiņu pieprasījumu %[2]s` pulls.reopened_at=`atkārtoti atvēra šo izmaiņu pieprasījumu %[2]s` pulls.merge_instruction_hint=`Varat aplūkot arī komandrindas instrukcijas.` @@ -1619,6 +1755,7 @@ pulls.delete.text=Vai patiešām vēlaties dzēst šo izmaiņu pieprasījumu? (N milestones.new=Jauns atskaites punkts milestones.closed=Aizvērts %s +milestones.update_ago=Atjaunots %s milestones.no_due_date=Bez termiņa milestones.open=Atvērta milestones.close=Aizvērt @@ -1630,10 +1767,12 @@ milestones.desc=Apraksts milestones.due_date=Termiņš (neobligāts) milestones.clear=Notīrīt milestones.invalid_due_date_format=Izpildes termiņam ir jābūt formāta 'yyyy-mm-dd'. +milestones.create_success=Atskaites punkts "%s" tika veiksmīgi izveidots. milestones.edit=Labot atskaites punktu milestones.edit_subheader=Atskaites punkti, ļauj organizēt problēmas un sekot to progresam. milestones.cancel=Atcelt milestones.modify=Labot atskaites punktu +milestones.edit_success=Izmaiņas atskaites punktā "%s" tika veiksmīgi saglabātas. milestones.deletion=Dzēst atskaites punktu milestones.deletion_desc=Dzēšot šo atskaites punktu, tas tiks noņemts no visām saistītajām problēmām un izmaiņu pieprasījumiem. Vai turpināt? milestones.deletion_success=Atskaites punkts tika veiksmīgi izdzēsts. @@ -1644,6 +1783,7 @@ milestones.filter_sort.most_complete=Visvairāk pabeigtais milestones.filter_sort.most_issues=Visvairāk problēmu milestones.filter_sort.least_issues=Vismazāk problēmu +signing.will_sign=`Šī revīzijas tiks parakstīta ar atslēgu "%s"` signing.wont_sign.error=Notika kļūda pārbaudot vai revīzija var tikt parakstīta signing.wont_sign.nokey=Nav pieejama atslēga ar ko parakstīt šo revīziju signing.wont_sign.never=Revīzijas nekad netiek parakstītas @@ -1668,6 +1808,8 @@ wiki.create_first_page=Izveidot pirmo lapu wiki.page=Lapa wiki.filter_page=Meklēt lapu wiki.new_page=Lapa +wiki.page_title=Lapas virsraksts +wiki.page_content=Lapas saturs wiki.default_commit_message=Ierakstiet piezīmes par šīs lapas izmaiņām (neobligāts). wiki.save_page=Saglabāt lapu wiki.last_commit_info=%s laboja lapu %s @@ -1677,10 +1819,13 @@ wiki.file_revision=Labas revīzija wiki.wiki_page_revisions=Vikivietnes lapas revīzijas wiki.back_to_wiki=Atpakaļ uz vikivietnes lapu wiki.delete_page_button=Dzēst lapu +wiki.delete_page_notice_1=Šī darbība izdzēsīs vikivietnes lapu "%s". Vai turpināt? wiki.page_already_exists=Vikivietnes lapa ar šādu nosaukumu jau eksistē. +wiki.reserved_page=Vikivietnes lapas nosaukums "%s" ir rezervēts. wiki.pages=Lapas wiki.last_updated=Pēdējo reizi labota %s wiki.page_name_desc=Ievadiet vikivietnes lapas nosaukumu. Speciālie nosaukumi ir: 'Home', '_Sidebar' un '_Footer'. +wiki.original_git_entry_tooltip=Attēlot oriģinālo Git faila nosaukumu. activity=Aktivitāte activity.period.filter_label=Laika periods: @@ -1772,6 +1917,16 @@ settings.hooks=Tīmekļa āķi settings.githooks=Git āķi settings.basic_settings=Pamatiestatījumi settings.mirror_settings=Spoguļa iestatījumi +settings.mirror_settings.docs=Iestatiet, ka tiks viekta automātiska revīziju, tagu un atzaru sinhronizācija ar citu repozitoriju. +settings.mirror_settings.docs.disabled_pull_mirror.instructions=Iestatiet, ka visas revīzijas, tagi un atzari tiks automātiski nosūtītu uz citu repozitoriju. Izgūšanas spoguļus administrators ir aizliedzis izmantot. +settings.mirror_settings.docs.disabled_push_mirror.instructions=Iestatiet, ka visas revīzijas, tagi un atzari tiks automātiski pārņemti no cita repozitorija. +settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Pašlaik to var izdarīt tikai, izmantojot, sadaļu "Jauna migrācija". Sīkākai informācijai, skatieties: +settings.mirror_settings.docs.disabled_push_mirror.info=Iesūtīšanas spoguļus administrators ir aizliedzis izmantot. +settings.mirror_settings.docs.no_new_mirrors=Šis repozitorijs spoguļo izmaiņas uz vai no cita repozitorija. Pašlaik vairāk nav iespējams izveidot jaunus spoguļa repozitorijus. +settings.mirror_settings.docs.can_still_use=Lai arī nav iespējams mainīt esošos vai izveidot jaunus spoguļa repozitorijus, esošie turpinās strādāt. +settings.mirror_settings.docs.pull_mirror_instructions=Lai ietatītu atvilkšanas spoguli, sekojiet instrukcijām: +settings.mirror_settings.docs.doc_link_title=Kā spoguļot repozitorijus? +settings.mirror_settings.docs.pulling_remote_title=Atvilkt no attāla repozitorija settings.mirror_settings.mirrored_repository=Spoguļotais repozitorijs settings.mirror_settings.direction=Virziens settings.mirror_settings.direction.pull=Izmaiņu saņemšana @@ -1784,6 +1939,8 @@ settings.sync_mirror=Sinhronizēt tagad settings.mirror_sync_in_progress=Notiek spoguļa sinhronizācija. Atjaunojiet lapu, lai pārbaudītu atkārtoti, pēc brīža. settings.site=Mājas lapa settings.update_settings=Mainīt iestatījumus +settings.update_mirror_settings=Atjaunot spoguļa iestatījumus +settings.branches.switch_default_branch=Mainīt noklusēto atzaru settings.branches.update_default_branch=Atjaunot noklusēto atzaru settings.branches.add_new_rule=Pievienot jaunu noteikumu settings.advanced_settings=Papildu iestatījumi @@ -1866,6 +2023,7 @@ settings.trust_model.collaborator.long=Līdzstrādnieka: Uzticēties līdzstrād settings.trust_model.collaborator.desc=Ticami līdzstrādnieku paraksti tiks atzīmēti kā "uzticami" (neatkarīgi no tā vai tie atbilst revīzijas iesūtītājam vai nē). Citos gadījumos ticami paraksti tiks atzīmēti kā "neuzticami", ja paraksts atbilst revīzijas iesūtītājam vai "nesakrītošs", ja neatbilst. settings.trust_model.committer=Revīzijas iesūtītāja settings.trust_model.committer.long=Revīzijas iesūtītāja: Uzticēties parakstiem, kas atbilst revīzijas iesūtītājiem (Šis atbilst GitHub uzvedībai un piespiedīs Gitea parakstītām revīzijām būt Gitea kā revīzijas iesūtītājam) +settings.trust_model.committer.desc=Ticami paraksti tiks atzīmēti kā "uzticami", ja tie atbilst revīzijas iesūtītājam, citos gadījumos tie tiks atzīmēti kā "nesakrītoši". Šis nozīmē, ka Gitea būs kā revīzijas iesūtītājs parakstītām revīzijām, kur īstais revīzijas iesūtītājs tiks atīzmēts revīzijas komentāra beigās ar tekstu Co-authored-by: un Co-committed-by:. Noklusētajai Gitea atslēgai ir jāatbilst lietotājam datu bāzē. settings.trust_model.collaboratorcommitter=Līdzstrādnieka un revīzijas iesūtītāja settings.trust_model.collaboratorcommitter.long=Līdzstrādnieka un revīzijas iesūtītāja: Uzticēties līdzstrādnieku parakstiem, kas atbilst revīzijas iesūtītājam settings.trust_model.collaboratorcommitter.desc=Ticami līdzstrādnieku paraksti tiks atzīmēti kā "uzticami", ja tie atbilst revīzijas iesūtītājam, citos gadījumos tie tiks atzīmēti kā "neuzticami", ja paraksts atbilst revīzijas iesūtītajam, vai "nesakrītoši", ja neatbilst. Šis nozīmē, ka Gitea būs kā revīzijas iesūtītājs parakstītām revīzijām, kur īstais revīzijas iesūtītājs tiks atīzmēts revīzijas komentāra beigās ar tekstu Co-Authored-By: un Co-Committed-By:. Noklusētajai Gitea atslēgai ir jāatbilst lietotājam datu bāzē. @@ -1881,6 +2039,7 @@ settings.delete_notices_2=- Šī darbība neatgriezeniski izdzēsīs visu repozi settings.delete_notices_fork_1=- Visi atdalītie repozitoriju pēc dzēšanas kļūs neatkarīgi. settings.deletion_success=Repozitorijs tika izdzēsts. settings.update_settings_success=Repozitorija iestatījumi tika saglabāti. +settings.update_settings_no_unit=Repozitorijam ir jābūt piešķirtām vismaz kādām tiesībām. settings.confirm_delete=Dzēst repozitoriju settings.add_collaborator=Pievienot līdzstrādnieku settings.add_collaborator_success=Jauns līdzstrādnieks tika pievienots. @@ -1978,6 +2137,10 @@ settings.event_pull_request_review=Izmaiņu pieprasījums recenzēts settings.event_pull_request_review_desc=Izmaiņu pieprasījums apstiprināts, noraidīts vai atstāts komentārs. settings.event_pull_request_sync=Izmaiņu pieprasījums sinhronizēts settings.event_pull_request_sync_desc=Izmaiņu pieprasījums sinhronizēts. +settings.event_pull_request_review_request=Izmaiņu pieprasījuma recenzīju pieprasīšana +settings.event_pull_request_review_request_desc=Izmaiņu pieprasījuma recenzījas pieprasījums vai recenzijas pieprasījuma atcelšana. +settings.event_pull_request_approvals=Izmaiņu pieprasījuma apstiprināšana +settings.event_pull_request_merge=Izmaiņu pieprasījuma sapludināšana settings.event_package=Pakotne settings.event_package_desc=Repozitorijā izveidota vai dzēsta pakotne. settings.branch_filter=Atzaru filtrs @@ -2022,6 +2185,7 @@ settings.title=Virsraksts settings.deploy_key_content=Saturs settings.key_been_used=Izvietošanas atslēga ar šādu saturu jau ir pievienota. settings.key_name_used=Izvietošanas atslēga ar šādu nosaukumu jau eksistē. +settings.add_key_success=Izvietošanas atslēga "%s" tika pievienota. settings.deploy_key_deletion=Noņemt izvietošanas atslēgu settings.deploy_key_deletion_desc=Noņemot izvietošanas atslēgu, tai tiks liegta piekļuve šim repozitorija. Vai turpināt? settings.deploy_key_deletion_success=Izvietošanas atslēga tika noņemta. @@ -2039,6 +2203,8 @@ settings.protect_disable_push=Neļaut iesūtīt izmaiņas settings.protect_disable_push_desc=Izmaiņu iesūtīšana šajā atzarā netiks atļauta. settings.protect_enable_push=Atļaut iesūtīt izmaiņas settings.protect_enable_push_desc=Ikviens, kam ir rakstīšanas tiesības uz šo repozitoriju, varēs iesūtīt izmaiņas šajā atzarā (piespiedu izmaiņu iesūtīšanas netiks atļauta). +settings.protect_enable_merge=Iespējot sapludināšanu +settings.protect_enable_merge_desc=Ikviens ar rakstīšanas tiesībām varēst sapludināt izmaiņu pieprasījumus šajā atzarā. settings.protect_whitelist_committers=Atļaut iesūtīt izmaiņas norādītajiem lietotājiem vai komandām settings.protect_whitelist_committers_desc=Tikai norādītiem lietotāji vai komandas varēs iesūtīt izmaiņas šajā atzarā (piespiedu izmaiņu iesūtīšanas netiks atļauta). settings.protect_whitelist_deploy_keys=Atļaut izvietošanas atslēgām ar rakstīšanas tiesībām nosūtīt izmaiņas. @@ -2051,8 +2217,13 @@ settings.protect_merge_whitelist_committers_desc=Atļaut tikai noteiktiem lietot settings.protect_merge_whitelist_users=Lietotāji, kas var veikt izmaiņu sapludināšanu: settings.protect_merge_whitelist_teams=Komandas, kas var veikt izmaiņu sapludināšanu: settings.protect_check_status_contexts=Iespējot statusu pārbaudi +settings.protect_status_check_patterns=Statusa pārbaudes šabloni: +settings.protect_status_check_patterns_desc=Norādiet šablonus, kurām statusa pārbaudēm ir jāatbilst pirms atzaru iespējams sapludināt šajā atzarā, kas atbilst šim nosacījumam. Katru šablonu norādīt savā rindā, tie nevar būt tukši. settings.protect_check_status_contexts_desc=Nepieciešamas veiksmīgas statusa pārbaudes pirms sapludināšanas. Izvēlieties, kurām statusa pārbaudēm ir jāizpildās pirms ir iespejams tās sapludināt. Ja iespējots, tad revīzijas sākotnēji jānosūta uz atsevišķu atzaru, pēc kā var tikt saplusinātas vai tieši nosūtītas uz atzariem, kas atbildst veiksmīgām norādītajām stautsa pārbaudēm. Ja konteksts nav norādīts, pēdējai revīzijai ir jābūt veiksmīga neatkarīgi no konteksta. settings.protect_check_status_contexts_list=Statusu pārbaudes, kas šim repozitorijam bijušas pēdējās nedēļas laikā +settings.protect_status_check_matched=Atbilst +settings.protect_invalid_status_check_pattern=Kļūdains statusa pārbaudes šablons: "%s". +settings.protect_no_valid_status_check_patterns=Nav korekta statusa pārbaudes šablona. settings.protect_required_approvals=Vajadzīgi apstiprinājumi: settings.protect_required_approvals_desc=Atļaut sapludināt izmaiņu pieprasījumu tikai ar pietiekamu skaitu pozitīvu recenziju. settings.protect_approvals_whitelist_enabled=Ierobežot apstiprinājumus norādītajiem lietotājiem vai komandām @@ -2064,8 +2235,16 @@ settings.dismiss_stale_approvals_desc=Kad tiek iesūtītas jaunas revīzijas, ka settings.require_signed_commits=Pieprasīt parakstītas revīzijas settings.require_signed_commits_desc=Noraidīt iesūtītās izmaiņas šim atzaram, ja tās nav parakstītas vai nav iespējams pārbaudīt. settings.protect_branch_name_pattern=Aizsargātā zara šablons +settings.protect_patterns=Šabloni +settings.protect_protected_file_patterns=Aizsargāto failu šablons (vairākus var norādīt atdalot ar semikolu ';'): +settings.protect_protected_file_patterns_desc=Aizsargātie faili, ko nevar mainīt, pat ja lietotājam ir tiesības veidot jaunus, labot vai dzēst failus šajā atzarā. Vairākus šablons ir iespējams norādīt atdalot tos ar semikolu (';'). Sīkāka informācija par šabloniem pieejama github.com/gobwas/glob dokumentācijā. Piemēram, .drone.yml, /docs/**/*.txt. +settings.protect_unprotected_file_patterns=Neaizsargāto failu šablons (vairākus var norādīt atdalot ar semikolu ';'): +settings.protect_unprotected_file_patterns_desc=Neaizsargātie faili, ko iespējams mainīt apejot iesūtīšanas ierobežojumus, ja lietotājam ir tiesības iesūtīt izmaiņas šajā atzarā. Vairākus šablons ir iespējams norādīt atdalot tos ar semikolu (';'). Sīkāka informācija par šabloniem pieejama github.com/gobwas/glob dokumentācijā. Piemēram, .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Iespējot aizsargāšanu settings.delete_protected_branch=Atspējot aizsargāšanu +settings.update_protect_branch_success=Atzara aizsardzības nosacījums "%s" tika saglabāta. +settings.remove_protected_branch_success=Atzara aizsardzības nosacījums "%s" tika noņemts. +settings.remove_protected_branch_failed=Neizdevās izdzēst atzara aizsardzības nosacījumu "%s". settings.protected_branch_deletion=Atspējot atzara aizsardzību settings.protected_branch_deletion_desc=Atspējojot atzara aizsardzību, ļaus lietotājiem ar rakstīšanas tiesībām nosūtīt izmaiņas uz atzaru. Vai turpināt? settings.block_rejected_reviews=Neļaut sapludināt izmaiņu pieprasījumus, kam ir pieprasītas izmaiņas @@ -2188,13 +2367,17 @@ diff.review.header=Iesūtīt recenziju diff.review.placeholder=Recenzijas komentārs diff.review.comment=Komentēt diff.review.approve=Apstiprināt +diff.review.self_reject=Izmaiņu pieprasījuma autors nevar pieprasīt izmaiņas savam izmaiņu pieprasījumam diff.review.reject=Pieprasīt izmaiņas +diff.review.self_approve=Izmaiņu pieprasījuma autors nevar apstiprināt savu izmaiņu pieprasījumu diff.committed_by=revīziju iesūtīja diff.protected=Aizsargāts diff.image.side_by_side=Blakus diff.image.swipe=Pārvelkot diff.image.overlay=Pārklājoši diff.has_escaped=Šajā līnijā ir paslēpti unikoda simboli +diff.show_file_tree=Parādīt failu koku +diff.hide_file_tree=Paslēpt failu koku releases.desc=Pārvaldiet projekta versijas un lejupielādes. release.releases=Laidieni @@ -2208,6 +2391,7 @@ release.compare=Salīdzināt release.edit=labot release.ahead.commits=%d revīzijas release.ahead.target=no %s kopš laidiena publicēšanas +tag.ahead.target=revīzijas atzarā %s no šī taga izveidošanas release.source_code=Izejas kods release.new_subheader=Laidieni palīdz organizēt projekta versijas. release.edit_subheader=Laidieni palīdz organizēt projekta versijas. @@ -2216,6 +2400,9 @@ release.target=Mērķis release.tag_helper=Izvēlieties jau esošu tagu vai izveidojiet jaunu. release.tag_helper_new=Jauns tags. Šis tags tiks izveidots no mērķa. release.tag_helper_existing=Esošs tags. +release.title=Laidiena nosaukums +release.title_empty=Nosaukums nevar būt tukšs. +release.message=Aprakstiet šo laidienu release.prerelease_desc=Atzīmēt kā pirmslaidiena versiju release.prerelease_helper=Atzīmēt, ka šo laidienu nav ieteicams lietot produkcijā. release.cancel=Atcelt @@ -2242,24 +2429,46 @@ release.tags_for=Repozitorija %s tagi branch.name=Atzara nosaukums branch.search=Meklēt atzarus +branch.already_exists=Atzars ar nosaukumu "%s" jau eksistē. branch.delete_head=Dzēst +branch.delete=`Dzēst atzaru "%s"` branch.delete_html=Dzēst atzaru branch.delete_desc=Atzara dzēšana ir neatgriezeniska, kā arī tā ir NEATGRIEZENISKA. Vai turpināt? +branch.deletion_success=Atzars "%s" tika izdzēsts. +branch.deletion_failed=Neizdevās izdzēst atzaru "%s". +branch.delete_branch_has_new_commits=Atzars "%s" nevar tik dzēsts, jo pēc sapludināšanas, tam ir pievienotas jaunas revīzijas. branch.create_branch=Izveidot atzaru %s +branch.create_from=`no "%s"` +branch.create_success=Tika izveidots atzars "%s". +branch.branch_already_exists=Atzars "%s" šajā repozitorijā jau eksistē. +branch.branch_name_conflict=Atzara nosaukums "%s" konfliktē ar jau esošu atzaru "%s" šajā repozitorijā. +branch.tag_collision=Atzaru "%s" nevar izveidot, jo repozitorijā eksistē tags ar tādu pašu nosaukumu. branch.deleted_by=Izdzēsa %s +branch.restore_success=Tika atjaunots atzars "%s". +branch.restore_failed=Neizdevās atjaunot atzaru "%s". +branch.protected_deletion_failed=Atzars "%s" ir aizsargāts. To nevar dzēst. +branch.default_deletion_failed=Atzars "%s" ir noklusētais atzars un to nevar dzēst. +branch.restore=`Atjaunot atzaru "%s"` +branch.download=`Lejupielādēt atzaru "%s"` +branch.rename=`Pārsaukt atzaru "%s"` branch.included_desc=Šis atzars ir daļa no noklusēta atzara branch.included=Iekļauts branch.create_new_branch=Izveidot jaunu atzaru no atzara: branch.confirm_create_branch=Izveidot atzaru +branch.warning_rename_default_branch=Tiks pārsaukts noklusētais atzars. +branch.rename_branch_to=Pārsaukt "%s" uz: branch.confirm_rename_branch=Pārsaukt atzaru branch.create_branch_operation=Izveidot atzaru branch.new_branch=Izveidot jaunu atzaru +branch.new_branch_from=`Izveidot jaunu atzaru no "%s"` branch.renamed=Atzars %s tika pārsaukts par %s. tag.create_tag=Izveidot tagu %s tag.create_tag_operation=Izveidot tagu tag.confirm_create_tag=Izveidot tagu +tag.create_tag_from=`Izveidot tagu no "%s"` +tag.create_success=Tags "%s" tika izveidots. topic.manage_topics=Pārvaldīt tēmas topic.done=Gatavs @@ -2296,6 +2505,8 @@ team_permission_desc=Atļauja team_unit_desc=Atļaut piekļuvi repozitorija sadaļām team_unit_disabled=(Atspējots) +form.name_reserved=Organizācijas nosaukums "%s" ir rezervēts. +form.name_pattern_not_allowed=Organizācijas nosaukums "%s" nav atļauts. form.create_org_not_allowed=Jums nav tiesību veidot jauno organizāciju. settings=Iestatījumi @@ -2307,6 +2518,7 @@ settings.permission=Tiesības settings.repoadminchangeteam=Repozitorija administrators var pievienot vai noņemt piekļuvi komandām settings.visibility=Redzamība settings.visibility.public=Publiska +settings.visibility.limited=Ierobežots (redzams tikai autentificētiem lietotājiem) settings.visibility.limited_shortname=Ierobežota settings.visibility.private=Privāta (redzama tikai organizācijas dalībniekiem) settings.visibility.private_shortname=Privāta @@ -2378,6 +2590,7 @@ teams.remove_all_repos_title=Noņemt visus komandas repozitorijus teams.remove_all_repos_desc=Šī darbība noņems visus repozitorijus no komandas. teams.add_all_repos_title=Pievienot visus repozitorijus teams.add_all_repos_desc=Šī darbība pievienos visus organizācijas repozitorijus šai komandai. +teams.add_nonexistent_repo=Repozitorijs, kuru mēģinat pievienot neeksistē, sākumā izveidojiet to. teams.add_duplicate_users=Lietotājs jau ir šajā komandā. teams.repos.none=Šai komandai nav piekļuves nevienam repozitorijam. teams.members.none=Šajā komandā nav pievienots neviens lietotājs. @@ -2407,6 +2620,7 @@ first_page=Pirmā last_page=Pēdējā total=Kopā: %d +dashboard.new_version_hint=Ir pieejama Gitea versija %s, pašreizējā versija %s. Papildus informācija par jauno versiju ir pieejama mājas lapā. dashboard.statistic=Kopsavilkums dashboard.operations=Uzturēšanas darbības dashboard.system_status=Sistēmas statuss @@ -2500,6 +2714,7 @@ users.created=Izveidots users.last_login=Pēdējā autorizācija users.never_login=Nekad nav autorizējies users.send_register_notify=Nosūtīt lietotājam reģistrācijas paziņojumu +users.new_success=Lietotāja konts "%s" tika izveidots. users.edit=Labot users.auth_source=Autentificēšanas avots users.local=Iebūvētā @@ -2524,6 +2739,7 @@ users.still_own_repo=Lietotājam pieder repozitoriji, tos sākumā ir nepiecieš users.still_has_org=Šis lietotājs ir vienas vai vairāku organizāciju biedrs, lietotāju sākumā ir nepieciešams pamest šīs organizācijas vai viņu no tām ir jāizdzēš. users.purge=Attīrīt lietotu users.purge_help=Piespiedu dzēst lietotāju un visus tā repozitorijus, organizācijas un pakotnes. Arī visi lietotāja komentāri tiks dzēsti. +users.still_own_packages=Šim lietotājam pieder viena vai vairākas pakotnes, tās nepieciešams izdzēst. users.deletion_success=Lietotāja konts veiksmīgi izdzēsts. users.reset_2fa=Noņemt 2FA users.list_status_filter.menu_text=Filtrs @@ -2698,6 +2914,7 @@ auths.tip.yandex=`Izveidojiet jaunu aplikāciju adresē https://oauth.yandex.com auths.tip.mastodon=Norādiet pielāgotu mastodon instances URL, ar kuru vēlaties autorizēties (vai izmantojiet noklusēto) auths.edit=Labot autentifikācijas avotu auths.activated=Autentifikācijas avots ir atkivizēts +auths.new_success=Jauna autentifikācija "%s" tika pievienota. auths.update_success=Autentifikācijas avots tika atjaunots. auths.update=Atjaunot autentifikācijas avotu auths.delete=Dzēst autentifikācijas avotu @@ -2705,6 +2922,7 @@ auths.delete_auth_title=Dzēst autentifikācijas avotu auths.delete_auth_desc=Izdzēšot autentifikācijas avotu, tā lietotājiem nebūs iespējams autorizēties. Vai turpināt? auths.still_in_used=Šo autentificēšanās avotu joprojām izmanto viens vai vairāki lietotāji, tos nepieciešams izdzēst vai pārvietot uz citu autentificēšanās avotu. auths.deletion_success=Autentifikācijas avots tika atjaunots. +auths.login_source_exist=Autentifikācijas avots ar nosaukumu "%s" jau eksistē. auths.login_source_of_type_exist=Autentifikācijas avots ar šādu veidu jau eksistē. auths.unable_to_initialize_openid=Nevarēja inicializēt OpenID Connect sliedzēju: %s auths.invalid_openIdConnectAutoDiscoveryURL=Kļūdains automātiskās atklāšanas URL (jābūt korektam URL, kas sākas ar http:// vai https://) @@ -2797,6 +3015,9 @@ config.mailer_sendmail_timeout=Sendmail noildze config.mailer_use_dummy=Tukšs config.test_email_placeholder=E-pasts (piemēram, test@example.com) config.send_test_mail=Nosūtīt pārbaudes e-pastu +config.send_test_mail_submit=Sūtīt +config.test_mail_failed=Neizdevās nosūtīt pārbaudes e-pastu uz "%s": %v +config.test_mail_sent=Pārbaudes e-pasts tika nosūtīts uz "%s". config.oauth_config=OAuth konfigurācija config.oauth_enabled=Iespējots @@ -2834,13 +3055,16 @@ config.git_pull_timeout=Izmaiņu saņemšanas darbības noilgums config.git_gc_timeout=GC darbības noilgums config.log_config=Žurnalizēšanas konfigurācija +config.logger_name_fmt=Žurnalizētājs: %s config.disabled_logger=Atspējots config.access_log_mode=Piekļuves žurnalizēšanas veids +config.access_log_template=Piekļuves žurnāla sagatave config.xorm_log_sql=SQL žurnalizēšana config.get_setting_failed=`Neizdevās izgūt iestatījumu "%s"` config.set_setting_failed=`Neizdevās uzstādīt iestatījumu "%s"` +monitor.stats=Statistika monitor.cron=Cron uzdevumi monitor.name=Nosaukums @@ -2850,6 +3074,8 @@ monitor.previous=Pēdējās izpildes laiks monitor.execute_times=Izpildes monitor.process=Darbojošies procesi monitor.stacktrace=Steka izsekojamība +monitor.processes_count=%d procesi +monitor.download_diagnosis_report=Lejupielādēt diagnostikas atskaiti monitor.desc=Apraksts monitor.start=Sākuma laiks monitor.execute_time=Izpildes laiks @@ -2870,11 +3096,14 @@ monitor.queue.numberinqueue=Skaits rindā monitor.queue.review=Pārbaudīt konfigurāciju monitor.queue.review_add=Pārbaudīt/Pievienot strādņus monitor.queue.settings.title=Pūla iestatījumi +monitor.queue.settings.desc=Pūls dinamiski tiek palielināts atkarībā no bloķētiem darbiem rindā. monitor.queue.settings.maxnumberworkers=Maksimālais strādņu skaits monitor.queue.settings.maxnumberworkers.placeholder=Pašalaik %[1]d monitor.queue.settings.maxnumberworkers.error=Maksimālajam strādņu skaitam ir jābūt skaitlim monitor.queue.settings.submit=Saglabāt iestatījumus monitor.queue.settings.changed=Iestatījumi saglabāti +monitor.queue.settings.remove_all_items=Noņemt visus +monitor.queue.settings.remove_all_items_done=Visi ieraksti rindā tika noņemti. notices.system_notice_list=Sistēmas paziņojumi notices.view_detail_header=Skatīt paziņojuma detaļas @@ -2984,6 +3213,7 @@ error.unit_not_allowed=Jums nav tiesību piekļūt šai repozitorija sadaļai. title=Pakotnes desc=Pārvaldīt repozitorija pakotnes. empty=Pašlaik šeit nav nevienas pakotnes. +empty.documentation=Papildus informācija par pakotņu reģistru pieejama dokumentācijā. empty.repo=Neparādās augšupielādēta pakotne? Apmeklējiet pakotņu iestatījumus, lai sasaistītu ar repozitoriju. filter.type=Veids filter.type.all=Visas @@ -3008,9 +3238,15 @@ versions=Versijas versions.view_all=Parādīt visas dependency.id=ID dependency.version=Versija +alpine.registry=Iestaties šo reģistru pievienojot tā URL /etc/apk/repositories failā: +alpine.registry.key=Lejupielādējiet reģistra publisko RSA atslēgu direktorijā /etc/apk/keys/, lai pārbaudītu indeksa parakstu: +alpine.registry.info=Izvēlieties $branch un $repository no saraksta zemāk. alpine.install=Lai instalētu pakotni, nepieciešams izpildīt sekojošu komandu: +alpine.documentation=Papildus informācija par Alpine reģistru pieejama dokumentācijā. +alpine.repository=Repozitorija informācija alpine.repository.branches=Atzari alpine.repository.repositories=Repozitoriji +alpine.repository.architectures=Arhitektūras cargo.registry=Uzstādiet šo reģistru Cargo konfigurācijas failā, piemēram, ~/.cargo/config.toml: cargo.install=Lai instalētu Cargo pakotni, izpildiet sekojošu komandu: cargo.documentation=Papildus informācija par Cargo reģistru pieejama dokumentācijā. @@ -3043,11 +3279,21 @@ container.layers=Attēla slāņi container.labels=Etiķetes container.labels.key=Atslēga container.labels.value=Vērtība +cran.registry=Iestaties šo reģistru savā Rprofile.site failā: cran.install=Lai instalētu pakotni, nepieciešams izpildīt sekojošu komandu: +cran.documentation=Papildus informācija par CRAN reģistru pieejama dokumentācijā. debian.registry=Konfigurējiet šo reģistru no komandrindas: +debian.registry.info=Izvēlieties $distribution un $component no saraksta zemāk. debian.install=Lai instalētu pakotni, nepieciešams izpildīt sekojošu komandu: +debian.documentation=Papildus informācija par Debian reģistru pieejama dokumentācijā. +debian.repository=Repozitorija informācija +debian.repository.distributions=Distribūcijas +debian.repository.components=Komponentes +debian.repository.architectures=Arhitektūras generic.download=Lejupielādēt pakotni, izmantojot, komandrindu: generic.documentation=Papildus informācija par ģenerisku pakotņu reģistru pieejama dokumentācijā. +go.install=Instalēt pakotni no komandrindas: +go.documentation=Papildus informācija par Go reģistru pieejama dokumentācijā. helm.registry=Konfigurējiet šo reģistru no komandrindas: helm.install=Lai instalētu pakotni, nepieciešams izpildīt sekojošu komandu: helm.documentation=Papildus informācija par Helm reģistru pieejama dokumentācijā. @@ -3076,6 +3322,7 @@ pypi.install=Lai instalētu pip pakotni, izpildiet sekojošu komandu: pypi.documentation=Papildus informācija par PyPI reģistru pieejama dokumentācijā. rpm.registry=Konfigurējiet šo reģistru no komandrindas: rpm.install=Lai instalētu pakotni, nepieciešams izpildīt sekojošu komandu: +rpm.documentation=Papildus informācija par RPM reģistru pieejama dokumentācijā. rubygems.install=Lai instalētu gem pakotni, izpildiet sekojošu komandu: rubygems.install2=vai pievienojiet Gemfile: rubygems.dependencies.runtime=Izpildlaika atkarības @@ -3084,6 +3331,9 @@ rubygems.required.ruby=Nepieciešamā Ruby versija rubygems.required.rubygems=Nepieciešamā RubyGem versija rubygems.documentation=Papildus informācija par RubyGems reģistru pieejama dokumentācijā. swift.registry=Konfigurējiet šo reģistru no komandrindas: +swift.install=Pievienojiet pakotni savā Package.swift failā: +swift.install2=un izpildiet sekojošu komandu: +swift.documentation=Papildus informācija par Swift reģistru pieejama dokumentācijā. vagrant.install=Lai pievienotu Vagrant kasti, izpildiet sekojošu komandu: vagrant.documentation=Papildus informācija par Vagrant reģistru pieejama dokumentācijā. settings.link=Piesaistīt pakotni šim repozitorijam @@ -3139,11 +3389,13 @@ name=Nosaukums creation=Pievienot noslēpumu 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. deletion.failed=Neizdevās dzēst noslēpumu. +management=Noslēpumu pārvaldība [actions] actions=Darbības @@ -3195,12 +3447,18 @@ runners.status.idle=Dīkstāvē runners.status.active=Aktīvs runners.status.offline=Bezsaistē runners.version=Versija +runners.reset_registration_token_success=Izpildītāja reģistrācijas talons tikai atiestatīts runs.all_workflows=Visas darbaplūsmas runs.commit=Revīzija runs.pushed_by=Iesūtītājs +runs.invalid_workflow_helper=Darbaplūsmas konfigurācijas fails ir kļūdains. Pārbaudiet konfiugrācijas failu: %s +runs.no_matching_runner_helper=Nav atbilstošu izpildītāju: %s need_approval_desc=Nepieciešams apstiprinājums, lai izpildītu izmaiņu pieprasījumu darbaplūsmas no atdalītiem repozitorijiem. [projects] +type-1.display_name=Individuālais projekts +type-2.display_name=Repozitorija projekts +type-3.display_name=Organizācijas projekts diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index f68ec11bbaf89..2eca35e81e99e 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -384,7 +384,7 @@ reset_password.text=Klik op de volgende link om je account te herstellen binnen register_success=Registratie succesvol issue_assigned.pull=@%[1]s heeft u toegewezen aan de pull request %[2]s in repository %[3]s. -issue_assigned.issue=@%[1]heeft u toegewezen aan issue %[2]s in repository %[3]s. +issue_assigned.issue=@%[1]s heeft u toegewezen aan issue %[2]s in repository %[3]s. issue.x_mentioned_you=@%s heeft u vermeld: issue.action.force_push=%[1]s heeft een force-push uitgevoerd %[2]s van %[3]s naar %[4]s. @@ -392,7 +392,7 @@ issue.action.push_1=@%[1]s heeft %[3]d commits gepusht naar %[2]s issue.action.push_n=@%[1]s heeft %[3]d commits gepusht naar %[2]s issue.action.close=@%[1]s sloot #%[2]d. issue.action.reopen=@%[1]s heropend #%[2]d. -issue.action.merge=@%[1] heeft een merge uitgevoerd van #%[2]d naar %[3]s. +issue.action.merge=@%[1]s heeft een merge uitgevoerd van #%[2]d naar %[3]s. issue.action.approve=@%[1]s heeft deze pull request goedgekeurd. issue.action.reject=@%[1]s vraagt om wijzigingen op deze pull request. issue.action.review=@%[1]s heeft gereageerd op deze pull request. diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 32a4582db36c1..09384f28bfc30 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -2494,7 +2494,7 @@ monitor.queue.review=Przejrzyj konfigurację monitor.queue.review_add=Przejrzyj/Dodaj procesy pracujące monitor.queue.settings.title=Ustawienia Puli monitor.queue.settings.maxnumberworkers=Maksymalna liczba procesów pracujących -monitor.queue.settings.maxnumberworkers.placeholder=Obecnie %[1]v +monitor.queue.settings.maxnumberworkers.placeholder=Obecnie %[1]d monitor.queue.settings.maxnumberworkers.error=Maksymalna liczba procesów pracujących musi być liczbą monitor.queue.settings.submit=Aktualizuj ustawienia monitor.queue.settings.changed=Zaktualizowano ustawienia diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index ccfbcbf98a2bd..a4cf7dd4531db 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -79,6 +79,8 @@ milestones=Marcos ok=Ok cancel=Cancelar +rerun=Reexecutar +rerun_all=Reexecutar todas as tarefas save=Salvar add=Adicionar add_all=Adicionar todos @@ -113,11 +115,18 @@ unknown=Desconhecido rss_feed=Feed RSS +pin=Fixar +unpin=Desfixar +artifacts=Artefatos +concept_system_global=Global +concept_user_individual=Individual concept_code_repository=Repositório concept_user_organization=Organização +show_log_seconds=Mostrar segundos +show_full_screen=Mostrar tela cheia [aria] navbar=Barra de navegação @@ -142,6 +151,7 @@ buttons.list.unordered.tooltip=Adicionar uma lista com marcadores buttons.list.ordered.tooltip=Adicionar uma lista numerada buttons.list.task.tooltip=Adicionar uma lista de tarefas buttons.mention.tooltip=Mencionar um usuário ou equipe +buttons.ref.tooltip=Referenciar um issue ou um pull request buttons.switch_to_legacy.tooltip=Em vez disso, usar o editor legado buttons.enable_monospace_font=Habilitar fonte mono espaçada buttons.disable_monospace_font=Desabilitar fonte mono espaçada @@ -247,6 +257,7 @@ openid_signup_popup=Habilitar o auto-cadastro com base no OpenID. enable_captcha=Habilitar CAPTCHA ao registrar enable_captcha_popup=Obrigar validação por CAPTCHA para auto-cadastro de usuários. require_sign_in_view=Exigir acesso do usuário para a visualização de páginas +require_sign_in_view_popup=Limitar o acesso de página aos usuários autenticados. Os visitantes só verão as páginas de autenticação e cadastro. admin_setting_desc=Criar uma conta de administrador é opcional. O primeiro usuário cadastrado automaticamente se tornará um administrador. admin_title=Configurações da conta de administrador admin_name=Nome do usuário administrador @@ -312,6 +323,7 @@ repos=Repositórios users=Usuários organizations=Organizações search=Pesquisar +go_to=Ir para code=Código search.type.tooltip=Tipo de pesquisa search.fuzzy=Similar @@ -425,7 +437,7 @@ reset_password.text=Por favor clique no link a seguir para recuperar sua conta e register_success=Cadastro bem-sucedido -issue_assigned.pull=@%[1]atribuiu a você o pull request %[2]s no repositório %[3]s. +issue_assigned.pull=@%[1]s atribuiu a você o pull request %[2]s no repositório %[3]s. issue_assigned.issue=@%[1]s atribuiu a você a issue %[2]s no repositório %[3]s. issue.x_mentioned_you=@%s mencionou você: @@ -467,6 +479,7 @@ team_invite.text_3=Nota: este convite foi destinado a %[1]s. Se você não estav [modal] yes=Sim no=Não +confirm=Confirmar cancel=Cancelar modify=Atualizar @@ -514,6 +527,7 @@ lang_select_error=Selecione um idioma da lista. username_been_taken=O nome de usuário já está sendo usado. username_change_not_local_user=Usuários não-locais não são autorizados a alterar nome de usuário. +username_has_not_been_changed=Nome de usuário não foi alterado repo_name_been_taken=O nome de repositório já está sendo usado. repository_force_private=Forçar Privado está ativado: repositórios privados não podem ser tornados públicos. repository_files_already_exist=Arquivos já existem neste repositório. Contate o administrador. @@ -555,11 +569,14 @@ auth_failed=Autenticação falhou: %v still_own_repo=Sua conta possui um ou mais repositórios, exclua ou transfira-os primeiro. still_has_org=Sua conta é um membro de uma ou mais organizações, deixe-as primeiro. still_own_packages=Sua conta possui um ou mais pacotes, exclua-os primeiro. +org_still_own_repo=Esta organização ainda possui repositórios, exclua ou transfira-os primeiro. +org_still_own_packages=Esta organização ainda possui pacotes, exclua-os primeiro. target_branch_not_exist=O branch de destino não existe. [user] change_avatar=Altere seu avatar... +joined_on=Inscreveu-se em %s repositories=Repositórios activity=Atividade pública followers=Seguidores @@ -684,10 +701,12 @@ add_new_email=Adicionar novo endereço de e-mail add_new_openid=Adicionar novo URI OpenID add_email=Adicionar novo endereço de e-mail add_openid=Adicionar URI OpenID +add_email_confirmation_sent=Um e-mail de confirmação foi enviado para "%s". Verifique sua caixa de entrada nos próximos %s para confirmar seu endereço de e-mail. add_email_success=O novo endereço de e-mail foi adicionado. email_preference_set_success=Preferência de e-mail definida com sucesso. add_openid_success=O novo endereço de OpenID foi adicionado. keep_email_private=Ocultar endereço de e-mail +keep_email_private_popup=Seu endereço de e-mail ficará visível apenas para você e para os administradores openid_desc=OpenID permite delegar autenticação para um provedor externo. manage_ssh_keys=Gerenciar Chaves SSH @@ -721,6 +740,7 @@ gpg_token_help=Você pode gerar uma assinatura usando: gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig gpg_token_signature=Assinatura GPG blindada key_signature_gpg_placeholder=Começa com '-----BEGIN PGP SIGNATURE-----' +verify_gpg_key_success=A chave GPG "%s" foi validada. ssh_key_verified=Chave validada ssh_key_verified_long=A chave foi validada com um token e pode ser usada para validar commits que correspondam a qualquer dos endereços de e-mail ativados deste usuário. ssh_key_verify=Validar @@ -730,11 +750,14 @@ ssh_token=Token ssh_token_help=Você pode gerar uma assinatura usando: ssh_token_signature=Assinatura SSH blindada key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----' +verify_ssh_key_success=A chave SSH "%s" foi validada. subkeys=Subchaves key_id=ID da chave key_name=Nome da Chave key_content=Conteúdo principal_content=Conteúdo +add_key_success=A chave SSH "%s" foi adicionada. +add_gpg_key_success=A chave GPG "%s" foi adicionada. delete_key=Remover ssh_key_deletion=Remover a chave SSH gpg_key_deletion=Remover a chave GPG @@ -745,6 +768,8 @@ ssh_principal_deletion_desc=A exclusão de um Nome Principal de um Certificado S ssh_key_deletion_success=A chave SSH foi removida. gpg_key_deletion_success=A chave GPG foi removida. ssh_principal_deletion_success=O nome principal foi removido. +added_on=Adicionado em %s +valid_until_date=Válido até %s valid_forever=Válido para sempre last_used=Última vez usado em no_activity=Nenhuma atividade recente @@ -756,6 +781,7 @@ principal_state_desc=Este nome principal foi utilizado nos últimos 7 dias show_openid=Mostrar no perfil hide_openid=Ocultar no perfil ssh_disabled=SSH desabilitado +ssh_signonly=O SSH está desativado no momento, portanto, essas chaves são usadas apenas para verificação de assinatura de confirmação. ssh_externally_managed=Esta chave SSH para este usuário é gerenciada externamente manage_social=Gerenciar contas sociais associadas social_desc=Essas contas sociais estão vinculadas à sua conta do Gitea. Certifique-se de reconhecer todas elas, pois elas podem ser usadas para acessar a sua conta do Gitea. @@ -775,6 +801,12 @@ access_token_deletion_cancel_action=Cancelar access_token_deletion_confirm_action=Excluir access_token_deletion_desc=A exclusão de um token revoga o acesso à sua conta para aplicativos que o usam. Continuar? delete_token_success=O token foi excluído. Os aplicativos que o utilizam já não têm acesso à sua conta. +repo_and_org_access=Acesso ao Repositório e Organização +permissions_public_only=Apenas público +permissions_access_all=Todos (público, privado e limitado) +select_permissions=Selecionar permissões +at_least_one_permission=Você deve selecionar pelo menos uma permissão para criar um token +permissions_list=Permissões: manage_oauth2_applications=Gerenciar aplicativos OAuth2 edit_oauth2_application=Editar aplicativo OAuth2 @@ -859,6 +891,7 @@ visibility=Visibilidade do usuário visibility.public=Pública visibility.public_tooltip=Visível para todos visibility.limited=Limitada +visibility.limited_tooltip=Visível apenas para usuários autenticados visibility.private=Privada visibility.private_tooltip=Visível apenas para membros da organização @@ -932,6 +965,7 @@ mirror_password_blank_placeholder=(não definida) mirror_password_help=Altere o nome de usuário para apagar uma senha armazenada. watchers=Observadores stargazers=Usuários que estrelaram +stars_remove_warning=Isto irá remover todos os favoritos dados a este repositório. forks=Forks reactions_more=e %d mais unit_disabled=O administrador do site desabilitou esta seção do repositório. @@ -946,6 +980,7 @@ delete_preexisting=Excluir arquivos pré-existentes delete_preexisting_content=Excluir arquivos em %s delete_preexisting_success=Arquivos órfãos excluídos em %s blame_prior=Ver a responsabilização anterior a esta modificação +author_search_tooltip=Mostra um máximo de 30 usuários transfer.accept=Aceitar transferência transfer.accept_desc=`Transferir para "%s"` @@ -974,11 +1009,14 @@ template.one_item=Deve-se selecionar pelo menos um item de modelo template.invalid=Deve-se selecionar um repositório de modelo archive.title=Este repositório está arquivado. Você pode visualizar os arquivos e realizar clone, mas não poderá realizar push nem abrir issues e pull requests. +archive.title_date=Este repositório foi arquivado em %s. Você pode visualizar os arquivos e realizar clone, mas não poderá realizar push nem abrir issues e pull requests. archive.issue.nocomment=Este repositório está arquivado. Você não pode comentar nas issues. archive.pull.nocomment=Este repositório está arquivado. Você não pode comentar nos pull requests. form.reach_limit_of_creation_1=Você já atingiu o seu limite de %d repositório. form.reach_limit_of_creation_n=Você já atingiu o limite de %d repositórios. +form.name_reserved=O nome de repositório "%s" está reservado. +form.name_pattern_not_allowed=O padrão "%s" não é permitido em um nome de repositório. need_auth=Autorização migrate_options=Opções de Migração @@ -1004,6 +1042,7 @@ migrate.github_token_desc=Você pode colocar aqui um ou mais tokens separados po migrate.clone_local_path=ou um caminho de servidor local migrate.permission_denied=Você não pode importar repositórios locais. migrate.permission_denied_blocked=Você não pode importar dos hosts não permitidos, por favor peça ao administrador para verificar as configurações ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS. +migrate.invalid_local_path=O caminho local é inválido. Ele não existe ou não é um diretório. migrate.invalid_lfs_endpoint=O destino LFS não é válido. migrate.failed=Migração falhou: %v migrate.migrate_items_options=Um Token de Acesso é necessário para migrar itens adicionais @@ -1012,6 +1051,7 @@ migrated_from_fake=Migrado de %[1]s migrate.migrate=Migrar de %s migrate.migrating=Migrando a partir de %s ... migrate.migrating_failed=Migração a partir de %s falhou. +migrate.migrating_failed.error=Falha ao migrar: %s migrate.migrating_failed_no_addr=A migração falhou. migrate.github.description=Migrar dados de github.com ou de outras instâncias do GitHub. migrate.git.description=Migrar um repositório somente de qualquer serviço Git. @@ -1028,6 +1068,8 @@ migrate.migrating_labels=Migrando Rótulos migrate.migrating_releases=Migrando Versões migrate.migrating_issues=Migrando Issues migrate.migrating_pulls=Migrando Pull Requests +migrate.cancel_migrating_title=Cancelar migração +migrate.cancel_migrating_confirm=Você quer cancelar essa migração? mirror_from=espelhamento de forked_from=feito fork de @@ -1112,6 +1154,7 @@ download_file=Baixar arquivo normal_view=Visão normal line=linha lines=linhas +from_comment=(comentário) editor.add_file=Adicionar Arquivo editor.new_file=Novo arquivo @@ -1126,6 +1169,7 @@ editor.must_be_on_a_branch=Você deve estar em um branch para propor alteraçõe editor.fork_before_edit=Você deve fazer um fork desse repositório para fazer ou propor alterações neste arquivo. editor.delete_this_file=Excluir arquivo editor.must_have_write_access=Você deve ter permissão de escrita para fazer ou propor alterações neste arquivo. +editor.file_delete_success=O arquivo "%s" foi excluído. editor.name_your_file=Nomeie o seu arquivo… editor.filename_help=Adicione um diretório digitando seu nome seguido por uma barra ('/'). Remova um diretório digitando o backspace no início do campo de entrada. editor.or=ou @@ -1133,8 +1177,12 @@ editor.cancel_lower=Cancelar editor.commit_signed_changes=Commit de alteradores assinadas editor.commit_changes=Aplicar commit das alterações editor.add_tmpl=Adicionar '' +editor.add=Adicionar %s +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. @@ -1146,15 +1194,29 @@ editor.new_branch_name=Nome do novo branch para este commit editor.new_branch_name_desc=Novo nome do branch... editor.cancel=Cancelar editor.filename_cannot_be_empty=Nome do arquivo não pode ser em branco. +editor.filename_is_invalid=O nome do arquivo é inválido: "%s". +editor.branch_does_not_exist=Branch "%s" não existe neste repositório. +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. Clique aqui para ver o que foi editado ou clique em Aplicar commit das alterações novamemente 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: editor.add_subdir=Adicionar um subdiretório... +editor.unable_to_upload_files=Ocorreu um erro ao enviar arquivos para "%s": %v +editor.upload_file_is_locked=Arquivo "%s" está bloqueado por %s. +editor.upload_files_to_dir=`Enviar arquivos para "%s"` +editor.cannot_commit_to_protected_branch=Não foi possível enviar commits para o branch protegido "%s". editor.no_commit_to_branch=Não foi possível fazer commit diretamente no branch porque: editor.user_no_push_to_branch=O usuário não pode fazer push no branch editor.require_signed_commit=Branch requer um commit assinado @@ -1163,6 +1225,7 @@ 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. commits.nothing_to_compare=Estes branches são iguais. commits.search=Pesquisar commits... commits.find=Pesquisar @@ -1197,12 +1260,14 @@ projects.create=Criar Projeto projects.title=Título projects.new=Novo projeto projects.new_subheader=Coordene, acompanhe e atualize seu trabalho em um só lugar, para que os projetos permaneçam transparentes e dentro do cronograma. +projects.create_success=Projeto "%s" criado. projects.deletion=Apagar Projeto projects.deletion_desc=Excluir um projeto o remove de todas as issues relacionadas. Deseja continuar? projects.deletion_success=O projeto foi excluido. projects.edit=Editar Projetos projects.edit_subheader=Projetos organizam issues e acompanham o progresso. projects.modify=Atualizar Projeto +projects.edit_success=Projeto "%s" atualizado. projects.type.none=Nenhum projects.type.basic_kanban=Kanban básico projects.type.bug_triage=Triagem de Bugs @@ -1295,6 +1360,10 @@ issues.filter_label_exclude=`Use alt + clique/enter pa issues.filter_label_no_select=Todas as etiquetas issues.filter_label_select_no_label=Sem etiqueta issues.filter_milestone=Marco +issues.filter_milestone_all=Todos os marcos +issues.filter_milestone_none=Sem marcos +issues.filter_milestone_open=Marcos abertos +issues.filter_milestone_closed=Marcos fechados issues.filter_project=Projeto issues.filter_project_all=Todos os projetos issues.filter_project_none=Sem projeto @@ -1353,6 +1422,7 @@ issues.context.reference_issue=Referência em uma nova issue issues.context.edit=Editar issues.context.delete=Excluir issues.no_content=Ainda não há conteúdo. +issues.close=Fechar issue issues.close_comment_issue=Comentar e fechar issues.reopen_issue=Reabrir issues.reopen_comment_issue=Comentar e reabrir @@ -1403,6 +1473,10 @@ issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba` issues.attachment.download=`Clique para baixar "%s"` issues.subscribe=Inscrever-se issues.unsubscribe=Desinscrever +issues.unpin_issue=Desfixar issue +issues.max_pinned=Você não pode fixar mais issues +issues.pin_comment=fixou isto %s +issues.unpin_comment=desafixou isto %s issues.lock=Bloquear conversação issues.unlock=Desbloquear conversação issues.lock.unknown_reason=Não pode-se bloquear uma issue com um motivo desconhecido. @@ -1461,13 +1535,16 @@ issues.due_date_form_remove=Remover issues.due_date_not_writer=Você deve ter permissão de escrita no repositório para atualizar a data limite de uma issue. issues.due_date_not_set=Data limite não informada. issues.due_date_added=adicionou a data limite %s %s -issues.due_date_modified=modificou a data limite de %[2]para %[1]s %[3]s +issues.due_date_modified=modificou a data limite de %[2]s para %[1]s %[3]s issues.due_date_remove=removeu a data limite %s %s issues.due_date_overdue=Em atraso issues.due_date_invalid=A data limite é inválida ou está fora do intervalo. Por favor, use o formato 'dd/mm/aaaa'. issues.dependency.title=Dependências issues.dependency.issue_no_dependencies=Nenhuma dependência definida. issues.dependency.pr_no_dependencies=Nenhuma dependência definida. +issues.dependency.no_permission_1=Você não tem permissão para ler %d dependência +issues.dependency.no_permission_n=Você não tem permissão para ler %d dependências +issues.dependency.no_permission.can_remove=Você não tem permissão para ler esta dependência, mas pode remover esta dependência issues.dependency.add=Adicione… issues.dependency.cancel=Cancelar issues.dependency.remove=Remover @@ -1506,6 +1583,7 @@ issues.review.add_review_request=solicitou revisão de %s %s issues.review.remove_review_request=removeu a solicitação de revisão para %s %s issues.review.remove_review_request_self=recusou revisar %s issues.review.pending=Pendente +issues.review.pending.tooltip=Este comentário não está atualmente visível para outros usuários. Para enviar seus comentários pendentes, selecione "%s" -> "%s/%s/%s" no topo da página. issues.review.review=Revisão issues.review.reviewers=Revisores issues.review.outdated=Desatualizado @@ -1540,6 +1618,8 @@ pulls.compare_changes_desc=Selecione o branch de destino (push) e o branch de or pulls.has_viewed_file=Visto pulls.has_changed_since_last_review=Alterado desde a última revisão pulls.viewed_files_label=%[1]d / %[2]d arquivos visualizados +pulls.expand_files=Expandir todos os arquivos +pulls.collapse_files=Colapsar todos os arquivos pulls.compare_base=merge em pulls.compare_compare=pull de pulls.switch_comparison_type=Mudar tipo de comparação @@ -1560,6 +1640,7 @@ pulls.reopen_to_merge=Por favor reabra este pull request para aplicar o merge. pulls.cant_reopen_deleted_branch=Este pull request não pode ser reaberto porque o branch foi excluído. pulls.merged=Merge aplicado pulls.manually_merged=Merge aplicado manualmente +pulls.merged_info_text=O branch %s pode ser excluído. pulls.is_closed=O pull request foi fechado. pulls.title_wip_desc=`Inicie o título com o prefixo %s para prevenir o merge do pull request até que o mesmo esteja pronto.` pulls.cannot_merge_work_in_progress=Este pull request está marcado como um trabalho em andamento. @@ -1630,6 +1711,7 @@ pulls.update_branch_rebase=Atualizar branch por rebase pulls.update_branch_success=Atualização do branch foi bem-sucedida pulls.update_not_allowed=Você não tem permissão para atualizar o branch pulls.outdated_with_base_branch=Este branch está desatualizado com o branch base +pulls.close=Fechar pull request pulls.closed_at=`fechou este pull request %[2]s` pulls.reopened_at=`reabriu este pull request %[2]s` pulls.merge_instruction_hint=`Você também pode ver as instruções para a linha de comandos.` @@ -1655,6 +1737,7 @@ pulls.delete.text=Você realmente deseja excluir este pull request? (Isto irá r milestones.new=Novo marco milestones.closed=Fechado %s +milestones.update_ago=Atualizado há %s milestones.no_due_date=Sem data limite milestones.open=Reabrir milestones.close=Fechar @@ -1666,10 +1749,12 @@ milestones.desc=Descrição milestones.due_date=Data limite (opcional) milestones.clear=Limpar milestones.invalid_due_date_format=Formato da data limite deve ser 'dd/mm/aaaa'. +milestones.create_success=O marco "%s" foi criado. milestones.edit=Editar marco milestones.edit_subheader=Marcos organizam as issues e acompanham o progresso. milestones.cancel=Cancelar milestones.modify=Atualizar marco +milestones.edit_success=O marco "%s" foi atualizado. milestones.deletion=Excluir marco milestones.deletion_desc=A exclusão deste marco irá removê-lo de todas as issues. Tem certeza que deseja continuar? milestones.deletion_success=O marco foi excluído. @@ -1680,6 +1765,7 @@ milestones.filter_sort.most_complete=Mais completo milestones.filter_sort.most_issues=Com mais issues milestones.filter_sort.least_issues=Com menos issues +signing.will_sign=`Este commit será assinado com a chave "%s"` signing.wont_sign.error=Houve um erro ao verificar se o commit poderia ser assinado signing.wont_sign.nokey=Não há chave disponível para assinar este commit signing.wont_sign.never=Commits nunca são assinados @@ -1704,6 +1790,8 @@ wiki.create_first_page=Criar a primeira página wiki.page=Página wiki.filter_page=Filtrar página wiki.new_page=Página +wiki.page_title=Título da página +wiki.page_content=Conteúdo wiki.default_commit_message=Escreva uma nota sobre a atualização nesta página (opcional). wiki.save_page=Salvar página wiki.last_commit_info=%s editou esta página %s @@ -1808,6 +1896,7 @@ settings.hooks=Webhooks settings.githooks=Hooks do Git settings.basic_settings=Configurações básicas settings.mirror_settings=Opções de espelhamento +settings.mirror_settings.docs.doc_link_title=Como posso espelhar repositórios? settings.mirror_settings.mirrored_repository=Repositório espelhado settings.mirror_settings.direction=Sentido settings.mirror_settings.direction.pull=Pull @@ -2100,8 +2189,11 @@ settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo settings.require_signed_commits=Exibir commits assinados settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis. settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida +settings.protect_protected_file_patterns=Padrões de arquivos protegidos (separados usando ponto e vírgula ';'): settings.add_protected_branch=Habilitar proteção settings.delete_protected_branch=Desabilitar proteção +settings.remove_protected_branch_success=Proteção do branch "%s" foi desabilitada. +settings.remove_protected_branch_failed=Removendo regra de proteção de branch "%s" falhou. settings.protected_branch_deletion=Desabilitar proteção de branch settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar? settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas @@ -2224,7 +2316,9 @@ diff.review.header=Enviar revisão diff.review.placeholder=Comentário da revisão diff.review.comment=Comentar diff.review.approve=Aprovar +diff.review.self_reject=Os autores do pull request não podem solicitar alterações em seus próprios pull request diff.review.reject=Solicitar alterações +diff.review.self_approve=Os autores do pull request não podem aprovar seu próprio pull request diff.committed_by=commit de diff.protected=Protegido diff.image.side_by_side=Lado a Lado @@ -2290,6 +2384,7 @@ branch.delete=`Excluir branch "%s"` branch.delete_html=Excluir Branch branch.delete_desc=A exclusão de um branch é permanente. Isto NÃO PODERÁ ser desfeito. Continuar? branch.create_branch=Criar branch %s +branch.branch_already_exists=Branch "%s" já existe neste repositório. branch.deleted_by=Excluído por %s branch.included_desc=Este branch faz parte do branch padrão branch.included=Incluído @@ -2462,7 +2557,7 @@ dashboard.clean_unbind_oauth_success=Todas as conexões de OAuth não vinculadas dashboard.task.started=Tarefa Iniciada: %[1]s dashboard.task.process=Tarefa: %[1]s dashboard.task.cancelled=Tarefa: %[1]s cancelada: %[3]s -dashboard.task.error=Erro na Tarefa: %[1]: %[3]s +dashboard.task.error=Erro na Tarefa: %[1]s: %[3]s dashboard.task.finished=Tarefa: %[1]s iniciada por %[2]s foi finalizada dashboard.task.unknown=Tarefa desconhecida: %[1]s dashboard.cron.started=Cron Iniciado: %[1]s @@ -2885,6 +2980,7 @@ config.xorm_log_sql=Log SQL config.get_setting_failed=Falha ao obter configuração %s config.set_setting_failed=Falha ao definir configuração %s +monitor.stats=Estatísticas monitor.cron=Tarefas cron monitor.name=Nome @@ -2894,6 +2990,8 @@ monitor.previous=Vez anterior monitor.execute_times=Execuções monitor.process=Processos em execução monitor.stacktrace=Stacktraces +monitor.processes_count=%d processos +monitor.download_diagnosis_report=Baixar relatório de diagnóstico monitor.desc=Descrição monitor.start=Hora de início monitor.execute_time=Tempo de execução @@ -2919,6 +3017,7 @@ monitor.queue.settings.maxnumberworkers.placeholder=Atualmente %[1]d monitor.queue.settings.maxnumberworkers.error=Número máximo de executores deve ser um número monitor.queue.settings.submit=Atualizar configurações monitor.queue.settings.changed=Configurações atualizadas +monitor.queue.settings.remove_all_items=Remover tudo notices.system_notice_list=Avisos do sistema notices.view_detail_header=Ver detalhes do aviso @@ -3053,8 +3152,10 @@ versions.view_all=Ver todas dependency.id=ID dependency.version=Versão alpine.install=Para instalar o pacote, execute o seguinte comando: +alpine.repository=Informações do repositório alpine.repository.branches=Branches alpine.repository.repositories=Repositórios +alpine.repository.architectures=Arquiteturas cargo.registry=Configurar este registro no arquivo de configuração de Cargo (por exemplo ~/.cargo/config.toml): cargo.install=Para instalar o pacote usando Cargo, execute o seguinte comando: cargo.documentation=Para obter mais informações sobre o registro Cargo, consulte a documentação. @@ -3089,7 +3190,12 @@ container.labels.key=Chave container.labels.value=Valor cran.install=Para instalar o pacote, execute o seguinte comando: debian.registry=Configure este registro pela linha de comando: +debian.registry.info=Escolha uma $distribution e um $component da lista abaixo: debian.install=Para instalar o pacote, execute o seguinte comando: +debian.repository=Informações do repositório +debian.repository.distributions=Distribuições +debian.repository.components=Componentes +debian.repository.architectures=Arquiteturas generic.download=Baixar pacote pela linha de comando: generic.documentation=Para obter mais informações sobre o registro genérico, consulte a documentação. helm.registry=Configurar este registro pela linha de comando: @@ -3242,13 +3348,18 @@ runners.status.idle=Inativo runners.status.active=Ativo runners.status.offline=Offiline runners.version=Versão +runners.reset_registration_token_success=Token de registro de runner redefinido com sucesso runs.all_workflows=Todos os Workflows runs.commit=Commit runs.pushed_by=Push realizado por runs.invalid_workflow_helper=O arquivo de configuração do workflow é inválido. Por favor, verifique seu arquivo de configuração: %s +runs.no_matching_runner_helper=Nenhum runner correspondente: %s need_approval_desc=Precisa de aprovação para executar workflows para pull request do fork. [projects] +type-1.display_name=Projeto individual +type-2.display_name=Projeto do repositório +type-3.display_name=Projeto da organização diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 3688a42d71fc2..0fe6277695337 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -472,7 +472,7 @@ repo.transfer.body=Для того чтобы принять или отклон repo.collaborator.added.subject=%s добавил(а) вас в %s repo.collaborator.added.text=Вы были добавлены в качестве соавтора репозитория: -team_invite.subject=%[1] приглашает вас присоединиться к организации %[2] +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. Если вы не ожидали этого приглашения, можете проигнорировать это письмо. diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 58e2fe044a471..009594dfdb11d 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -353,17 +353,17 @@ issue.action.push_1=@%[1]s pushed%[3]d%[2]s කිරීමට කැප issue.action.push_n=@%[1]s pushed%[3]d%[2]s දක්වා කැපේ issue.action.close=@%[1]s වසා #%[2]d. issue.action.reopen=@%[1]s නැවත විවෘත කරන ලද #%[2]d. -issue.action.merge=@%[1]ගේ ඒකාබද්ධ #%[2]ඈ into%[3]s. +issue.action.merge=@%[1]s ගේ ඒකාබද්ධ #%[2]d ඈ into%[3]s. issue.action.approve=@%[1]s මෙම අදින්න ඉල්ලීම අනුමත. issue.action.reject=@%[1]s මෙම අදින්න ඉල්ලීම මත වෙනස්කම් ඉල්ලා. issue.action.review=@%[1]s මෙම අදින්න ඉල්ලීම පිළිබඳව අදහස් දැක්වීය. issue.action.review_dismissed=@%[1]s මෙම අදින්න ඉල්ලීම සඳහා%[2]s සිට පසුගිය සමාලෝචනය බැහැර. issue.action.ready_for_review=@%[1]s සමාලෝචනය සඳහා සූදානම් මෙම අදින්න ඉල්ලීම සලකුණු. -issue.action.new=@%[1]ගේ නිර්මාණය #%[2]ඈ. +issue.action.new=@%[1]s ගේ නිර්මාණය #%[2]d ඈ. issue.in_tree_path=%sදී: release.new.subject=%s %s නිදහස් -release.new.text=@%[1]හි නිකුතුව%[2]හි[3]හි +release.new.text=@%[1]s හි නිකුතුව%[2]s හි %[3]s හි release.title=සිරැසිය: %s release.note=සටහන: release.downloads=බාගැනීම්: @@ -844,7 +844,7 @@ migrate.permission_denied_blocked=ඔබට අවසර නොලත් ධා migrate.invalid_lfs_endpoint=මෙම LFS අවසන් ලක්ෂ්යය වලංගු නොවේ. migrate.failed=සංක්රමණය අසාර්ථකයි: %v migrate.migrate_items_options=අමතර අයිතම සංක්රමණය කිරීම සඳහා ප්රවේශ ටෝකනය අවශ්ය වේ -migrated_from=%[2]සිටදක්වා සංක්රමණය වී ඇත +migrated_from=%[2]s සිටදක්වා සංක්රමණය වී ඇත migrated_from_fake=සංක්රමණය වූ ගෙම්%[1]s migrate.migrate=%sසිට සංක්රමණය migrate.migrating=%s සිට සංක්රමණය වීම... @@ -1071,7 +1071,7 @@ issues.remove_ref_at=`ඉවත් කරන ලද යොමු %s %s` issues.add_ref_at=`එකතු කරන ලද යොමු %s %s` issues.delete_branch_at=`මකාදැමූ ශාඛාව %s %s` issues.filter_label=ලේබලය -issues.filter_label_exclude=Labels` ඉවත් කිරීමට alt + ක්ලික් කරන්න/ඇතුළු කරන්න +issues.filter_label_exclude=ලේබල බැහැර කිරීමට alt + click/enter භාවිත කරන්න issues.filter_label_no_select=සියලු ලේබල issues.filter_label_select_no_label=ලේබලයක් නැත issues.filter_milestone=සන්ධිස්ථානය @@ -1127,7 +1127,7 @@ issues.reopen_comment_issue=අදහස් දක්වා විවෘත ක issues.create_comment=අදහස issues.closed_at=`මෙම ගැටළුව වසා %[2]s` issues.reopened_at=`මෙම ගැටළුව නැවත විවෘත කරන ලදි %[2]s` -issues.ref_issue_from=මෙම නිකුතුව%[4]හි %[2]s +issues.ref_issue_from=මෙම නිකුතුව %[4]s හි %[2]s issues.ref_pull_from=මෙම අදින්න ඉල්ලීම%[4]s %[2]s issues.ref_closing_from=මෙම ගැටළුව වසා දමනු ඇත%[4]s මෙම ගැටළුව %[2]s issues.ref_reopening_from=මෙම ගැටළුව නැවත විවෘත කරනු ඇත%[4]s මෙම ගැටළුව %[2]s @@ -1214,7 +1214,7 @@ 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=`බලය-pushed%[1]s සිට %[2]s %[4]ගේ %[6]s` +issues.force_push_codes=`බලය-pushed%[1]s සිට %[2]s %[4]s ගේ %[6]s` issues.force_push_compare=සසඳන්න issues.due_date_form=Yyy-mm-dd issues.due_date_form_add=නියමිත දිනය එකතු කරන්න @@ -2039,7 +2039,7 @@ members.member_role=සාමාජික කාර්යභාරය: members.owner=හිමිකරු members.member=සාමාජික members.remove=ඉවත් කරන්න -members.remove.detail=%[1]ගේ සිට%[2]ගේ ඉවත්? +members.remove.detail=%[1]s ගේ සිට%[2]s ගේ ඉවත්? members.leave=හැරයන්න members.leave.detail=%s හැරයනවාද? members.invite_desc=%sවෙත නව සාමාජිකයෙකු එක් කරන්න: @@ -2111,13 +2111,13 @@ dashboard.clean_unbind_oauth=පිරිසිදු නොබැඳි OAUTH dashboard.clean_unbind_oauth_success=සියලුම නොබැඳි OAUTH සම්බන්ධතා මකා දමා ඇත. dashboard.task.started=ආරම්භ කාර්යය:%[1]s dashboard.task.process=කාර්යය:%[1]s -dashboard.task.cancelled=කාර්යය:%[1]ගේ අවලංගු:%[3]ගේ +dashboard.task.cancelled=කාර්යය: %[1]s ගේ අවලංගු: %[3]s ගේ dashboard.task.error=කාර්යයයේ දෝෂය:%[1]s:%[3]s dashboard.task.finished=කාර්යය:%[1]s[2]s විසින් ආරම්භ කර ඇත dashboard.task.unknown=නොදන්නා කාර්යය:%[1]s dashboard.cron.started=ආරම්භ Con:%[1]s dashboard.cron.process=ක්රෝන්:%[1]s -dashboard.cron.cancelled=Con: %s අවලංගු:%[3]ගේ +dashboard.cron.cancelled=Cron: %s අවලංගු:%[3]sගේ dashboard.cron.error=ක්රෝන් හි දෝෂය: %s:%[3]s dashboard.cron.finished=ක්රෝන්:%[1]s අවසන් වී ඇත dashboard.delete_inactive_accounts=සියලුම අක්රීය ගිණුම් මකන්න @@ -2551,16 +2551,16 @@ comment_issue=`නිකුතුව පිළිබඳ අදහස් %[3]s #%[2]s` merge_pull_request=`ඒකාබද්ධ අදින්න ඉල්ලීම %[3]s #%[2]s` transfer_repo=මාරු කරන ලද ගබඩාව %s සිට %s -push_tag=තල්ලු ටැගය %[3]ගේ %[4]ගේ -delete_tag=මකාදැමුවා ටැගය%[2]සිට %[3]s -delete_branch=මකාදැමූ ශාඛාව%[2]සිට %[3]s +push_tag=තල්ලු ටැගය %[3]s ගේ %[4]s ගේ +delete_tag=මකාදැමුවා ටැගය%[2]s සිට %[3]s +delete_branch=මකාදැමූ ශාඛාව %[2]s සිට %[3]s compare_branch=සසඳන්න compare_commits=%d විවරයන් සසඳා බලන්න compare_commits_general=විවරයන් සසඳා බලන්න mirror_sync_push=සමමුහුර්ත %[3]s%[4]s කැඩපත සිට mirror_sync_create=සමමුහුර්ත නව යොමු %[3]s සිට %[4]s කැඩපත සිට mirror_sync_delete=සමමුහුර්ත සහ මකාදැමූ යොමු %[2]s හි %[3]s කැඩපතෙන් -approve_pull_request=`අනුමත %[3]s #%[2]ගේ` +approve_pull_request=`අනුමත %[3]s #%[2]s ගේ` reject_pull_request=%[3]s #%[2]sසඳහා යෝජිත වෙනස්කම් publish_release=`නිදහස් "%[4]s" හි %[3]s` review_dismissed_reason=හේතුව: diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index 2ac69fe196ec5..4132b2d4a3a3e 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -382,7 +382,7 @@ reset_password.text=Pre obnovenie vašeho účtu kliknite, prosím, na nasledovn register_success=Registrácia prebehla úspešne -issue_assigned.pull=@%[1]s vám pridelil pull request %[2] v repozitári %[3]s. +issue_assigned.pull=@%[1]s vám pridelil pull request %[2]s repozitári %[3]s. issue_assigned.issue=@%[1]s vám pridelil úkol %[2]s v repozitári %[3]s. issue.x_mentioned_you=@%s vás zmienil: @@ -397,7 +397,7 @@ issue.action.reject=@%[1]s požadoval zmeny v tomto pull requeste. issue.action.review=@%[1]s okomentoval tento pull request. issue.action.review_dismissed=@%[1]s zamietol poslednú recenziu od %[2]s pre tento pull request. issue.action.ready_for_review=@%[1]s označil tento pull request ako pripravený na revíziu. -issue.action.new=@%[1] vytvoril/a #%[2]d. +issue.action.new=@%[1]s vytvoril/a #%[2]d. issue.in_tree_path=V %s: release.new.subject=%s v %s vydané diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 71b92cbee111e..c75b60af44e60 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -461,10 +461,10 @@ repo.transfer.body=Kabul veya reddetmek için %s ziyaret edin veya görmezden ge repo.collaborator.added.subject=%s sizi %s ekledi repo.collaborator.added.text=Bu depo için katkıcı olarak eklendiniz: -team_invite.subject=%[1] sizi %[2] organizasyonuna katılmaya davet etti -team_invite.text_1=%[1] sizi %[3] organizasyonundaki %[2] takımına katılmaya davet etti. +team_invite.subject=%[1]s sizi %[2]s organizasyonuna katılmaya davet etti +team_invite.text_1=%[1]s sizi %[3]s organizasyonundaki %[2]s takımına katılmaya davet etti. team_invite.text_2=Takıma katılmak lütfen aşağıdaki bağlantıya tıklayın: -team_invite.text_3=Not: Bu davet %[1] içindi. Bu daveti beklemiyorsanız, e-postayı yok sayabilirsiniz. +team_invite.text_3=Not: Bu davet %[1]s içindi. Bu daveti beklemiyorsanız, e-postayı yok sayabilirsiniz. [modal] yes=Evet diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 9a4476d1d1538..1eaf6ae3b541c 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1182,10 +1182,10 @@ issues.create_comment=Коментар issues.closed_at=`закрив цю задачу %[2]s` issues.reopened_at=`повторно відкрив цю задачу %[2]s` issues.commit_ref_at=`згадано цю задачу в коміті %[2]s` -issues.ref_issue_from=`посилання на цю задачу %[4] %[2]s` +issues.ref_issue_from=`посилання на цю задачу %[4]s %[2]s` issues.ref_pull_from=`послався на цей запит злиття %[4]s %[2]s` -issues.ref_closing_from=`згадав запит на злиття %[4]с, які закриють цю задачу %[2]s` -issues.ref_reopening_from=`згадав запит на злиття %[4]с, які повторно відкриють цю задачу %[2]s` +issues.ref_closing_from=`згадав запит на злиття %[4]s, які закриють цю задачу %[2]s` +issues.ref_reopening_from=`згадав запит на злиття %[4]s, які повторно відкриють цю задачу %[2]s` issues.ref_closed_from=`закрив цю задачу %[4]s %[2]s` issues.ref_reopened_from=`повторно відкрито цю задачу %[4]s %[2]s` issues.ref_from=`із %[1]s` @@ -1269,7 +1269,7 @@ 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 з %[2] до %[4]s %[6]s` +issues.force_push_codes=`примусово залито %[1]s з %[2]s до %[4]s %[6]s` issues.force_push_compare=Порівняти issues.due_date_form=рррр-мм-дд issues.due_date_form_add=Додати дату завершення @@ -2613,7 +2613,7 @@ comment_issue=`прокоментував задачу %[3]s#%[2 comment_pull=`прокоментував запит злиття %[3]s#%[2]s` merge_pull_request=`прийняв запит злиття %[3]s#%[2]s` transfer_repo=перенесено репозиторій %s у %s -push_tag=створив мітку %[3] в %[4]s +push_tag=створив мітку %[3]s в %[4]s delete_tag=видалено мітку %[2]s з %[3]s delete_branch=видалено гілку %[2]s з %[3]s compare_branch=Порівняти @@ -2622,14 +2622,14 @@ compare_commits_general=Порівняти коміти mirror_sync_push=синхронізував коміти в %[3]s в %[4]s із дзеркала mirror_sync_create=синхронізував нове посилання %[3]s в %[4]s із дзеркала mirror_sync_delete=синхронізовано й видалено посилання %[2]s на %[3]s із дзеркала -approve_pull_request=`схвалив %[3]s#%[2]` +approve_pull_request=`схвалив %[3]s#%[2]s` reject_pull_request=`запропонував зміни до %[3]s#%[2]s` publish_release=`опублікував випуск "%[4]s" з %[3]s` -review_dismissed=`відхилив відгук від %[4] для %[3]s#%[2]s` +review_dismissed=`відхилив відгук від %[4]s для %[3]s#%[2]s` review_dismissed_reason=Причина: create_branch=створив гілку %[3]s в %[4]s starred_repo=додав %[2]s у обране -watched_repo=почав слідкувати за %[2] +watched_repo=почав слідкувати за %[2]s [tool] now=зараз diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 03d0317cc3dde..155db3d32c0dc 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -3135,7 +3135,7 @@ error.unit_not_allowed=您没有权限访问此仓库单元 title=软件包 desc=管理仓库软件包。 empty=还没有软件包。 -empty.documentation=关于软件包注册中心的更多信息,请参阅 文档 。 +empty.documentation=关于软件包注册中心的更多信息,请参阅 文档 。 empty.repo=您上传了一个包,但没有显示在这里吗?转到 包设置 并将其链接到这个仓库中。 filter.type=类型 filter.type.all=所有 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 35c8fa4ce6fa1..af7c19a164f96 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -3110,7 +3110,7 @@ error.unit_not_allowed=您未被允許訪問此儲存庫區域 title=套件 desc=管理儲存庫套件。 empty=目前還沒有套件。 -empty.documentation=關於套件註冊中心的詳情請參閱說明文件。 +empty.documentation=關於套件註冊中心的詳情請參閱說明文件。 empty.repo=已經上傳了一個套件,但是沒有顯示在這裡嗎?打開套件設定並將其連結到這個儲存庫。 filter.type=類型 filter.type.all=所有 diff --git a/package-lock.json b/package-lock.json index 11638d4d7f66d..92e4087b08495 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,46 +18,48 @@ "@webcomponents/custom-elements": "1.6.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "5.2.1", - "asciinema-player": "3.4.0", + "asciinema-player": "3.5.0", "clippie": "4.0.1", "css-loader": "6.8.1", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", "esbuild-loader": "3.0.1", "escape-goat": "4.0.0", - "fast-glob": "3.2.12", + "fast-glob": "3.3.0", "jquery": "3.7.0", "jquery.are-you-sure": "1.9.0", - "katex": "0.16.7", + "katex": "0.16.8", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "10.2.3", + "lightningcss-loader": "2.1.0", + "mermaid": "10.2.4", "mini-css-extract-plugin": "2.7.6", - "minimatch": "9.0.1", - "monaco-editor": "0.39.0", + "minimatch": "9.0.3", + "monaco-editor": "0.40.0", "monaco-editor-webpack-plugin": "7.0.1", "pdfobject": "2.2.12", "pretty-ms": "8.0.0", "sortablejs": "1.15.0", - "swagger-ui-dist": "5.0.0", + "swagger-ui-dist": "5.1.0", "throttle-debounce": "5.0.0", + "tinycolor2": "1.6.0", "tippy.js": "6.3.7", + "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "3.3.4", "vue-bar-graph": "2.0.0", "vue-loader": "17.2.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.87.0", + "webpack": "5.88.1", "webpack-cli": "5.1.4", "wrap-ansi": "8.1.0" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "3.2.1", "@playwright/test": "1.35.1", - "@rollup/pluginutils": "5.0.2", "@stoplight/spectral-cli": "6.8.0", "@vitejs/plugin-vue": "4.2.3", - "eslint": "8.43.0", + "eslint": "8.44.0", "eslint-plugin-array-func": "3.1.8", "eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-import": "2.27.5", @@ -67,23 +69,33 @@ "eslint-plugin-regexp": "1.15.0", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "47.0.0", - "eslint-plugin-vue": "9.14.1", + "eslint-plugin-vue": "9.15.1", "eslint-plugin-wc": "1.5.0", "jsdom": "22.1.0", "markdownlint-cli": "0.35.0", "postcss-html": "1.5.0", - "stylelint": "15.8.0", + "stylelint": "15.9.0", "stylelint-declaration-block-no-ignored-properties": "2.7.0", "stylelint-declaration-strict-value": "1.9.2", "stylelint-stylistic": "0.4.2", "svgo": "3.0.2", - "updates": "14.2.4", - "vitest": "0.32.2" + "updates": "14.3.2", + "vite-string-plugin": "1.1.0", + "vitest": "0.33.0" }, "engines": { "node": ">= 16.0.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@asyncapi/specs": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-4.3.1.tgz", @@ -197,9 +209,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -208,9 +220,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -367,9 +379,9 @@ } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.2.0.tgz", - "integrity": "sha512-9BoQ/jSrPq4vv3b9jjLW+PNNv56KlDH5JMx5yASSNrCtvq70FCNZUjXRvbCeR9hYj9ZyhURtqpU/RFIgg6kiOw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.0.tgz", + "integrity": "sha512-dTKSIHHWc0zPvcS5cqGP+/TPFUJB0ekJ9dGKvMAFoNuBFhDPBt9OMGNZiIA5vTiNdGHHBeScYPXIGBMnVOahsA==", "dev": true, "funding": [ { @@ -402,9 +414,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.0.tgz", - "integrity": "sha512-MXkR+TeaS2q9IkpyO6jVCdtA/bfpABJxIrfkLswThFN8EZZgI2RfAHhm6sDNDuYV25d5+b8Lj1fpTccIcSLPsQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.2.tgz", + "integrity": "sha512-M8cFGGwl866o6++vIY7j1AKuq9v57cf+dGepScwCcbut9ypJNr4Cj+LLTWligYUZ0uyhEoJDKt5lvyBfh2L3ZQ==", "dev": true, "funding": [ { @@ -420,7 +432,7 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-parser-algorithms": "^2.3.0", "@csstools/css-tokenizer": "^2.1.1" } }, @@ -819,14 +831,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -886,9 +898,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", - "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1039,6 +1051,18 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1069,9 +1093,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -1263,7 +1287,7 @@ "rollup": "^2.68.0" } }, - "node_modules/@rollup/plugin-commonjs/node_modules/@rollup/pluginutils": { + "node_modules/@rollup/pluginutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", @@ -1280,40 +1304,18 @@ "rollup": "^1.20.0||^2.0.0" } }, - "node_modules/@rollup/plugin-commonjs/node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", "dev": true }, - "node_modules/@rollup/plugin-commonjs/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "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", @@ -1438,10 +1440,38 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/@stoplight/spectral-cli/node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@stoplight/spectral-cli/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@stoplight/spectral-core": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.18.0.tgz", - "integrity": "sha512-0aj+IELHvhjoPWoOFj41EJilPbaexUuWFg7GCsiJ3BXrniRp3GnPl+TIZkC1ZuuAr/oi77RviDhW9Gm7ndKB9Q==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.18.1.tgz", + "integrity": "sha512-tgUoQZ68hdhFG6CJToBZQtY2fCkh66T4BUtBhdzQ0ZqLU6MhgdL4PlE0yT2tObwsSExNuzaRZH6ssjvbjnHtQQ==", "dev": true, "dependencies": { "@stoplight/better-ajv-errors": "1.0.3", @@ -1622,9 +1652,9 @@ } }, "node_modules/@stoplight/spectral-ruleset-migrator": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-ruleset-migrator/-/spectral-ruleset-migrator-1.9.3.tgz", - "integrity": "sha512-lqdBW7pZT+V1DNbOxG2EkjqMIMC5YjQ4bFkgvziH6HFAnGLlvoa2chJHt+X22+DhuvBF8tzpaYrT51iL/BhxXg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-ruleset-migrator/-/spectral-ruleset-migrator-1.9.4.tgz", + "integrity": "sha512-bQjmYTf1COdhXdFg4dRzfZ7Ukc9ylr9f9J8c1PO3NGZtryUavw/109BrYfdQGgO0Hfkc/yVsRbkI4mKYNlvnXg==", "dev": true, "dependencies": { "@stoplight/json": "~3.20.1", @@ -1800,9 +1830,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.40.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz", - "integrity": "sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-gsF+c/0XOguWgaOgvFs+xnnRqt9GwgTvIks36WpE6ueeI4KCEHHd8K/CKHqhOqrJKsYH8m27kRzQEvWXAwXUTw==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -1818,9 +1848,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, "node_modules/@types/json-schema": { "version": "7.0.12", @@ -1858,9 +1888,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", - "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==" + "version": "20.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.1.tgz", + "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -1901,13 +1931,13 @@ } }, "node_modules/@vitest/expect": { - "version": "0.32.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.32.2.tgz", - "integrity": "sha512-6q5yzweLnyEv5Zz1fqK5u5E83LU+gOMVBDuxBl2d2Jfx1BAp5M+rZgc5mlyqdnxquyoiOXpXmFNkcGcfFnFH3Q==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz", + "integrity": "sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==", "dev": true, "dependencies": { - "@vitest/spy": "0.32.2", - "@vitest/utils": "0.32.2", + "@vitest/spy": "0.33.0", + "@vitest/utils": "0.33.0", "chai": "^4.3.7" }, "funding": { @@ -1915,15 +1945,14 @@ } }, "node_modules/@vitest/runner": { - "version": "0.32.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.32.2.tgz", - "integrity": "sha512-06vEL0C1pomOEktGoLjzZw+1Fb+7RBRhmw/06WkDrd1akkT9i12su0ku+R/0QM69dfkIL/rAIDTG+CSuQVDcKw==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.33.0.tgz", + "integrity": "sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==", "dev": true, "dependencies": { - "@vitest/utils": "0.32.2", - "concordance": "^5.0.4", + "@vitest/utils": "0.33.0", "p-limit": "^4.0.0", - "pathe": "^1.1.0" + "pathe": "^1.1.1" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1957,52 +1986,58 @@ } }, "node_modules/@vitest/snapshot": { - "version": "0.32.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.32.2.tgz", - "integrity": "sha512-JwhpeH/PPc7GJX38vEfCy9LtRzf9F4er7i4OsAJyV7sjPwjj+AIR8cUgpMTWK4S3TiamzopcTyLsZDMuldoi5A==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.33.0.tgz", + "integrity": "sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==", "dev": true, "dependencies": { - "magic-string": "^0.30.0", - "pathe": "^1.1.0", - "pretty-format": "^27.5.1" + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" } }, "node_modules/@vitest/spy": { - "version": "0.32.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.32.2.tgz", - "integrity": "sha512-Q/ZNILJ4ca/VzQbRM8ur3Si5Sardsh1HofatG9wsJY1RfEaw0XKP8IVax2lI1qnrk9YPuG9LA2LkZ0EI/3d4ug==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz", + "integrity": "sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==", "dev": true, "dependencies": { - "tinyspy": "^2.1.0" + "tinyspy": "^2.1.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "0.32.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.32.2.tgz", - "integrity": "sha512-lnJ0T5i03j0IJaeW73hxe2AuVnZ/y1BhhCOuIcl9LIzXnbpXJT9Lrt6brwKHXLOiA7MZ6N5hSJjt0xE1dGNCzQ==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.33.0.tgz", + "integrity": "sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==", "dev": true, "dependencies": { "diff-sequences": "^29.4.3", "loupe": "^2.3.6", - "pretty-format": "^27.5.1" + "pretty-format": "^29.5.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2045,12 +2080,17 @@ "source-map-js": "^1.0.2" } }, + "node_modules/@vue/compiler-sfc/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, "node_modules/@vue/compiler-sfc/node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -2085,12 +2125,17 @@ "magic-string": "^0.30.0" } }, + "node_modules/@vue/reactivity-transform/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, "node_modules/@vue/reactivity-transform/node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -2338,9 +2383,9 @@ } }, "node_modules/acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -2604,9 +2649,9 @@ } }, "node_modules/asciinema-player": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.4.0.tgz", - "integrity": "sha512-dX6jt5S3K6daItsVWzyY9mRDK+ivC2QgqCxFkdSiNslo0vY/ZqA4upcTzqIKZqBtxppovOZk44ltg9VnHG9QVg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.5.0.tgz", + "integrity": "sha512-o4B2AscBuCZo4+JB9TBGrfZ7GQL99wsbm08WwmuNJTPd1lyLQJq8wgacnBsdvb2sC0K875ScYr8T5XmfeH/6dg==", "dependencies": { "@babel/runtime": "^7.21.0", "solid-js": "^1.3.0" @@ -2701,12 +2746,6 @@ "node": "*" } }, - "node_modules/blueimp-md5": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2876,9 +2915,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001504", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz", - "integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==", + "version": "1.0.30001513", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001513.tgz", + "integrity": "sha512-pnjGJo7SOOjAGytZZ203Em95MRM8Cr6jhCXNF/FAXTpCTRTECnqQWLpiTRqrFtdYcth8hf4WECUpkezuYsMVww==", "funding": [ { "type": "opencollective", @@ -3122,25 +3161,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", - "dev": true, - "dependencies": { - "date-time": "^3.1.0", - "esutils": "^2.0.3", - "fast-diff": "^1.2.0", - "js-string-escape": "^1.0.1", - "lodash": "^4.17.15", - "md5-hex": "^3.0.1", - "semver": "^7.3.2", - "well-known-symbols": "^2.0.0" - }, - "engines": { - "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3811,22 +3831,10 @@ "node": ">=14" } }, - "node_modules/date-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", - "dev": true, - "dependencies": { - "time-zone": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/dayjs": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz", - "integrity": "sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==" + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, "node_modules/debug": { "version": "4.3.4", @@ -4009,6 +4017,17 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", @@ -4161,9 +4180,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.433", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz", - "integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==" + "version": "1.4.454", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz", + "integrity": "sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==" }, "node_modules/elkjs": { "version": "0.8.2", @@ -4204,10 +4223,22 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", "bin": { "envinfo": "dist/cli.js" }, @@ -4502,15 +4533,15 @@ } }, "node_modules/eslint": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", - "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.43.0", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -4522,7 +4553,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "espree": "^9.6.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -4542,7 +4573,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -4806,9 +4837,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz", - "integrity": "sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==", + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz", + "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.3.0", @@ -4924,12 +4955,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -5020,16 +5051,10 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -5364,9 +5389,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz", - "integrity": "sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz", + "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -5586,9 +5611,9 @@ "dev": true }, "node_modules/gsap": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.1.tgz", - "integrity": "sha512-FXtb2YbBE9l8I9Pl5DFLpCMedaiMPztRlr0Ln0CMSnJn+pbTaeKlzgth8cLNPc7PzNwIZe+SEQiBBAWaBKJdVA==" + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.2.tgz", + "integrity": "sha512-EkYnpG8qHgYBFAwsgsGEqvT1WUidX0tt/ijepx7z8EUJHElykg91RvW1XbkT59T0gZzzszOpjQv7SE41XuIXyQ==" }, "node_modules/hard-rejection": { "version": "2.1.0", @@ -6400,15 +6425,6 @@ "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", "dev": true }, - "node_modules/js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/js-tokens": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.1.tgz", @@ -6576,9 +6592,9 @@ "integrity": "sha512-b+z6yF1d4EOyDgylzQo5IminlUmzSeqR1hs/bzjBNjuGras4FXq/6TrzjxfN0j+TmI0ltJzTNlqXUMCniciwKQ==" }, "node_modules/katex": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", - "integrity": "sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -6704,70 +6720,268 @@ "node": ">=8" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", - "dev": true, + "node_modules/lightningcss": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.21.5.tgz", + "integrity": "sha512-/pEUPeih2EwIx9n4T82aOG6CInN83tl/mWlw6B5gWLf36UplQi1L+5p3FUHsdt4fXVfOkkh9KIaM3owoq7ss8A==", "dependencies": { - "uc.micro": "^1.0.1" + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.21.5", + "lightningcss-darwin-x64": "1.21.5", + "lightningcss-linux-arm-gnueabihf": "1.21.5", + "lightningcss-linux-arm64-gnu": "1.21.5", + "lightningcss-linux-arm64-musl": "1.21.5", + "lightningcss-linux-x64-gnu": "1.21.5", + "lightningcss-linux-x64-musl": "1.21.5", + "lightningcss-win32-x64-msvc": "1.21.5" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.21.5.tgz", + "integrity": "sha512-z05hyLX85WY0UfhkFUOrWEFqD69lpVAmgl3aDzMKlIZJGygbhbegqb4PV8qfUrKKNBauut/qVNPKZglhTaDDxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.21.5.tgz", + "integrity": "sha512-MSJhmej/U9MrdPxDk7+FWhO8+UqVoZUHG4VvKT5RQ4RJtqtANTiWiI97LvoVNMtdMnHaKs1Pkji6wHUFxjJsHQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.11.5" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.21.5.tgz", + "integrity": "sha512-xN6+5/JsMrbZHL1lPl+MiNJ3Xza12ueBKPepiyDCFQzlhFRTj7D0LG+cfNTzPBTO8KcYQynLpl1iBB8LGp3Xtw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.21.5.tgz", + "integrity": "sha512-KfzFNhC4XTbmG3ma/xcTs/IhCwieW89XALIusKmnV0N618ZDXEB0XjWOYQRCXeK9mfqPdbTBpurEHV/XZtkniQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.9.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", - "dev": true, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.21.5.tgz", + "integrity": "sha512-bc0GytQO5Mn9QM6szaZ+31fQHNdidgpM1sSCwzPItz8hg3wOvKl8039rU0veMJV3ZgC9z0ypNRceLrSHeRHmXw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.21.5.tgz", + "integrity": "sha512-JwMbgypPQgc2kW2av3OwzZ8cbrEuIiDiXPJdXRE6aVxu67yHauJawQLqJKTGUhiAhy6iLDG8Wg0a3/ziL+m+Kw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.21.5.tgz", + "integrity": "sha512-Ib8b6IQ/OR/VrPU6YBgy4T3QnuHY7DUa95O+nz+cwrTkMSN6fuHcTcIaz4t8TJ6HI5pl3uxUOZjmtls2pyQWow==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/lodash": { + "node_modules/lightningcss-loader": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lightningcss-loader/-/lightningcss-loader-2.1.0.tgz", + "integrity": "sha512-mB+M/lvs/GdXT4yc8ZiNgLUAbYpPI9grDyC3ybz/Zo6s4GZv53iZnLTnkJT/Qm3Sh89dbFUm+omoHFXCfZtcXw==", + "dependencies": { + "browserslist": "^4.21.4", + "lightningcss": "^1.16.0", + "webpack-sources": "^3.2.3" + }, + "peerDependencies": { + "webpack": ">=5" + } + }, + "node_modules/lightningcss-loader/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==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.21.5.tgz", + "integrity": "sha512-A8cSi8lUpBeVmoF+DqqW7cd0FemDbCuKr490IXdjyeI+KL8adpSKUs8tcqO0OXPh1EoDqK7JNkD/dELmd4Iz5g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" @@ -6881,18 +7095,6 @@ "markdown-it": "bin/markdown-it.js" } }, - "node_modules/markdown-it/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/markdownlint": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.29.0.tgz", @@ -6996,18 +7198,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/md5-hex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", - "dev": true, - "dependencies": { - "blueimp-md5": "^2.10.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/mdast-util-from-markdown": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", @@ -7152,9 +7342,9 @@ } }, "node_modules/mermaid": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.2.3.tgz", - "integrity": "sha512-cMVE5s9PlQvOwfORkyVpr5beMsLdInrycAosdr+tpZ0WFjG4RJ/bUHST7aTgHNJbujHkdBRAm+N50P3puQOfPw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.2.4.tgz", + "integrity": "sha512-zHGjEI7lBvWZX+PQYmlhSA2p40OzW6QbGodTCSzDeVpqaTnyAC+2sRGqrpXO+uQk3CnoeClHQPraQUMStdqy2g==", "dependencies": { "@braintree/sanitize-url": "^6.0.2", "cytoscape": "^3.23.0", @@ -7655,9 +7845,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -7701,21 +7891,21 @@ } }, "node_modules/mlly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.3.0.tgz", - "integrity": "sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz", + "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==", "dev": true, "dependencies": { - "acorn": "^8.8.2", - "pathe": "^1.1.0", + "acorn": "^8.9.0", + "pathe": "^1.1.1", "pkg-types": "^1.0.3", "ufo": "^1.1.2" } }, "node_modules/monaco-editor": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.39.0.tgz", - "integrity": "sha512-zhbZ2Nx93tLR8aJmL2zI1mhJpsl87HMebNBM6R8z4pLfs8pj604pIVIVwyF1TivcfNtIPpMXL+nb3DsBmE/x6Q==" + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.40.0.tgz", + "integrity": "sha512-1wymccLEuFSMBvCk/jT1YDW/GuxMLYwnFwF9CDyYCxoTw2Pt379J3FUhwy9c43j51JdcxVPjwk0jm0EVDsBS2g==" }, "node_modules/monaco-editor-webpack-plugin": { "version": "7.0.1", @@ -7814,9 +8004,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -7852,9 +8042,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, "node_modules/non-layered-tidy-tree-layout": { "version": "2.0.2", @@ -7904,9 +8094,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.5.tgz", - "integrity": "sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", "dev": true }, "node_modules/obj-props": { @@ -7988,17 +8178,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -8194,13 +8384,13 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz", - "integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1", - "minipass": "^5.0.0 || ^6.0.2" + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -8210,9 +8400,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", - "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz", + "integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -8370,9 +8560,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", + "integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==", "funding": [ { "type": "opencollective", @@ -8521,17 +8711,17 @@ } }, "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.0", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -8707,9 +8897,9 @@ } }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, "node_modules/read-pkg": { @@ -9062,14 +9252,14 @@ "dev": true }, "node_modules/run-con": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.11.tgz", - "integrity": "sha512-NEMGsUT+cglWkzEr4IFK21P4Jca45HqiAbIIZIBdX5+UZTB24Mb/21iNGgz9xZa8tL6vbW7CXmq7MFN42+VjNQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.12.tgz", + "integrity": "sha512-5257ILMYIF4RztL9uoZ7V9Q97zHtNHn5bN3NobeAnzB1P3ASLgg8qocM2u+R18ttp+VEM78N2LK8XcNVtnSRrg==", "dev": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~3.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "strip-json-comments": "~3.1.1" }, "bin": { @@ -9209,9 +9399,9 @@ } }, "node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -9414,9 +9604,9 @@ "dev": true }, "node_modules/solid-js": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.6.tgz", - "integrity": "sha512-DXVOTjUh/bIAhE0fIqu3ezGLyQaez7v8EOw3uPLIi87DmLjg+hsuCAgKyNIZ+o4jUetOk3ZORccvJmE1yZUk8g==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.7.tgz", + "integrity": "sha512-SPdYVke/Z6Za24PBTbULyQYPrhGO1ZbPany76atO2zF2dmYn2pCotbsw1JtlgWnr9dK2JbwPGnA3ODTGPLhZNw==", "dependencies": { "csstype": "^3.1.0", "seroval": "^0.5.0" @@ -9712,9 +9902,9 @@ "dev": true }, "node_modules/stylelint": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.8.0.tgz", - "integrity": "sha512-x9qBk84F3MEjMEUNCE7MtWmfj9G9y5XzJ0cpQeJdy2l/IoqjC8Ih0N0ytmOTnXE4Yv0J7I1cmVRQUVNSPCxTsA==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.9.0.tgz", + "integrity": "sha512-sXtAZi64CllWr6A+8ymDWnlIaYwuAa7XRmGnJxLQXFNnLjd3Izm4HAD+loKVaZ7cpK6SLxhAUX1lwPJKGCn0mg==", "dev": true, "dependencies": { "@csstools/css-parser-algorithms": "^2.2.0", @@ -9826,9 +10016,9 @@ } }, "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" }, "node_modules/superstruct": { "version": "0.10.13", @@ -9910,9 +10100,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.0.0.tgz", - "integrity": "sha512-bwl6og9I9CAHKGSnYLKydjhBuH7d3oU6RX6uKN8oDCkLusTHXOW3sZMyBWjRtjGFnCMmN085oZoaR/4Wm9nIaQ==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.1.0.tgz", + "integrity": "sha512-c1KmAjuVODxw+vwkNLALQZrgdlBAuBbr2xSPfYrJgseEi7gFKcTvShysPmyuDI4kcUa1+5rFpjWvXdusKY74mg==" }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -9957,9 +10147,9 @@ } }, "node_modules/terser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz", - "integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz", + "integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -10070,25 +10260,21 @@ "node": ">=12.22" } }, - "node_modules/time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/tinybench": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz", "integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==", "dev": true }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "node_modules/tinypool": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz", - "integrity": "sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.6.0.tgz", + "integrity": "sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -10122,6 +10308,11 @@ "node": ">=8.0" } }, + "node_modules/toastify-js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz", + "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -10214,9 +10405,9 @@ } }, "node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", "dev": true }, "node_modules/type-check": { @@ -10363,9 +10554,9 @@ } }, "node_modules/updates": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/updates/-/updates-14.2.4.tgz", - "integrity": "sha512-r54h4Q12lUAmQ9dENy7BnY22AnTfW4YGEZw73gv6RvNEWgcZ3qS88jPLc1ckPAzt/8TPKWwLkSVpbEpgGwglJw==", + "version": "14.3.2", + "resolved": "https://registry.npmjs.org/updates/-/updates-14.3.2.tgz", + "integrity": "sha512-cgEaWadntOSW8aFG6b956U3TVrMbRzZnF+iHjJhLEGvyV36v7cZ0RVFW3eZ4iLYyWZ2E7/aV3DrA/W6rLwpS1g==", "dev": true, "bin": { "updates": "bin/updates.js" @@ -10457,14 +10648,14 @@ } }, "node_modules/vite": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", - "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.2.tgz", + "integrity": "sha512-zUcsJN+UvdSyHhYa277UHhiJ3iq4hUBwHavOpsNUGsTgjBeoBlK8eDt+iT09pBq0h9/knhG/SPrZiM7cGmg7NA==", "dev": true, "dependencies": { - "esbuild": "^0.17.5", - "postcss": "^8.4.23", - "rollup": "^3.21.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.24", + "rollup": "^3.25.2" }, "bin": { "vite": "bin/vite.js" @@ -10472,12 +10663,16 @@ "engines": { "node": "^14.18.0 || >=16.0.0" }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", + "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", @@ -10490,6 +10685,9 @@ "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, @@ -10505,15 +10703,15 @@ } }, "node_modules/vite-node": { - "version": "0.32.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.32.2.tgz", - "integrity": "sha512-dTQ1DCLwl2aEseov7cfQ+kDMNJpM1ebpyMMMwWzBvLbis8Nla/6c9WQcqpPssTwS6Rp/+U6KwlIj8Eapw4bLdA==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.33.0.tgz", + "integrity": "sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==", "dev": true, "dependencies": { "cac": "^6.7.14", "debug": "^4.3.4", - "mlly": "^1.2.0", - "pathe": "^1.1.0", + "mlly": "^1.4.0", + "pathe": "^1.1.1", "picocolors": "^1.0.0", "vite": "^3.0.0 || ^4.0.0" }, @@ -10527,10 +10725,408 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-string-plugin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.1.0.tgz", + "integrity": "sha512-RMxstCLl6Zyyz4tkzzHDFrrVvom1GAxjo2lTmgfus5CZYOnFNcJ2iQiDaFpc4KAOHSNkBNtkj5YauSs+f1gqCA==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.11.tgz", + "integrity": "sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz", + "integrity": "sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.11.tgz", + "integrity": "sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz", + "integrity": "sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz", + "integrity": "sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz", + "integrity": "sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz", + "integrity": "sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz", + "integrity": "sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz", + "integrity": "sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz", + "integrity": "sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz", + "integrity": "sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz", + "integrity": "sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz", + "integrity": "sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz", + "integrity": "sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz", + "integrity": "sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz", + "integrity": "sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz", + "integrity": "sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz", + "integrity": "sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz", + "integrity": "sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz", + "integrity": "sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz", + "integrity": "sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz", + "integrity": "sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.11.tgz", + "integrity": "sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.11", + "@esbuild/android-arm64": "0.18.11", + "@esbuild/android-x64": "0.18.11", + "@esbuild/darwin-arm64": "0.18.11", + "@esbuild/darwin-x64": "0.18.11", + "@esbuild/freebsd-arm64": "0.18.11", + "@esbuild/freebsd-x64": "0.18.11", + "@esbuild/linux-arm": "0.18.11", + "@esbuild/linux-arm64": "0.18.11", + "@esbuild/linux-ia32": "0.18.11", + "@esbuild/linux-loong64": "0.18.11", + "@esbuild/linux-mips64el": "0.18.11", + "@esbuild/linux-ppc64": "0.18.11", + "@esbuild/linux-riscv64": "0.18.11", + "@esbuild/linux-s390x": "0.18.11", + "@esbuild/linux-x64": "0.18.11", + "@esbuild/netbsd-x64": "0.18.11", + "@esbuild/openbsd-x64": "0.18.11", + "@esbuild/sunos-x64": "0.18.11", + "@esbuild/win32-arm64": "0.18.11", + "@esbuild/win32-ia32": "0.18.11", + "@esbuild/win32-x64": "0.18.11" + } + }, "node_modules/vite/node_modules/rollup": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", - "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==", + "version": "3.26.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz", + "integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -10544,35 +11140,34 @@ } }, "node_modules/vitest": { - "version": "0.32.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.32.2.tgz", - "integrity": "sha512-hU8GNNuQfwuQmqTLfiKcqEhZY72Zxb7nnN07koCUNmntNxbKQnVbeIS6sqUgR3eXSlbOpit8+/gr1KpqoMgWCQ==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.33.0.tgz", + "integrity": "sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==", "dev": true, "dependencies": { "@types/chai": "^4.3.5", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "@vitest/expect": "0.32.2", - "@vitest/runner": "0.32.2", - "@vitest/snapshot": "0.32.2", - "@vitest/spy": "0.32.2", - "@vitest/utils": "0.32.2", - "acorn": "^8.8.2", + "@vitest/expect": "0.33.0", + "@vitest/runner": "0.33.0", + "@vitest/snapshot": "0.33.0", + "@vitest/spy": "0.33.0", + "@vitest/utils": "0.33.0", + "acorn": "^8.9.0", "acorn-walk": "^8.2.0", "cac": "^6.7.14", "chai": "^4.3.7", - "concordance": "^5.0.4", "debug": "^4.3.4", "local-pkg": "^0.4.3", - "magic-string": "^0.30.0", - "pathe": "^1.1.0", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", "picocolors": "^1.0.0", - "std-env": "^3.3.2", + "std-env": "^3.3.3", "strip-literal": "^1.0.1", "tinybench": "^2.5.0", - "tinypool": "^0.5.0", + "tinypool": "^0.6.0", "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.32.2", + "vite-node": "0.33.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -10621,13 +11216,19 @@ } } }, + "node_modules/vitest/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, "node_modules/vitest/node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -10766,9 +11367,9 @@ } }, "node_modules/webpack": { - "version": "5.87.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.87.0.tgz", - "integrity": "sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==", + "version": "5.88.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -10884,6 +11485,11 @@ "source-map": "~0.6.1" } }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" + }, "node_modules/webpack/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -10957,15 +11563,6 @@ "node": ">=10.13.0" } }, - "node_modules/well-known-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", diff --git a/package.json b/package.json index fd68c1d341863..10956595b6891 100644 --- a/package.json +++ b/package.json @@ -17,46 +17,48 @@ "@webcomponents/custom-elements": "1.6.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "5.2.1", - "asciinema-player": "3.4.0", + "asciinema-player": "3.5.0", "clippie": "4.0.1", "css-loader": "6.8.1", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", "esbuild-loader": "3.0.1", "escape-goat": "4.0.0", - "fast-glob": "3.2.12", + "fast-glob": "3.3.0", "jquery": "3.7.0", "jquery.are-you-sure": "1.9.0", - "katex": "0.16.7", + "katex": "0.16.8", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "10.2.3", + "lightningcss-loader": "2.1.0", + "mermaid": "10.2.4", "mini-css-extract-plugin": "2.7.6", - "minimatch": "9.0.1", - "monaco-editor": "0.39.0", + "minimatch": "9.0.3", + "monaco-editor": "0.40.0", "monaco-editor-webpack-plugin": "7.0.1", "pdfobject": "2.2.12", "pretty-ms": "8.0.0", "sortablejs": "1.15.0", - "swagger-ui-dist": "5.0.0", + "swagger-ui-dist": "5.1.0", "throttle-debounce": "5.0.0", + "tinycolor2": "1.6.0", "tippy.js": "6.3.7", + "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "3.3.4", "vue-bar-graph": "2.0.0", "vue-loader": "17.2.2", "vue3-calendar-heatmap": "2.0.5", - "webpack": "5.87.0", + "webpack": "5.88.1", "webpack-cli": "5.1.4", "wrap-ansi": "8.1.0" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "3.2.1", "@playwright/test": "1.35.1", - "@rollup/pluginutils": "5.0.2", "@stoplight/spectral-cli": "6.8.0", "@vitejs/plugin-vue": "4.2.3", - "eslint": "8.43.0", + "eslint": "8.44.0", "eslint-plugin-array-func": "3.1.8", "eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-import": "2.27.5", @@ -66,18 +68,19 @@ "eslint-plugin-regexp": "1.15.0", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "47.0.0", - "eslint-plugin-vue": "9.14.1", + "eslint-plugin-vue": "9.15.1", "eslint-plugin-wc": "1.5.0", "jsdom": "22.1.0", "markdownlint-cli": "0.35.0", "postcss-html": "1.5.0", - "stylelint": "15.8.0", + "stylelint": "15.9.0", "stylelint-declaration-block-no-ignored-properties": "2.7.0", "stylelint-declaration-strict-value": "1.9.2", "stylelint-stylistic": "0.4.2", "svgo": "3.0.2", - "updates": "14.2.4", - "vitest": "0.32.2" + "updates": "14.3.2", + "vite-string-plugin": "1.1.0", + "vitest": "0.33.0" }, "browserslist": [ "defaults", diff --git a/poetry.lock b/poetry.lock index 69258f749c99f..7d106e15519ea 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,13 +42,13 @@ six = ">=1.13.0" [[package]] name = "djlint" -version = "1.31.0" +version = "1.31.1" description = "HTML Template Linter and Formatter" optional = false python-versions = ">=3.8.0,<4.0.0" files = [ - {file = "djlint-1.31.0-py3-none-any.whl", hash = "sha256:2b9200c67103b79835b7547ff732e910888d1f0ef684f5b329eb64b14d09c046"}, - {file = "djlint-1.31.0.tar.gz", hash = "sha256:8acb4b751b429c5aabb1aef5b6007bdf53224eceda25c5fbe04c42cc57c0a7ba"}, + {file = "djlint-1.31.1-py3-none-any.whl", hash = "sha256:9b2e2fc3a059a8e5a62f309edea15c1aeee331a279ab2699b9fb51a31d8c0934"}, + {file = "djlint-1.31.1.tar.gz", hash = "sha256:a11739e2f919f760b3986eb13d06e00171f3bd342b8d88e9bd914a4260eaa8ce"}, ] [package.dependencies] @@ -328,4 +328,4 @@ telegram = ["requests"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "22c4af11eadd8784b613951d6160d67be0f33500238a450741c3d75beb218dad" +content-hash = "f03ad8e7c4f6e797ac3c04630db8cc16438cd59642653c26fd401633cd62d696" diff --git a/pyproject.toml b/pyproject.toml index ce5f475b272fa..7a30f5914066e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [] python = "^3.8" [tool.poetry.group.dev.dependencies] -djlint = "1.31.0" +djlint = "1.31.1" [tool.djlint] profile="golang" diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go index ab70f622b39b7..e95df7a00fa80 100644 --- a/routers/api/actions/runner/utils.go +++ b/routers/api/actions/runner/utils.go @@ -116,7 +116,7 @@ func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map } func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { - event := map[string]interface{}{} + event := map[string]any{} _ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event) // TriggerEvent is added in https://github.com/go-gitea/gitea/pull/25229 @@ -129,14 +129,24 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { baseRef := "" headRef := "" + ref := t.Job.Run.Ref + sha := t.Job.Run.CommitSHA if pullPayload, err := t.Job.Run.GetPullRequestEventPayload(); err == nil && pullPayload.PullRequest != nil && pullPayload.PullRequest.Base != nil && pullPayload.PullRequest.Head != nil { baseRef = pullPayload.PullRequest.Base.Ref headRef = pullPayload.PullRequest.Head.Ref + + // if the TriggerEvent is pull_request_target, ref and sha need to be set according to the base of pull request + // In GitHub's documentation, ref should be the branch or tag that triggered workflow. But when the TriggerEvent is pull_request_target, + // the ref will be the base branch. + if t.Job.Run.TriggerEvent == actions_module.GithubEventPullRequestTarget { + ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name + sha = pullPayload.PullRequest.Base.Sha + } } - refName := git.RefName(t.Job.Run.Ref) + refName := git.RefName(ref) - taskContext, err := structpb.NewStruct(map[string]interface{}{ + taskContext, err := structpb.NewStruct(map[string]any{ // 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. @@ -153,7 +163,7 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { "graphql_url": "", // string, The URL of the GitHub GraphQL API. "head_ref": headRef, // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. "job": fmt.Sprint(t.JobID), // string, The job_id of the current job. - "ref": t.Job.Run.Ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1. + "ref": ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1. "ref_name": refName.ShortName(), // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. "ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run. "ref_type": refName.RefType(), // string, The type of ref that triggered the workflow run. Valid values are branch or tag. @@ -167,14 +177,14 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { "run_attempt": fmt.Sprint(t.Job.Attempt), // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. "secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces. "server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com. - "sha": t.Job.Run.CommitSHA, // string, The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53. + "sha": sha, // string, The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53. "token": t.Token, // string, A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the GITHUB_TOKEN secret. For more information, see "Automatic token authentication." "triggering_actor": "", // string, The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. "workflow": t.Job.Run.WorkflowID, // string, The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository. "workspace": "", // string, The default working directory on the runner for steps, and the default location of your repository when using the checkout action. // additional contexts - "gitea_default_actions_url": setting.Actions.DefaultActionsURL, + "gitea_default_actions_url": setting.Actions.DefaultActionsURL.URL(), }) if err != nil { log.Error("structpb.NewStruct failed: %v", err) diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go index 9a551a219b64b..51a5c784e0280 100644 --- a/routers/api/packages/alpine/alpine.go +++ b/routers/api/packages/alpine/alpine.go @@ -24,7 +24,7 @@ import ( alpine_service "code.gitea.io/gitea/services/packages/alpine" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -68,7 +68,7 @@ func GetRepositoryFile(ctx *context.Context) { return } - s, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( ctx, pv, &packages_service.PackageFileInfo{ @@ -84,12 +84,8 @@ func GetRepositoryFile(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } func UploadPackageFile(ctx *context.Context) { @@ -200,7 +196,7 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) @@ -209,12 +205,8 @@ func DownloadPackageFile(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } func DeletePackageFile(ctx *context.Context) { diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go index b666bdde6c9f2..a2e835df578b4 100644 --- a/routers/api/packages/cargo/cargo.go +++ b/routers/api/packages/cargo/cargo.go @@ -33,7 +33,7 @@ type StatusMessage struct { Message string `json:"detail"` } -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, StatusResponse{ OK: false, @@ -165,7 +165,7 @@ func ListOwners(ctx *context.Context) { // DownloadPackageFile serves the content of a package func DownloadPackageFile(ctx *context.Context) { - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -185,12 +185,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // https://doc.rust-lang.org/cargo/reference/registries.html#publish diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go index b48b1778c42d1..908f9fc4be1b2 100644 --- a/routers/api/packages/chef/chef.go +++ b/routers/api/packages/chef/chef.go @@ -24,7 +24,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { type Error struct { ErrorMessages []string `json:"error_messages"` } @@ -341,17 +341,13 @@ func DownloadPackage(ctx *context.Context) { pf := pd.Files[0].File - s, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // https://github.com/chef/chef/blob/main/knife/lib/chef/knife/supermarket_unshare.rb diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index d93b11efdf66d..bf5bda743f560 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -26,7 +26,7 @@ import ( "github.com/hashicorp/go-version" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { type Error struct { Status int `json:"status"` @@ -162,7 +162,7 @@ func PackageMetadata(ctx *context.Context) { // DownloadPackageFile serves the content of a package func DownloadPackageFile(ctx *context.Context) { - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -182,12 +182,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // UploadPackage creates a new package diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go index caeb8c11bc4c8..6e1727fd76471 100644 --- a/routers/api/packages/conan/conan.go +++ b/routers/api/packages/conan/conan.go @@ -47,7 +47,7 @@ var ( ) ) -func jsonResponse(ctx *context.Context, status int, obj interface{}) { +func jsonResponse(ctx *context.Context, status int, obj any) { // https://github.com/conan-io/conan/issues/6613 ctx.Resp.Header().Set("Content-Type", "application/json") ctx.Status(status) @@ -56,7 +56,7 @@ func jsonResponse(ctx *context.Context, status int, obj interface{}) { } } -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { jsonResponse(ctx, status, map[string]string{ "message": message, @@ -453,7 +453,7 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe return } - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -474,12 +474,8 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // DeleteRecipeV1 deletes the requested recipe(s) @@ -800,13 +796,13 @@ func listRevisionFiles(ctx *context.Context, fileKey string) { return } - files := make(map[string]interface{}) + files := make(map[string]any) for _, pf := range pfs { files[pf.Name] = nil } type FileList struct { - Files map[string]interface{} `json:"files"` + Files map[string]any `json:"files"` } jsonResponse(ctx, http.StatusOK, &FileList{ diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go index f7786906309b1..0bf0fc1f625b3 100644 --- a/routers/api/packages/conda/conda.go +++ b/routers/api/packages/conda/conda.go @@ -24,7 +24,7 @@ import ( "github.com/dsnet/compress/bzip2" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, struct { Reason string `json:"reason"` @@ -292,15 +292,11 @@ func DownloadPackageFile(ctx *context.Context) { pf := pfs[0] - s, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index eba5f14e5db1b..8f79805cc8b36 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -75,7 +75,7 @@ func setResponseHeaders(resp http.ResponseWriter, h *containerHeaders) { resp.WriteHeader(h.Status) } -func jsonResponse(ctx *context.Context, status int, obj interface{}) { +func jsonResponse(ctx *context.Context, status int, obj any) { setResponseHeaders(ctx.Resp, &containerHeaders{ Status: status, ContentType: "application/json", @@ -490,22 +490,7 @@ func GetBlob(ctx *context.Context) { return } - s, _, err := packages_service.GetPackageFileStream(ctx, blob.File) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - defer s.Close() - - setResponseHeaders(ctx.Resp, &containerHeaders{ - ContentDigest: blob.Properties.GetByName(container_module.PropertyDigest), - ContentType: blob.Properties.GetByName(container_module.PropertyMediaType), - ContentLength: blob.Blob.Size, - Status: http.StatusOK, - }) - if _, err := io.Copy(ctx.Resp, s); err != nil { - log.Error("Error whilst copying content to response: %v", err) - } + serveBlob(ctx, blob) } // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-blobs @@ -644,22 +629,7 @@ func GetManifest(ctx *context.Context) { return } - s, _, err := packages_service.GetPackageFileStream(ctx, manifest.File) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - defer s.Close() - - setResponseHeaders(ctx.Resp, &containerHeaders{ - ContentDigest: manifest.Properties.GetByName(container_module.PropertyDigest), - ContentType: manifest.Properties.GetByName(container_module.PropertyMediaType), - ContentLength: manifest.Blob.Size, - Status: http.StatusOK, - }) - if _, err := io.Copy(ctx.Resp, s); err != nil { - log.Error("Error whilst copying content to response: %v", err) - } + serveBlob(ctx, manifest) } // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#deleting-tags @@ -694,6 +664,36 @@ func DeleteManifest(ctx *context.Context) { }) } +func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) { + s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + headers := &containerHeaders{ + ContentDigest: pfd.Properties.GetByName(container_module.PropertyDigest), + ContentType: pfd.Properties.GetByName(container_module.PropertyMediaType), + ContentLength: pfd.Blob.Size, + Status: http.StatusOK, + } + + if u != nil { + headers.Status = http.StatusTemporaryRedirect + headers.Location = u.String() + + setResponseHeaders(ctx.Resp, headers) + return + } + + defer s.Close() + + setResponseHeaders(ctx.Resp, headers) + if _, err := io.Copy(ctx.Resp, s); err != nil { + log.Error("Error whilst copying content to response: %v", err) + } +} + // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery func GetTagList(ctx *context.Context) { image := ctx.Params("image") diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go index eb3f9a452b1ac..0ef6eff88d65f 100644 --- a/routers/api/packages/cran/cran.go +++ b/routers/api/packages/cran/cran.go @@ -21,7 +21,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -249,7 +249,7 @@ func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) { return } - s, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) @@ -258,10 +258,6 @@ func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index cfc03ae522a6f..f7270e0ae0ea6 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -23,7 +23,7 @@ import ( debian_service "code.gitea.io/gitea/services/packages/debian" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -59,7 +59,7 @@ func GetRepositoryFile(ctx *context.Context) { key += "|" + component + "|" + architecture } - s, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( ctx, pv, &packages_service.PackageFileInfo{ @@ -75,12 +75,8 @@ func GetRepositoryFile(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29 @@ -110,7 +106,7 @@ func GetRepositoryFileByHash(ctx *context.Context) { return } - s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) @@ -119,12 +115,8 @@ func GetRepositoryFileByHash(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } func UploadPackageFile(ctx *context.Context) { @@ -217,7 +209,7 @@ func DownloadPackageFile(ctx *context.Context) { name := ctx.Params("name") version := ctx.Params("version") - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -238,9 +230,8 @@ func DownloadPackageFile(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ + helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{ ContentType: "application/vnd.debian.binary-package", Filename: pf.Name, LastModified: pf.CreatedUnix.AsLocalTime(), diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index 0c873119ef54d..c5866ef9c354b 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -22,7 +22,7 @@ var ( filenameRegex = packageNameRegex ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -30,7 +30,7 @@ func apiError(ctx *context.Context, status int, obj interface{}) { // DownloadPackageFile serves the specific generic package. func DownloadPackageFile(ctx *context.Context) { - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -50,12 +50,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // UploadPackage uploads the specific generic package. diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go index d0bc9c1e98c37..bacdc4ec62f47 100644 --- a/routers/api/packages/goproxy/goproxy.go +++ b/routers/api/packages/goproxy/goproxy.go @@ -20,7 +20,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -105,7 +105,7 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, _, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, _, err := packages_service.GetPackageFileStream(ctx, pfs[0]) if err != nil { if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) @@ -114,12 +114,8 @@ func DownloadPackageFile(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pfs[0].Name, - LastModified: pfs[0].CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pfs[0]) } func resolvePackage(ctx *context.Context, ownerID int64, name, version string) (*packages_model.PackageVersion, error) { diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go index b7edc8b7fef8a..9097adf29e7bf 100644 --- a/routers/api/packages/helm/helm.go +++ b/routers/api/packages/helm/helm.go @@ -26,7 +26,7 @@ import ( "gopkg.in/yaml.v3" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { type Error struct { Error string `json:"error"` @@ -121,7 +121,7 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( ctx, pvs[0], &packages_service.PackageFileInfo{ @@ -136,12 +136,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // UploadPackage creates a new package diff --git a/routers/api/packages/helper/helper.go b/routers/api/packages/helper/helper.go index 660aaec1a3a5f..aadb10376c81e 100644 --- a/routers/api/packages/helper/helper.go +++ b/routers/api/packages/helper/helper.go @@ -5,8 +5,11 @@ package helper import ( "fmt" + "io" "net/http" + "net/url" + packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -14,7 +17,7 @@ import ( // LogAndProcessError logs an error and calls a custom callback with the processed error message. // If the error is an InternalServerError the message is stripped if the user is not an admin. -func LogAndProcessError(ctx *context.Context, status int, obj interface{}, cb func(string)) { +func LogAndProcessError(ctx *context.Context, status int, obj any, cb func(string)) { var message string if err, ok := obj.(error); ok { message = err.Error() @@ -35,3 +38,26 @@ func LogAndProcessError(ctx *context.Context, status int, obj interface{}, cb fu cb(message) } } + +// Serves the content of the package file +// If the url is set it will redirect the request, otherwise the content is copied to the response. +func ServePackageFile(ctx *context.Context, s io.ReadSeekCloser, u *url.URL, pf *packages_model.PackageFile, forceOpts ...*context.ServeHeaderOptions) { + if u != nil { + ctx.Redirect(u.String()) + return + } + + defer s.Close() + + var opts *context.ServeHeaderOptions + if len(forceOpts) > 0 { + opts = forceOpts[0] + } else { + opts = &context.ServeHeaderOptions{ + Filename: pf.Name, + LastModified: pf.CreatedUnix.AsLocalTime(), + } + } + + ctx.ServeContent(s, opts) +} diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index dd270ff0edd2f..84e3324367814 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -47,7 +47,7 @@ var ( illegalCharacters = regexp.MustCompile(`[\\/:"<>|?\*]`) ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -210,21 +210,15 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool return } - s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256)) + s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb) if err != nil { apiError(ctx, http.StatusInternalServerError, err) - } - defer s.Close() - - if pf.IsLead { - if err := packages_model.IncrementDownloadCounter(ctx, pv.ID); err != nil { - log.Error("Error incrementing download counter: %v", err) - } + return } opts.Filename = pf.Name - ctx.ServeContent(s, opts) + helper.ServePackageFile(ctx, s, u, pf, opts) } // UploadPackageFile adds a file to the package. If the package does not exist, it gets created. diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 89476a776a0dd..3616211d61845 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -30,7 +30,7 @@ import ( // errInvalidTagName indicates an invalid tag name var errInvalidTagName = errors.New("The tag name is invalid") -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, map[string]string{ "error": message, @@ -83,7 +83,7 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.Params("version") filename := ctx.Params("filename") - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -103,12 +103,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // DownloadPackageFileByName finds the version and serves the contents of a package @@ -134,7 +130,7 @@ func DownloadPackageFileByName(ctx *context.Context) { return } - s, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( ctx, pvs[0], &packages_service.PackageFileInfo{ @@ -149,12 +145,8 @@ func DownloadPackageFileByName(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // UploadPackage creates a new package diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 716d8a969d665..edeba19b3bbdb 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -25,7 +25,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, map[string]string{ "Message": message, @@ -33,7 +33,7 @@ func apiError(ctx *context.Context, status int, obj interface{}) { }) } -func xmlResponse(ctx *context.Context, status int, obj interface{}) { +func xmlResponse(ctx *context.Context, status int, obj any) { 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 { @@ -362,7 +362,7 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.Params("version") filename := ctx.Params("filename") - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -382,12 +382,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // UploadPackage creates a new package with the metadata contained in the uploaded nupgk file @@ -600,7 +596,7 @@ func DownloadSymbolFile(ctx *context.Context) { return } - s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) if err != nil { if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { apiError(ctx, http.StatusNotFound, err) @@ -609,12 +605,8 @@ func DownloadSymbolFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // DeletePackage hard deletes the package diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go index ae0c6e7859898..ef07836b8860b 100644 --- a/routers/api/packages/pub/pub.go +++ b/routers/api/packages/pub/pub.go @@ -25,7 +25,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" ) -func jsonResponse(ctx *context.Context, status int, obj interface{}) { +func jsonResponse(ctx *context.Context, status int, obj any) { resp := ctx.Resp resp.Header().Set("Content-Type", "application/vnd.pub.v2+json") resp.WriteHeader(status) @@ -34,7 +34,7 @@ func jsonResponse(ctx *context.Context, status int, obj interface{}) { } } -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { type Error struct { Code string `json:"code"` Message string `json:"message"` @@ -60,10 +60,10 @@ type packageVersions struct { } type versionMetadata struct { - Version string `json:"version"` - ArchiveURL string `json:"archive_url"` - Published time.Time `json:"published"` - Pubspec interface{} `json:"pubspec,omitempty"` + Version string `json:"version"` + ArchiveURL string `json:"archive_url"` + Published time.Time `json:"published"` + Pubspec any `json:"pubspec,omitempty"` } func packageDescriptorToMetadata(baseURL string, pd *packages_model.PackageDescriptor) *versionMetadata { @@ -273,15 +273,11 @@ func DownloadPackageFile(ctx *context.Context) { pf := pd.Files[0].File - s, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go index 90a37ec2a8f1a..d97b894bbed8a 100644 --- a/routers/api/packages/pypi/pypi.go +++ b/routers/api/packages/pypi/pypi.go @@ -37,7 +37,7 @@ var versionMatcher = regexp.MustCompile(`\Av?` + `(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?` + // local version `\z`) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -80,7 +80,7 @@ func DownloadPackageFile(ctx *context.Context) { packageVersion := ctx.Params("version") filename := ctx.Params("filename") - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -100,12 +100,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // UploadPackageFile adds a file to the package. If the package does not exist, it gets created. diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 73e457237ab4a..930b20208ae17 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -25,7 +25,7 @@ import ( rpm_service "code.gitea.io/gitea/services/packages/rpm" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -65,7 +65,7 @@ func GetRepositoryFile(ctx *context.Context) { return } - s, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( ctx, pv, &packages_service.PackageFileInfo{ @@ -80,12 +80,8 @@ func GetRepositoryFile(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } func UploadPackageFile(ctx *context.Context) { @@ -173,7 +169,7 @@ func DownloadPackageFile(ctx *context.Context) { name := ctx.Params("name") version := ctx.Params("version") - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -193,13 +189,8 @@ func DownloadPackageFile(ctx *context.Context) { } return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - ContentType: "application/x-rpm", - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } func DeletePackageFile(webctx *context.Context) { diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index 740efa9baba8b..88d70f10bdce6 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -21,7 +21,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.PlainText(status, message) }) @@ -65,9 +65,9 @@ func enumeratePackages(ctx *context.Context, filename string, pvs []*packages_mo return } - specs := make([]interface{}, 0, len(pds)) + specs := make([]any, 0, len(pds)) for _, p := range pds { - specs = append(specs, []interface{}{ + specs = append(specs, []any{ p.Package.Name, &rubygems_module.RubyUserMarshal{ Name: "Gem::Version", @@ -129,7 +129,7 @@ func ServePackageSpecification(ctx *context.Context) { // create a Ruby Gem::Specification object spec := &rubygems_module.RubyUserDef{ Name: "Gem::Specification", - Value: []interface{}{ + Value: []any{ "3.2.3", // @rubygems_version 4, // @specification_version, pd.Package.Name, @@ -142,7 +142,7 @@ func ServePackageSpecification(ctx *context.Context) { nil, // @required_ruby_version nil, // @required_rubygems_version metadata.Platform, // @original_platform - []interface{}{}, // @dependencies + []any{}, // @dependencies nil, // rubyforge_project "", // @email metadata.Authors, @@ -175,7 +175,7 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, pf, err := packages_service.GetFileStreamByPackageVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( ctx, pvs[0], &packages_service.PackageFileInfo{ @@ -190,12 +190,8 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } // UploadPackageFile adds a file to the package. If the package does not exist, it gets created. diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go index 06f592dd648f9..bd4b8095c2c97 100644 --- a/routers/api/packages/swift/swift.go +++ b/routers/api/packages/swift/swift.go @@ -69,7 +69,7 @@ func setResponseHeaders(resp http.ResponseWriter, h *headers) { } // https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#33-error-handling -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { // https://www.rfc-editor.org/rfc/rfc7807 type Problem struct { Status int `json:"status"` @@ -397,18 +397,17 @@ func DownloadPackageFile(ctx *context.Context) { pf := pd.Files[0].File - s, _, err := packages_service.GetPackageFileStream(ctx, pf) + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() setResponseHeaders(ctx.Resp, &headers{ Digest: pd.Files[0].Blob.HashSHA256, }) - ctx.ServeContent(s, &context.ServeHeaderOptions{ + helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{ Filename: pf.Name, ContentType: "application/zip", LastModified: pf.CreatedUnix.AsLocalTime(), diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go index cefdc45b10ff4..9fe7ab56f6626 100644 --- a/routers/api/packages/vagrant/vagrant.go +++ b/routers/api/packages/vagrant/vagrant.go @@ -22,7 +22,7 @@ import ( "github.com/hashicorp/go-version" ) -func apiError(ctx *context.Context, status int, obj interface{}) { +func apiError(ctx *context.Context, status int, obj any) { helper.LogAndProcessError(ctx, status, obj, func(message string) { ctx.JSON(status, struct { Errors []string `json:"errors"` @@ -216,7 +216,7 @@ func UploadPackageFile(ctx *context.Context) { } func DownloadPackageFile(ctx *context.Context) { - s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( ctx, &packages_service.PackageInfo{ Owner: ctx.Package.Owner, @@ -236,10 +236,6 @@ func DownloadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + helper.ServePackageFile(ctx, s, u, pf) } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index be66cc5240812..0e28bde68309a 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -777,11 +777,11 @@ func Routes() *web.Route { m.Group("/notifications", func() { m.Combo(""). Get(notify.ListNotifications). - Put(notify.ReadNotifications, reqToken()) + Put(reqToken(), notify.ReadNotifications) m.Get("/new", notify.NewAvailable) m.Combo("/threads/{id}"). Get(notify.GetThread). - Patch(notify.ReadThread, reqToken()) + Patch(reqToken(), notify.ReadThread) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification)) // Users (requires user scope) @@ -899,6 +899,11 @@ func Routes() *web.Route { Patch(bind(api.EditHookOption{}), user.EditHook). Delete(user.DeleteHook) }, reqWebhooksEnabled()) + + m.Group("/avatar", func() { + m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar) + m.Delete("", user.DeleteAvatar) + }, reqToken()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) // Repositories (requires repo scope, org scope) @@ -1134,6 +1139,10 @@ func Routes() *web.Route { m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages) m.Get("/activities/feeds", repo.ListRepoActivityFeeds) m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed) + m.Group("/avatar", func() { + m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar) + m.Delete("", repo.DeleteAvatar) + }, reqAdmin(), reqToken()) }, repoAssignment()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) @@ -1314,6 +1323,10 @@ func Routes() *web.Route { Patch(bind(api.EditHookOption{}), org.EditHook). Delete(org.DeleteHook) }, reqToken(), reqOrgOwnership(), reqWebhooksEnabled()) + m.Group("/avatar", func() { + m.Post("", bind(api.UpdateUserAvatarOption{}), org.UpdateAvatar) + m.Delete("", org.DeleteAvatar) + }, reqToken(), reqOrgOwnership()) m.Get("/activities/feeds", org.ListOrgActivityFeeds) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true)) m.Group("/teams/{teamid}", func() { diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index bd3b86a6f1525..e16c54a2c0bb4 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -183,7 +183,7 @@ func ReadRepoNotifications(ctx *context.APIContext) { if len(qLastRead) > 0 { tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) if err != nil { - ctx.InternalServerError(err) + ctx.Error(http.StatusBadRequest, "Parse", err) return } if !tmpLastRead.IsZero() { diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index 2261610c09238..a9c6b4361794d 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -132,7 +132,7 @@ func ReadNotifications(ctx *context.APIContext) { if len(qLastRead) > 0 { tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) if err != nil { - ctx.InternalServerError(err) + ctx.Error(http.StatusBadRequest, "Parse", err) return } if !tmpLastRead.IsZero() { diff --git a/routers/api/v1/org/avatar.go b/routers/api/v1/org/avatar.go new file mode 100644 index 0000000000000..b3cb0b81a6be8 --- /dev/null +++ b/routers/api/v1/org/avatar.go @@ -0,0 +1,74 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package org + +import ( + "encoding/base64" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + user_service "code.gitea.io/gitea/services/user" +) + +// UpdateAvatarupdates the Avatar of an Organisation +func UpdateAvatar(ctx *context.APIContext) { + // swagger:operation POST /orgs/{org}/avatar organization orgUpdateAvatar + // --- + // summary: Update Avatar + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateUserAvatarOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + form := web.GetForm(ctx).(*api.UpdateUserAvatarOption) + + content, err := base64.StdEncoding.DecodeString(form.Image) + if err != nil { + ctx.Error(http.StatusBadRequest, "DecodeImage", err) + return + } + + err = user_service.UploadAvatar(ctx.Org.Organization.AsUser(), content) + if err != nil { + ctx.Error(http.StatusInternalServerError, "UploadAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteAvatar deletes the Avatar of an Organisation +func DeleteAvatar(ctx *context.APIContext) { + // swagger:operation DELETE /orgs/{org}/avatar organization orgDeleteAvatar + // --- + // summary: Delete Avatar + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + err := user_service.DeleteAvatar(ctx.Org.Organization.AsUser()) + if err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 1aaefacdd596c..0e11acc901fb4 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -792,7 +792,7 @@ func SearchTeam(ctx *context.APIContext) { teams, maxResults, err := organization.SearchTeam(opts) if err != nil { log.Error("SearchTeam failed: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + ctx.JSON(http.StatusInternalServerError, map[string]any{ "ok": false, "error": "SearchTeam internal failure", }) @@ -807,7 +807,7 @@ func SearchTeam(ctx *context.APIContext) { ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) ctx.SetTotalCountHeader(maxResults) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, "data": apiTeams, }) diff --git a/routers/api/v1/repo/avatar.go b/routers/api/v1/repo/avatar.go new file mode 100644 index 0000000000000..48bd143d0c8a4 --- /dev/null +++ b/routers/api/v1/repo/avatar.go @@ -0,0 +1,84 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "encoding/base64" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + repo_service "code.gitea.io/gitea/services/repository" +) + +// UpdateVatar updates the Avatar of an Repo +func UpdateAvatar(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/avatar repository repoUpdateAvatar + // --- + // summary: Update avatar + // 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: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateRepoAvatarOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + form := web.GetForm(ctx).(*api.UpdateRepoAvatarOption) + + content, err := base64.StdEncoding.DecodeString(form.Image) + if err != nil { + ctx.Error(http.StatusBadRequest, "DecodeImage", err) + return + } + + err = repo_service.UploadAvatar(ctx, ctx.Repo.Repository, content) + if err != nil { + ctx.Error(http.StatusInternalServerError, "UploadAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} + +// UpdateAvatar deletes the Avatar of an Repo +func DeleteAvatar(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/avatar repository repoDeleteAvatar + // --- + // summary: Delete avatar + // 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 + // responses: + // "204": + // "$ref": "#/responses/empty" + err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository) + if err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 5336ccb79743d..5e2c9878f0884 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -15,7 +15,9 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + repo_module "code.gitea.io/gitea/modules/repository" 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/utils" "code.gitea.io/gitea/services/convert" @@ -76,7 +78,7 @@ func GetBranch(ctx *context.APIContext) { return } - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) return @@ -116,8 +118,54 @@ func DeleteBranch(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + if ctx.Repo.Repository.IsEmpty { + ctx.Error(http.StatusNotFound, "", "Git Repository is empty.") + return + } + + if ctx.Repo.Repository.IsArchived { + ctx.Error(http.StatusForbidden, "", "Git Repository is archived.") + return + } + + if ctx.Repo.Repository.IsMirror { + ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.") + return + } + branchName := ctx.Params("*") + if ctx.Repo.Repository.IsEmpty { + ctx.Error(http.StatusForbidden, "", "Git Repository is empty.") + return + } + + // check whether branches of this repository has been synced + totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{ + RepoID: ctx.Repo.Repository.ID, + IsDeletedBranch: util.OptionalBoolFalse, + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "CountBranches", err) + return + } + if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch + _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) + if err != nil { + ctx.ServerError("SyncRepoBranches", err) + return + } + } + + if ctx.Repo.Repository.IsArchived { + ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository")) + return + } + if ctx.Repo.Repository.IsMirror { + ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository")) + return + } + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): @@ -162,17 +210,30 @@ func CreateBranch(ctx *context.APIContext) { // responses: // "201": // "$ref": "#/responses/Branch" + // "403": + // description: The branch is archived or a mirror. // "404": // description: The old branch does not exist. // "409": // description: The branch with the same name already exists. - opt := web.GetForm(ctx).(*api.CreateBranchRepoOption) if ctx.Repo.Repository.IsEmpty { ctx.Error(http.StatusNotFound, "", "Git Repository is empty.") return } + if ctx.Repo.Repository.IsArchived { + ctx.Error(http.StatusForbidden, "", "Git Repository is archived.") + return + } + + if ctx.Repo.Repository.IsMirror { + ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.") + return + } + + opt := web.GetForm(ctx).(*api.CreateBranchRepoOption) + var oldCommit *git.Commit var err error @@ -203,14 +264,14 @@ func CreateBranch(ctx *context.APIContext) { err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName) if err != nil { - if models.IsErrBranchDoesNotExist(err) { + if git_model.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "", "The old branch does not exist") } if models.IsErrTagAlreadyExists(err) { ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") - } else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { + } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { ctx.Error(http.StatusConflict, "", "The branch already exists.") - } else if models.IsErrBranchNameConflict(err) { + } else if git_model.IsErrBranchNameConflict(err) { ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.") } else { ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err) @@ -236,7 +297,7 @@ func CreateBranch(ctx *context.APIContext) { return } - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) return @@ -275,20 +336,43 @@ func ListBranches(ctx *context.APIContext) { // "200": // "$ref": "#/responses/BranchList" - var totalNumOfBranches int + var totalNumOfBranches int64 var apiBranches []*api.Branch listOptions := utils.GetListOptions(ctx) - if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil { + if !ctx.Repo.Repository.IsEmpty { + if ctx.Repo.GitRepo == nil { + ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil) + return + } + + branchOpts := git_model.FindBranchOptions{ + ListOptions: listOptions, + RepoID: ctx.Repo.Repository.ID, + IsDeletedBranch: util.OptionalBoolFalse, + } + var err error + totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts) + if err != nil { + ctx.Error(http.StatusInternalServerError, "CountBranches", err) + return + } + if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch + totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0) + if err != nil { + ctx.ServerError("SyncRepoBranches", err) + return + } + } + rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err) return } - skip, _ := listOptions.GetStartEnd() - branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize) + branches, err := git_model.FindBranches(ctx, branchOpts) if err != nil { ctx.Error(http.StatusInternalServerError, "GetBranches", err) return @@ -296,11 +380,11 @@ func ListBranches(ctx *context.APIContext) { apiBranches = make([]*api.Branch, 0, len(branches)) for i := range branches { - c, err := branches[i].GetCommit() + c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name) if err != nil { // Skip if this branch doesn't exist anymore. if git.IsErrNotExist(err) { - total-- + totalNumOfBranches-- continue } ctx.Error(http.StatusInternalServerError, "GetCommit", err) @@ -308,19 +392,17 @@ func ListBranches(ctx *context.APIContext) { } branchProtection := rules.GetFirstMatched(branches[i].Name) - apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) return } apiBranches = append(apiBranches, apiBranch) } - - totalNumOfBranches = total } - ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize) - ctx.SetTotalCountHeader(int64(totalNumOfBranches)) + ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize) + ctx.SetTotalCountHeader(totalNumOfBranches) ctx.JSON(http.StatusOK, apiBranches) } @@ -580,7 +662,7 @@ func CreateBranchProtection(ctx *context.APIContext) { }() } // FIXME: since we only need to recheck files protected rules, we could improve this - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, ruleName) + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName) if err != nil { ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) return @@ -851,7 +933,7 @@ func EditBranchProtection(ctx *context.APIContext) { } // FIXME: since we only need to recheck files protected rules, we could improve this - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName) if err != nil { ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) return diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 2b468d6e739b1..bf37532fc86f4 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -219,7 +219,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { common.ServeContentByReadSeeker(ctx.Base, ctx.Repo.TreePath, lastModified, lfsDataRc) } -func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEntry, lastModified time.Time) { +func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEntry, lastModified *time.Time) { entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) if err != nil { if git.IsErrNotExist(err) { @@ -227,23 +227,23 @@ func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEn } else { ctx.Error(http.StatusInternalServerError, "GetTreeEntryByPath", err) } - return + return nil, nil, nil } if entry.IsDir() || entry.IsSubModule() { ctx.NotFound("getBlobForEntry", nil) - return + return nil, nil, nil } info, _, err := git.Entries([]*git.TreeEntry{entry}).GetCommitsInfo(ctx, ctx.Repo.Commit, path.Dir("/" + ctx.Repo.TreePath)[1:]) if err != nil { ctx.Error(http.StatusInternalServerError, "GetCommitsInfo", err) - return + return nil, nil, nil } if len(info) == 1 { // Not Modified - lastModified = info[0].Commit.Committer.When + lastModified = &info[0].Commit.Committer.When } blob = entry.Blob() @@ -687,12 +687,12 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { ctx.Error(http.StatusForbidden, "Access", err) return } - if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || + if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } - if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) { + if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) return } @@ -843,7 +843,7 @@ func DeleteFile(ctx *context.APIContext) { if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) { ctx.Error(http.StatusNotFound, "DeleteFile", err) return - } else if models.IsErrBranchAlreadyExists(err) || + } else if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || models.IsErrCommitIDDoesNotMatch(err) || diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 49252f7a4b49a..e76775ae82226 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -195,7 +195,7 @@ func SearchIssues(ctx *context.APIContext) { } var issueIDs []int64 if len(keyword) > 0 && len(repoIDs) > 0 { - if issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, repoIDs, keyword); err != nil { + if issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, repoIDs, keyword, ctx.FormString("state")); err != nil { ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err) return } @@ -394,7 +394,7 @@ func ListIssues(ctx *context.APIContext) { var issueIDs []int64 var labelIDs []int64 if len(keyword) > 0 { - issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{ctx.Repo.Repository.ID}, keyword) + issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{ctx.Repo.Repository.ID}, keyword, ctx.FormString("state")) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err) return diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go index 1f0d21141bcce..9d43c66d8c99c 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -298,26 +298,26 @@ func ClearIssueLabels(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } -func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (issue *issues_model.Issue, labels []*issues_model.Label, err error) { - issue, err = issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) +func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (*issues_model.Issue, []*issues_model.Label, error) { + issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if issues_model.IsErrIssueNotExist(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err) } - return + return nil, nil, err } - labels, err = issues_model.GetLabelsByIDs(form.Labels) + labels, err := issues_model.GetLabelsByIDs(form.Labels) if err != nil { ctx.Error(http.StatusInternalServerError, "GetLabelsByIDs", err) - return + return nil, nil, err } if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { ctx.Status(http.StatusForbidden) - return + return nil, nil, nil } return issue, labels, err diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 06bfabe3d2d29..9d8497927ec44 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -258,7 +258,7 @@ func AddPushMirror(ctx *context.APIContext) { // schema: // "$ref": "#/definitions/CreatePushMirrorOption" // responses: - // "201": + // "200": // "$ref": "#/responses/PushMirror" // "403": // "$ref": "#/responses/forbidden" diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go index 6fbb9e7b3a75f..d2f055355de0d 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -8,6 +8,7 @@ import ( "time" "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -91,12 +92,12 @@ func ApplyDiffPatch(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "Access", err) return } - if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || + if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } - if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) { + if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) return } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index a9f8b5b7e7307..d9424d9e7698b 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -378,7 +378,7 @@ func Generate(ctx *context.APIContext) { ctxUser, err = user_model.GetUserByName(ctx, form.Owner) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.JSON(http.StatusNotFound, map[string]interface{}{ + ctx.JSON(http.StatusNotFound, map[string]any{ "error": "request owner `" + form.Owner + "` does not exist", }) return diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go index 7d27fe7d25a0d..8bf3012d48822 100644 --- a/routers/api/v1/repo/topic.go +++ b/routers/api/v1/repo/topic.go @@ -63,7 +63,7 @@ func ListTopics(ctx *context.APIContext) { } ctx.SetTotalCountHeader(total) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "topics": topicNames, }) } @@ -101,7 +101,7 @@ func UpdateTopics(ctx *context.APIContext) { validTopics, invalidTopics := repo_model.SanitizeAndValidateTopics(topicNames) if len(validTopics) > 25 { - ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + ctx.JSON(http.StatusUnprocessableEntity, map[string]any{ "invalidTopics": nil, "message": "Exceeding maximum number of topics per repo", }) @@ -109,7 +109,7 @@ func UpdateTopics(ctx *context.APIContext) { } if len(invalidTopics) > 0 { - ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + ctx.JSON(http.StatusUnprocessableEntity, map[string]any{ "invalidTopics": invalidTopics, "message": "Topic names are invalid", }) @@ -158,7 +158,7 @@ func AddTopic(ctx *context.APIContext) { topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic"))) if !repo_model.ValidateTopic(topicName) { - ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + ctx.JSON(http.StatusUnprocessableEntity, map[string]any{ "invalidTopics": topicName, "message": "Topic name is invalid", }) @@ -175,7 +175,7 @@ func AddTopic(ctx *context.APIContext) { return } if count >= 25 { - ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + ctx.JSON(http.StatusUnprocessableEntity, map[string]any{ "message": "Exceeding maximum allowed topics per repo.", }) return @@ -223,7 +223,7 @@ func DeleteTopic(ctx *context.APIContext) { topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic"))) if !repo_model.ValidateTopic(topicName) { - ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + ctx.JSON(http.StatusUnprocessableEntity, map[string]any{ "invalidTopics": topicName, "message": "Topic name is invalid", }) @@ -289,7 +289,7 @@ func TopicSearch(ctx *context.APIContext) { } ctx.SetTotalCountHeader(total) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "topics": topicResponses, }) } diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 353d32e2142ee..073d9a19f7d30 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -181,4 +181,10 @@ type swaggerParameterBodies struct { // in:body CreatePushMirrorOption api.CreatePushMirrorOption + + // in:body + UpdateUserAvatarOptions api.UpdateUserAvatarOption + + // in:body + UpdateRepoAvatarOptions api.UpdateRepoAvatarOption } diff --git a/routers/api/v1/user/avatar.go b/routers/api/v1/user/avatar.go new file mode 100644 index 0000000000000..84fa129b13abc --- /dev/null +++ b/routers/api/v1/user/avatar.go @@ -0,0 +1,63 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "encoding/base64" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" + user_service "code.gitea.io/gitea/services/user" +) + +// UpdateAvatar updates the Avatar of an User +func UpdateAvatar(ctx *context.APIContext) { + // swagger:operation POST /user/avatar user userUpdateAvatar + // --- + // summary: Update Avatar + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateUserAvatarOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + form := web.GetForm(ctx).(*api.UpdateUserAvatarOption) + + content, err := base64.StdEncoding.DecodeString(form.Image) + if err != nil { + ctx.Error(http.StatusBadRequest, "DecodeImage", err) + return + } + + err = user_service.UploadAvatar(ctx.Doer, content) + if err != nil { + ctx.Error(http.StatusInternalServerError, "UploadAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteAvatar deletes the Avatar of an User +func DeleteAvatar(ctx *context.APIContext) { + // swagger:operation DELETE /user/avatar user userDeleteAvatar + // --- + // summary: Delete Avatar + // produces: + // - application/json + // responses: + // "204": + // "$ref": "#/responses/empty" + err := user_service.DeleteAvatar(ctx.Doer) + if err != nil { + ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err) + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 314116962b559..2a2361be678bf 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -62,7 +62,7 @@ func Search(ctx *context.APIContext) { ListOptions: listOptions, }) if err != nil { - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + ctx.JSON(http.StatusInternalServerError, map[string]any{ "ok": false, "error": err.Error(), }) @@ -72,7 +72,7 @@ func Search(ctx *context.APIContext) { ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) ctx.SetTotalCountHeader(maxResults) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, "data": convert.ToUsers(ctx, ctx.Doer, users), }) diff --git a/routers/common/serve.go b/routers/common/serve.go index 3094ee6a6ec9b..8a7f8b3332127 100644 --- a/routers/common/serve.go +++ b/routers/common/serve.go @@ -15,7 +15,7 @@ import ( ) // ServeBlob download a git.Blob -func ServeBlob(ctx *context.Base, filePath string, blob *git.Blob, lastModified time.Time) error { +func ServeBlob(ctx *context.Base, filePath string, blob *git.Blob, lastModified *time.Time) error { if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { return nil } @@ -38,6 +38,6 @@ func ServeContentByReader(ctx *context.Base, filePath string, size int64, reader httplib.ServeContentByReader(ctx.Req, ctx.Resp, filePath, size, reader) } -func ServeContentByReadSeeker(ctx *context.Base, filePath string, modTime time.Time, reader io.ReadSeeker) { +func ServeContentByReadSeeker(ctx *context.Base, filePath string, modTime *time.Time, reader io.ReadSeeker) { httplib.ServeContentByReadSeeker(ctx.Req, ctx.Resp, filePath, modTime, reader) } diff --git a/routers/private/manager_process.go b/routers/private/manager_process.go index a5993bf3718d3..68e4a21805b2f 100644 --- a/routers/private/manager_process.go +++ b/routers/private/manager_process.go @@ -49,7 +49,7 @@ func Processes(ctx *context.PrivateContext) { } if json { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "TotalNumberOfGoroutines": goroutineCount, "TotalNumberOfProcesses": processCount, "Processes": processes, diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go index 797ba8798d06c..225a8c6705368 100644 --- a/routers/web/admin/admin.go +++ b/routers/web/admin/admin.go @@ -14,12 +14,15 @@ import ( activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/updatechecker" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/cron" "code.gitea.io/gitea/services/forms" + repo_service "code.gitea.io/gitea/services/repository" ) const ( @@ -133,12 +136,22 @@ func DashboardPost(ctx *context.Context) { // Run operation. if form.Op != "" { - task := cron.GetTask(form.Op) - if task != nil { - go task.RunWithUser(ctx.Doer, nil) - ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) - } else { - ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) + switch form.Op { + case "sync_repo_branches": + go func() { + if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil { + log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err) + } + }() + ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started")) + default: + task := cron.GetTask(form.Op) + if task != nil { + go task.RunWithUser(ctx.Doer, nil) + ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) + } else { + ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) + } } } if form.From == "monitor" { diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index b6ea3ff40300a..adde26f0b56e1 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -60,7 +60,7 @@ func Authentications(ctx *context.Context) { type dropdownItem struct { Name string - Type interface{} + Type any } var ( @@ -454,7 +454,7 @@ func DeleteAuthSource(ctx *context.Context) { } else { ctx.Flash.Error(fmt.Sprintf("auth_service.DeleteSource: %v", err)) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.Params(":authid")), }) return @@ -462,7 +462,7 @@ func DeleteAuthSource(ctx *context.Context) { log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID) ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/admin/auths", }) } diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index 2c6989a71dbb3..1c233ae852ac9 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -207,7 +207,7 @@ func ChangeConfig(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "version": version + 1, }) } diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index 46dc734d252fa..2e4122c9048bf 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -26,8 +26,8 @@ func DefaultOrSystemWebhooks(ctx *context.Context) { ctx.Data["PageIsAdminSystemHooks"] = true ctx.Data["PageIsAdminDefaultHooks"] = true - def := make(map[string]interface{}, len(ctx.Data)) - sys := make(map[string]interface{}, len(ctx.Data)) + def := make(map[string]any, len(ctx.Data)) + sys := make(map[string]any, len(ctx.Data)) for k, v := range ctx.Data { def[k] = v sys[k] = v @@ -67,7 +67,7 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/admin/hooks", }) } diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go index 4d5987e780b6f..ace54fc0d80ea 100644 --- a/routers/web/admin/packages.go +++ b/routers/web/admin/packages.go @@ -97,7 +97,7 @@ func DeletePackageVersion(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("packages.settings.delete.success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/admin/packages?page=" + url.QueryEscape(ctx.FormString("page")) + "&q=" + url.QueryEscape(ctx.FormString("q")) + "&type=" + url.QueryEscape(ctx.FormString("type")), }) } diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go index 9a0e467b48712..2ea8a2ad35819 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -58,7 +58,7 @@ func DeleteRepo(ctx *context.Context) { log.Trace("Repository deleted: %s", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/admin/repos?page=" + url.QueryEscape(ctx.FormString("page")) + "&sort=" + url.QueryEscape(ctx.FormString("sort")), }) } diff --git a/routers/web/admin/stacktrace.go b/routers/web/admin/stacktrace.go index 4b225c2c8a4ce..f2d2be481a6a3 100644 --- a/routers/web/admin/stacktrace.go +++ b/routers/web/admin/stacktrace.go @@ -42,7 +42,7 @@ func Stacktrace(ctx *context.Context) { func StacktraceCancel(ctx *context.Context) { pid := ctx.Params("pid") process.GetManager().Cancel(process.IDType(pid)) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/admin/monitor/stacktrace", }) } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 3895bcfdb9293..08a4076418897 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -56,7 +56,7 @@ func Users(ctx *context.Context) { sortType = explore.UserSearchDefaultAdminSort ctx.SetFormString("sort", sortType) } - ctx.PageData["adminUserListSearchForm"] = map[string]interface{}{ + ctx.PageData["adminUserListSearchForm"] = map[string]any{ "StatusFilterMap": statusFilterMap, "SortType": sortType, } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index bc8f6d58c9270..3bf133f56222c 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -78,7 +78,7 @@ func AutoSignIn(ctx *context.Context) (bool, error) { isSucceed = true - if err := updateSession(ctx, nil, map[string]interface{}{ + if err := updateSession(ctx, nil, map[string]any{ // Set session IDs "uid": u.ID, "uname": u.Name, @@ -201,7 +201,7 @@ func SignInPost(ctx *context.Context) { u, source, err := auth_service.UserSignIn(form.UserName, form.Password) if err != nil { - if user_model.IsErrUserNotExist(err) || user_model.IsErrEmailAddressNotExist(err) { + if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) { ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) } else if user_model.IsErrEmailAlreadyUsed(err) { @@ -255,7 +255,7 @@ func SignInPost(ctx *context.Context) { return } - updates := map[string]interface{}{ + updates := map[string]any{ // User will need to use 2FA TOTP or WebAuthn, save data "twofaUid": u.ID, "twofaRemember": form.Remember, @@ -305,7 +305,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe "twofaUid", "twofaRemember", "linkAccount", - }, map[string]interface{}{ + }, map[string]any{ "uid": u.ID, "uname": u.Name, }); err != nil { @@ -476,7 +476,7 @@ func SignUpPost(ctx *context.Context) { // createAndHandleCreatedUser calls createUserInContext and // then handleUserCreated. -func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool { +func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool { if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) { return false } @@ -485,7 +485,7 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form int // createUserInContext creates a user and handles errors within a given context. // Optionally a template can be specified. -func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) { +func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) { if err := user_model.CreateUser(u, overwrites); err != nil { if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) { if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { @@ -497,23 +497,23 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ hasUser, err = user_model.GetUser(user) if !hasUser || err != nil { ctx.ServerError("UserLinkAccount", err) - return + return false } } // TODO: probably we should respect 'remember' user's choice... linkAccount(ctx, user, *gothUser, true) - return // user is already created here, all redirects are handled + return false // user is already created here, all redirects are handled } else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin { showLinkingLogin(ctx, *gothUser) - return // user will be created only after linking login + return false // user will be created only after linking login } } // handle error without template if len(tpl) == 0 { ctx.ServerError("CreateUser", err) - return + return false } // handle error with template @@ -542,7 +542,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ default: ctx.ServerError("CreateUser", err) } - return + return false } log.Trace("Account created: %s", u.Name) return true @@ -559,7 +559,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. u.SetLastLogin() if err := user_model.UpdateUserCols(ctx, u, "is_admin", "is_active", "last_login_unix"); err != nil { ctx.ServerError("UpdateUser", err) - return + return false } } @@ -577,7 +577,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. if setting.Service.RegisterManualConfirm { ctx.Data["ManualActivationOnly"] = true ctx.HTML(http.StatusOK, TplActivate) - return + return false } mailer.SendActivateAccountMail(ctx.Locale, u) @@ -592,7 +592,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. log.Error("Set cache(MailResendLimit) fail: %v", err) } } - return + return false } return true @@ -707,7 +707,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { log.Trace("User activated: %s", user.Name) - if err := updateSession(ctx, nil, map[string]interface{}{ + if err := updateSession(ctx, nil, map[string]any{ "uid": user.ID, "uname": user.Name, }); err != nil { @@ -760,7 +760,7 @@ func ActivateEmail(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/account") } -func updateSession(ctx *context.Context, deletes []string, updates map[string]interface{}) error { +func updateSession(ctx *context.Context, deletes []string, updates map[string]any) error { if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil { return fmt.Errorf("regenerate session: %w", err) } diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go index 865bcca15240d..0f7ecf1af49ae 100644 --- a/routers/web/auth/linkaccount.go +++ b/routers/web/auth/linkaccount.go @@ -13,7 +13,9 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" @@ -81,6 +83,32 @@ func LinkAccount(ctx *context.Context) { ctx.HTML(http.StatusOK, tplLinkAccount) } +func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl base.TplName, invoker string, err error) { + if errors.Is(err, util.ErrNotExist) { + ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm) + } else if errors.Is(err, util.ErrInvalidArgument) { + ctx.Data["user_exists"] = true + ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm) + } else if user_model.IsErrUserProhibitLogin(err) { + ctx.Data["user_exists"] = true + log.Info("Failed authentication attempt for %s from %s: %v", userName, ctx.RemoteAddr(), err) + ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") + ctx.HTML(http.StatusOK, "user/auth/prohibit_login") + } else if user_model.IsErrUserInactive(err) { + ctx.Data["user_exists"] = true + if setting.Service.RegisterEmailConfirm { + ctx.Data["Title"] = ctx.Tr("auth.active_your_account") + ctx.HTML(http.StatusOK, TplActivate) + } else { + log.Info("Failed authentication attempt for %s from %s: %v", userName, ctx.RemoteAddr(), err) + ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") + ctx.HTML(http.StatusOK, "user/auth/prohibit_login") + } + } else { + ctx.ServerError(invoker, err) + } +} + // LinkAccountPostSignIn handle the coupling of external account with another account using signIn func LinkAccountPostSignIn(ctx *context.Context) { signInForm := web.GetForm(ctx).(*forms.SignInForm) @@ -116,12 +144,7 @@ func LinkAccountPostSignIn(ctx *context.Context) { u, _, err := auth_service.UserSignIn(signInForm.UserName, signInForm.Password) if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.Data["user_exists"] = true - ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplLinkAccount, &signInForm) - } else { - ctx.ServerError("UserLinkAccount", err) - } + handleSignInError(ctx, signInForm.UserName, &signInForm, tplLinkAccount, "UserLinkAccount", err) return } @@ -151,7 +174,7 @@ func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, r return } - if err := updateSession(ctx, nil, map[string]interface{}{ + if err := updateSession(ctx, nil, map[string]any{ // User needs to use 2FA, save data and redirect to 2FA page. "twofaUid": u.ID, "twofaRemember": remember, diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 0ce3bbde00314..db15bf2e3d1a5 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1008,13 +1008,13 @@ func SignInOAuthCallback(ctx *context.Context) { handleOAuth2SignIn(ctx, authSource, u, gothUser) } -func claimValueToStringSet(claimValue interface{}) container.Set[string] { +func claimValueToStringSet(claimValue any) container.Set[string] { var groups []string switch rawGroup := claimValue.(type) { case []string: groups = rawGroup - case []interface{}: + case []any: for _, group := range rawGroup { groups = append(groups, fmt.Sprintf("%s", group)) } @@ -1067,7 +1067,7 @@ func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_mod } func showLinkingLogin(ctx *context.Context, gothUser goth.User) { - if err := updateSession(ctx, nil, map[string]interface{}{ + if err := updateSession(ctx, nil, map[string]any{ "linkAccountGothUser": gothUser, }); err != nil { ctx.ServerError("updateSession", err) @@ -1119,7 +1119,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model // If this user is enrolled in 2FA and this source doesn't override it, // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page. if !needs2FA { - if err := updateSession(ctx, nil, map[string]interface{}{ + if err := updateSession(ctx, nil, map[string]any{ "uid": u.ID, "uname": u.Name, }); err != nil { @@ -1189,7 +1189,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } } - if err := updateSession(ctx, nil, map[string]interface{}{ + if err := updateSession(ctx, nil, map[string]any{ // User needs to use 2FA, save data and redirect to 2FA page. "twofaUid": u.ID, "twofaRemember": false, diff --git a/routers/web/auth/oauth_test.go b/routers/web/auth/oauth_test.go index 62f723600d3b1..adf933fd239bf 100644 --- a/routers/web/auth/oauth_test.go +++ b/routers/web/auth/oauth_test.go @@ -26,7 +26,7 @@ func createAndParseToken(t *testing.T, grant *auth.OAuth2Grant) *oauth2.OIDCToke assert.Nil(t, terr) assert.NotNil(t, response) - parsedToken, err := jwt.ParseWithClaims(response.IDToken, &oauth2.OIDCToken{}, func(token *jwt.Token) (interface{}, error) { + parsedToken, err := jwt.ParseWithClaims(response.IDToken, &oauth2.OIDCToken{}, func(token *jwt.Token) (any, error) { assert.NotNil(t, token.Method) assert.Equal(t, signingKey.SigningMethod().Alg(), token.Method.Alg()) return signingKey.VerifyKey(), nil diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 5e0e7b258f665..00fc17f098019 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -230,7 +230,7 @@ func signInOpenIDVerify(ctx *context.Context) { if u != nil { nickname = u.LowerName } - if err := updateSession(ctx, nil, map[string]interface{}{ + if err := updateSession(ctx, nil, map[string]any{ "openid_verified_uri": id, "openid_determined_email": email, "openid_determined_username": nickname, @@ -282,11 +282,7 @@ func ConnectOpenIDPost(ctx *context.Context) { u, _, err := auth.UserSignIn(form.UserName, form.Password) if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplConnectOID, &form) - } else { - ctx.ServerError("ConnectOpenIDPost", err) - } + handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err) return } diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 64b732c035671..525ca9be53c63 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -34,7 +34,7 @@ func List(ctx *context.Context) { func FetchActionTest(ctx *context.Context) { _ = ctx.Req.ParseForm() - ctx.Flash.Info(ctx.Req.Method + " " + ctx.Req.RequestURI + "
" + + ctx.Flash.Info("fetch-action: " + ctx.Req.Method + " " + ctx.Req.RequestURI + "
" + "Form: " + ctx.Req.Form.Encode() + "
" + "PostForm: " + ctx.Req.PostForm.Encode(), ) @@ -52,5 +52,15 @@ func Tmpl(ctx *context.Context) { ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second) ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second) + if ctx.Req.Method == "POST" { + _ = ctx.Req.ParseForm() + ctx.Flash.Info("form: "+ctx.Req.Method+" "+ctx.Req.RequestURI+"
"+ + "Form: "+ctx.Req.Form.Encode()+"
"+ + "PostForm: "+ctx.Req.PostForm.Encode(), + true, + ) + time.Sleep(2 * time.Second) + } + ctx.HTML(http.StatusOK, base.TplName("devtest"+path.Clean("/"+ctx.Params("sub")))) } diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index be5ad1b015b79..e5f7977abd01e 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -73,6 +73,14 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { orderBy = db.SearchOrderBySizeReverse case "size": orderBy = db.SearchOrderBySize + case "reversegitsize": + orderBy = db.SearchOrderByGitSizeReverse + case "gitsize": + orderBy = db.SearchOrderByGitSize + case "reverselfssize": + orderBy = db.SearchOrderByLFSSizeReverse + case "lfssize": + orderBy = db.SearchOrderByLFSSize case "moststars": orderBy = db.SearchOrderByStarsReverse case "feweststars": diff --git a/routers/web/explore/topic.go b/routers/web/explore/topic.go index e172d9e04d018..132ef23fa757d 100644 --- a/routers/web/explore/topic.go +++ b/routers/web/explore/topic.go @@ -35,7 +35,7 @@ func TopicSearch(ctx *context.Context) { } ctx.SetTotalCountHeader(total) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "topics": topicResponses, }) } diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 1953a8b2849b3..8da0f0b9fd012 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -101,7 +101,7 @@ func MembersAction(ctx *context.Context) { err = models.RemoveOrgUser(org.ID, uid) if organization.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Org.OrgLink + "/members", }) return @@ -110,12 +110,12 @@ func MembersAction(ctx *context.Context) { err = models.RemoveOrgUser(org.ID, ctx.Doer.ID) if err == nil { ctx.Flash.Success(ctx.Tr("form.organization_leave_success", org.DisplayName())) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": "", // keep the user stay on current page, in case they want to do other operations. }) } else if organization.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Org.OrgLink + "/members", }) } else { @@ -126,7 +126,7 @@ func MembersAction(ctx *context.Context) { if err != nil { log.Error("Action(%s): %v", ctx.Params(":action"), err) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": false, "err": err.Error(), }) @@ -138,7 +138,7 @@ func MembersAction(ctx *context.Context) { redirect = setting.AppSubURL + "/" } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": redirect, }) } diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go index 9ce05680d7bd2..08566637a8780 100644 --- a/routers/web/org/org_labels.go +++ b/routers/web/org/org_labels.go @@ -90,7 +90,7 @@ func DeleteLabel(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Org.OrgLink + "/settings/labels", }) } diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index e525f2c43f3ac..21cb23000d2e9 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -41,6 +41,7 @@ func MustEnableProjects(ctx *context.Context) { // Projects renders the home page of projects func Projects(ctx *context.Context) { + shared_user.PrepareContextForProfileBigAvatar(ctx) ctx.Data["Title"] = ctx.Tr("repo.project_board") sortType := ctx.FormTrim("sort") @@ -218,7 +219,7 @@ func DeleteProject(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.ContextUser.HomeLink() + "/-/projects", }) } @@ -436,9 +437,11 @@ func UpdateIssueProject(ctx *context.Context) { projectID := ctx.FormInt64("id") for _, issue := range issues { - oldProjectID := issue.Project.ID - if oldProjectID == projectID { - continue + if issue.Project != nil { + oldProjectID := issue.Project.ID + if oldProjectID == projectID { + continue + } } if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil { @@ -447,7 +450,7 @@ func UpdateIssueProject(ctx *context.Context) { } } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -495,7 +498,7 @@ func DeleteProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -524,7 +527,7 @@ func AddBoardToProjectPost(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -592,7 +595,7 @@ func EditProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -609,7 +612,7 @@ func SetDefaultProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -626,7 +629,7 @@ func UnsetDefaultProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -728,7 +731,7 @@ func MoveIssues(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 2c4a6b93e39e0..b5653160a26d5 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -224,7 +224,7 @@ func DeleteWebhook(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Org.OrgLink + "/settings/hooks", }) } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 2ce4bf5322717..aefadaf809e82 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -79,7 +79,7 @@ func TeamsAction(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) } else { log.Error("Action(%s): %v", ctx.Params(":action"), err) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": false, "err": err.Error(), }) @@ -95,7 +95,7 @@ func TeamsAction(ctx *context.Context) { redirect = setting.AppSubURL + "/" } ctx.JSON(http.StatusOK, - map[string]interface{}{ + map[string]any{ "redirect": redirect, }) return @@ -117,7 +117,7 @@ func TeamsAction(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) } else { log.Error("Action(%s): %v", ctx.Params(":action"), err) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": false, "err": err.Error(), }) @@ -125,7 +125,7 @@ func TeamsAction(ctx *context.Context) { } } ctx.JSON(http.StatusOK, - map[string]interface{}{ + map[string]any{ "redirect": ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName), }) return @@ -199,7 +199,7 @@ func TeamsAction(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) } else { log.Error("Action(%s): %v", ctx.Params(":action"), err) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": false, "err": err.Error(), }) @@ -256,7 +256,7 @@ func TeamsRepoAction(ctx *context.Context) { } if action == "addall" || action == "removeall" { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories", }) return @@ -414,7 +414,7 @@ func SearchTeam(ctx *context.Context) { teams, maxResults, err := org_model.SearchTeam(opts) if err != nil { log.Error("SearchTeam failed: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + ctx.JSON(http.StatusInternalServerError, map[string]any{ "ok": false, "error": "SearchTeam internal failure", }) @@ -424,7 +424,7 @@ func SearchTeam(ctx *context.Context) { apiTeams, err := convert.ToTeams(ctx, teams, false) if err != nil { log.Error("convert ToTeams failed: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + ctx.JSON(http.StatusInternalServerError, map[string]any{ "ok": false, "error": "SearchTeam failed to get units", }) @@ -432,7 +432,7 @@ func SearchTeam(ctx *context.Context) { } ctx.SetTotalCountHeader(maxResults) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, "data": apiTeams, }) @@ -530,7 +530,7 @@ func DeleteTeam(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Org.OrgLink + "/teams", }) } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 7c2e9d63d6d32..537bc618075c4 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strings" "time" actions_model "code.gitea.io/gitea/models/actions" @@ -310,6 +311,55 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) erro return nil } +func Logs(ctx *context_module.Context) { + runIndex := ctx.ParamsInt64("run") + jobIndex := ctx.ParamsInt64("job") + + job, _ := getRunJobs(ctx, runIndex, jobIndex) + if ctx.Written() { + return + } + if job.TaskID == 0 { + ctx.Error(http.StatusNotFound, "job is not started") + return + } + + err := job.LoadRun(ctx) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + task, err := actions_model.GetTaskByID(ctx, job.TaskID) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + if task.LogExpired { + ctx.Error(http.StatusNotFound, "logs have been cleaned up") + return + } + + reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + defer reader.Close() + + workflowName := job.Run.WorkflowID + if p := strings.Index(workflowName, "."); p > 0 { + workflowName = workflowName[0:p] + } + ctx.ServeContent(reader, &context_module.ServeHeaderOptions{ + Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, job.Name, task.ID), + ContentLength: &task.LogSize, + ContentType: "text/plain", + ContentTypeCharset: "utf-8", + Disposition: "attachment", + }) +} + func Cancel(ctx *context_module.Context) { runIndex := ctx.ParamsInt64("run") diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index af1842ad109a5..b7be77914f045 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/upload" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/attachment" repo_service "code.gitea.io/gitea/services/repository" @@ -148,7 +149,7 @@ func ServeAttachment(ctx *context.Context, uuid string) { } defer fr.Close() - common.ServeContentByReadSeeker(ctx.Base, attach.Name, attach.CreatedUnix.AsTime(), fr) + common.ServeContentByReadSeeker(ctx.Base, attach.Name, util.ToPointer(attach.CreatedUnix.AsTime()), fr) } // GetAttachment serve attachments diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index ea2c01856d45f..999104d7872dd 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" - issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" @@ -28,32 +27,16 @@ import ( "code.gitea.io/gitea/services/forms" release_service "code.gitea.io/gitea/services/release" repo_service "code.gitea.io/gitea/services/repository" - files_service "code.gitea.io/gitea/services/repository/files" ) const ( tplBranch base.TplName = "repo/branch/list" ) -// Branch contains the branch information -type Branch struct { - Name string - Commit *git.Commit - IsProtected bool - IsDeleted bool - IsIncluded bool - DeletedBranch *git_model.DeletedBranch - CommitsAhead int - CommitsBehind int - LatestPullRequest *issues_model.PullRequest - MergeMovedOn bool -} - // Branches render repository branch page func Branches(ctx *context.Context) { ctx.Data["Title"] = "Branches" ctx.Data["IsRepoToolbarBranches"] = true - ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls() ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode) ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror @@ -68,15 +51,33 @@ func Branches(ctx *context.Context) { } pageSize := setting.Git.BranchesRangeSize - skip := (page - 1) * pageSize - log.Debug("Branches: skip: %d limit: %d", skip, pageSize) - defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize) - if ctx.Written() { + defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, page, pageSize) + if err != nil { + ctx.ServerError("LoadBranches", err) + return + } + + commitIDs := []string{defaultBranch.DBBranch.CommitID} + for _, branch := range branches { + commitIDs = append(commitIDs, branch.DBBranch.CommitID) + } + + commitStatuses, err := git_model.GetLatestCommitStatusForRepoCommitIDs(ctx, ctx.Repo.Repository.ID, commitIDs) + if err != nil { + ctx.ServerError("LoadBranches", err) return } + + commitStatus := make(map[string]*git_model.CommitStatus) + for commitID, cs := range commitStatuses { + commitStatus[commitID] = git_model.CalcCommitStatus(cs) + } + ctx.Data["Branches"] = branches - ctx.Data["DefaultBranchBranch"] = defaultBranchBranch - pager := context.NewPagination(branchesCount, pageSize, page, 5) + ctx.Data["CommitStatus"] = commitStatus + ctx.Data["CommitStatuses"] = commitStatuses + ctx.Data["DefaultBranchBranch"] = defaultBranch + pager := context.NewPagination(int(branchesCount), pageSize, page, 5) pager.SetDefaultParams(ctx) ctx.Data["Page"] = pager @@ -130,7 +131,7 @@ func RestoreBranchPost(ctx *context.Context) { if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{ Remote: ctx.Repo.Repository.RepoPath(), - Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name), + Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name), Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository), }); err != nil { if strings.Contains(err.Error(), "already exists") { @@ -148,7 +149,7 @@ func RestoreBranchPost(ctx *context.Context) { &repo_module.PushUpdateOptions{ RefFullName: git.RefNameFromBranch(deletedBranch.Name), OldCommitID: git.EmptySHA, - NewCommitID: deletedBranch.Commit, + NewCommitID: deletedBranch.CommitID, PusherID: ctx.Doer.ID, PusherName: ctx.Doer.Name, RepoUserName: ctx.Repo.Owner.Name, @@ -161,185 +162,11 @@ func RestoreBranchPost(ctx *context.Context) { } func redirect(ctx *context.Context) { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page")), }) } -// loadBranches loads branches from the repository limited by page & pageSize. -// NOTE: May write to context on error. -func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, int) { - defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch) - if err != nil { - if !git.IsErrBranchNotExist(err) { - log.Error("loadBranches: get default branch: %v", err) - ctx.ServerError("GetDefaultBranch", err) - return nil, nil, 0 - } - log.Warn("loadBranches: missing default branch %s for %-v", ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository) - } - - rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit) - if err != nil { - log.Error("GetBranches: %v", err) - ctx.ServerError("GetBranches", err) - return nil, nil, 0 - } - - rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) - if err != nil { - ctx.ServerError("FindRepoProtectedBranchRules", err) - return nil, nil, 0 - } - - repoIDToRepo := map[int64]*repo_model.Repository{} - repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository - - repoIDToGitRepo := map[int64]*git.Repository{} - repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo - - var branches []*Branch - for i := range rawBranches { - if defaultBranch != nil && rawBranches[i].Name == defaultBranch.Name { - // Skip default branch - continue - } - - branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) - if branch == nil { - return nil, nil, 0 - } - - branches = append(branches, branch) - } - - var defaultBranchBranch *Branch - if defaultBranch != nil { - // Always add the default branch - log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name) - defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo) - branches = append(branches, defaultBranchBranch) - } - - if ctx.Repo.CanWrite(unit.TypeCode) { - deletedBranches, err := getDeletedBranches(ctx) - if err != nil { - ctx.ServerError("getDeletedBranches", err) - return nil, nil, 0 - } - branches = append(branches, deletedBranches...) - } - - return defaultBranchBranch, branches, totalNumOfBranches -} - -func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules, - repoIDToRepo map[int64]*repo_model.Repository, - repoIDToGitRepo map[int64]*git.Repository, -) *Branch { - log.Trace("loadOneBranch: '%s'", rawBranch.Name) - - commit, err := rawBranch.GetCommit() - if err != nil { - ctx.ServerError("GetCommit", err) - return nil - } - - branchName := rawBranch.Name - p := protectedBranches.GetFirstMatched(branchName) - isProtected := p != nil - - divergence := &git.DivergeObject{ - Ahead: -1, - Behind: -1, - } - if defaultBranch != nil { - divergence, err = files_service.CountDivergingCommits(ctx, ctx.Repo.Repository, git.BranchPrefix+branchName) - if err != nil { - log.Error("CountDivergingCommits", err) - } - } - - pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName) - if err != nil { - ctx.ServerError("GetLatestPullRequestByHeadInfo", err) - return nil - } - headCommit := commit.ID.String() - - mergeMovedOn := false - if pr != nil { - pr.HeadRepo = ctx.Repo.Repository - if err := pr.LoadIssue(ctx); err != nil { - ctx.ServerError("LoadIssue", err) - return nil - } - if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { - pr.BaseRepo = repo - } else if err := pr.LoadBaseRepo(ctx); err != nil { - ctx.ServerError("LoadBaseRepo", err) - return nil - } else { - repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo - } - pr.Issue.Repo = pr.BaseRepo - - if pr.HasMerged { - baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] - if !ok { - baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) - if err != nil { - ctx.ServerError("OpenRepository", err) - return nil - } - defer baseGitRepo.Close() - repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo - } - pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) - if err != nil && !git.IsErrNotExist(err) { - ctx.ServerError("GetBranchCommitID", err) - return nil - } - if err == nil && headCommit != pullCommit { - // the head has moved on from the merge - we shouldn't delete - mergeMovedOn = true - } - } - } - - isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName - return &Branch{ - Name: branchName, - Commit: commit, - IsProtected: isProtected, - IsIncluded: isIncluded, - CommitsAhead: divergence.Ahead, - CommitsBehind: divergence.Behind, - LatestPullRequest: pr, - MergeMovedOn: mergeMovedOn, - } -} - -func getDeletedBranches(ctx *context.Context) ([]*Branch, error) { - branches := []*Branch{} - - deletedBranches, err := git_model.GetDeletedBranches(ctx, ctx.Repo.Repository.ID) - if err != nil { - return branches, err - } - - for i := range deletedBranches { - deletedBranches[i].LoadUser(ctx) - branches = append(branches, &Branch{ - Name: deletedBranches[i].Name, - IsDeleted: true, - DeletedBranch: deletedBranches[i], - }) - } - - return branches, nil -} - // CreateBranch creates new branch in repository func CreateBranch(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewBranchForm) @@ -380,13 +207,13 @@ func CreateBranch(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { + if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName)) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - if models.IsErrBranchNameConflict(err) { - e := err.(models.ErrBranchNameConflict) + if git_model.IsErrBranchNameConflict(err) { + e := err.(git_model.ErrBranchNameConflict) ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return @@ -396,7 +223,7 @@ func CreateBranch(ctx *context.Context) { if len(e.Message) == 0 { ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message")) } else { - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.editor.push_rejected"), "Summary": ctx.Tr("repo.editor.push_rejected_summary"), "Details": utils.SanitizeFlashErrorString(e.Message), diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index 48bc6959e075f..5017d02252274 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -124,9 +125,9 @@ func CherryPickPost(ctx *context.Context) { // 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 models.IsErrBranchAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) { // User has specified a branch that already exists - branchErr := err.(models.ErrBranchAlreadyExists) + branchErr := err.(git_model.ErrBranchAlreadyExists) ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) return @@ -161,9 +162,9 @@ func CherryPickPost(ctx *context.Context) { ctx.Data["FileContent"] = opts.Content if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil { - if models.IsErrBranchAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) { // User has specified a branch that already exists - branchErr := err.(models.ErrBranchAlreadyExists) + branchErr := err.(git_model.ErrBranchAlreadyExists) ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) return diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 0ca1f90547efc..7089c219ad25c 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -16,6 +16,7 @@ import ( "path/filepath" "strings" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" @@ -683,7 +684,13 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor } defer gitRepo.Close() - branches, _, err = gitRepo.GetBranchNames(0, 0) + branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: repo.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) if err != nil { return nil, nil, err } @@ -734,7 +741,13 @@ func CompareDiff(ctx *context.Context) { return } - headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0) + headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: ci.HeadRepo.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) if err != nil { ctx.ServerError("GetBranches", err) return diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go index fd67b82ef277a..a9e2e2b2fad77 100644 --- a/routers/web/repo/download.go +++ b/routers/web/repo/download.go @@ -20,7 +20,7 @@ import ( ) // ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary -func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified time.Time) error { +func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Time) error { if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { return nil } @@ -82,7 +82,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified time.Time return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) } -func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified time.Time) { +func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified *time.Time) { entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) if err != nil { if git.IsErrNotExist(err) { @@ -90,23 +90,23 @@ func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified time.Ti } else { ctx.ServerError("GetTreeEntryByPath", err) } - return + return nil, nil } if entry.IsDir() || entry.IsSubModule() { ctx.NotFound("getBlobForEntry", nil) - return + return nil, nil } info, _, err := git.Entries([]*git.TreeEntry{entry}).GetCommitsInfo(ctx, ctx.Repo.Commit, path.Dir("/" + ctx.Repo.TreePath)[1:]) if err != nil { ctx.ServerError("GetCommitsInfo", err) - return + return nil, nil } if len(info) == 1 { // Not Modified - lastModified = info[0].Commit.Committer.When + lastModified = &info[0].Commit.Committer.When } blob = entry.Blob() @@ -148,7 +148,7 @@ func DownloadByID(ctx *context.Context) { } return } - if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, time.Time{}); err != nil { + if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, nil); err != nil { ctx.ServerError("ServeBlob", err) } } @@ -164,7 +164,7 @@ func DownloadByIDOrLFS(ctx *context.Context) { } return } - if err = ServeBlobOrLFS(ctx, blob, time.Time{}); err != nil { + if err = ServeBlobOrLFS(ctx, blob, nil); err != nil { ctx.ServerError("ServeBlob", err) } } diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 2fea8a9532b90..88f9e42f3bf43 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -327,10 +327,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrBranchAlreadyExists(err) { + } 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.(models.ErrBranchAlreadyExists); ok { + if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) } else { ctx.Error(http.StatusInternalServerError, err.Error()) @@ -344,7 +344,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b if len(errPushRej.Message) == 0 { ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form) } else { - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.editor.push_rejected"), "Summary": ctx.Tr("repo.editor.push_rejected_summary"), "Details": utils.SanitizeFlashErrorString(errPushRej.Message), @@ -356,7 +356,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b ctx.RenderWithErr(flashError, tplEditFile, &form) } } else { - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(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()), @@ -529,9 +529,9 @@ func DeleteFilePost(ctx *context.Context) { } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrBranchAlreadyExists(err) { + } else if git_model.IsErrBranchAlreadyExists(err) { // For when a user specifies a new branch that already exists - if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok { + if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok { ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form) } else { ctx.Error(http.StatusInternalServerError, err.Error()) @@ -543,7 +543,7 @@ func DeleteFilePost(ctx *context.Context) { if len(errPushRej.Message) == 0 { ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplDeleteFile, &form) } else { - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.editor.push_rejected"), "Summary": ctx.Tr("repo.editor.push_rejected_summary"), "Details": utils.SanitizeFlashErrorString(errPushRej.Message), @@ -731,10 +731,10 @@ func UploadFilePost(ctx *context.Context) { } 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 models.IsErrBranchAlreadyExists(err) { + } else if git_model.IsErrBranchAlreadyExists(err) { // For when a user specifies a new branch that already exists ctx.Data["Err_NewBranchName"] = true - branchErr := err.(models.ErrBranchAlreadyExists) + 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) @@ -743,7 +743,7 @@ func UploadFilePost(ctx *context.Context) { if len(errPushRej.Message) == 0 { ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplUploadFile, &form) } else { - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.editor.push_rejected"), "Summary": ctx.Tr("repo.editor.push_rejected_summary"), "Details": utils.SanitizeFlashErrorString(errPushRej.Message), diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index f4e9ac86a1bc3..0cae9aeda4549 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -56,13 +56,13 @@ func CorsHandler() func(next http.Handler) http.Handler { } // httpBase implementation git smart HTTP protocol -func httpBase(ctx *context.Context) (h *serviceHandler) { +func httpBase(ctx *context.Context) *serviceHandler { username := ctx.Params(":username") reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git") if ctx.FormString("go-get") == "1" { context.EarlyResponseForGoGetMeta(ctx) - return + return nil } var isPull, receivePack bool @@ -101,7 +101,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { owner := ctx.ContextUser if !owner.IsOrganization() && !owner.IsActive { ctx.PlainText(http.StatusForbidden, "Repository cannot be accessed. You cannot push or open issues/pull-requests.") - return + return nil } repoExist := true @@ -110,19 +110,19 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { if repo_model.IsErrRepoNotExist(err) { if redirectRepoID, err := repo_model.LookupRedirect(owner.ID, reponame); err == nil { context.RedirectToRepo(ctx.Base, redirectRepoID) - return + return nil } repoExist = false } else { ctx.ServerError("GetRepositoryByName", err) - return + return nil } } // Don't allow pushing if the repo is archived if repoExist && repo.IsArchived && !isPull { ctx.PlainText(http.StatusForbidden, "This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.") - return + return nil } // Only public pull don't need auth. @@ -136,7 +136,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { if isPublicPull { if err := repo.LoadOwner(ctx); err != nil { ctx.ServerError("LoadOwner", err) - return + return nil } askAuth = askAuth || (repo.Owner.Visibility != structs.VisibleTypePublic) @@ -149,12 +149,12 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { // TODO: support digit auth - which would be Authorization header with digit ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"") ctx.Error(http.StatusUnauthorized) - return + return nil } context.CheckRepoScopedToken(ctx, repo, auth_model.GetScopeLevelFromAccessMode(accessMode)) if ctx.Written() { - return + return nil } if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && ctx.Data["IsActionsToken"] != true { @@ -162,16 +162,16 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { if err == nil { // TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented ctx.PlainText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page") - return + return nil } else if !auth_model.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("IsErrTwoFactorNotEnrolled", err) - return + return nil } } if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin { ctx.PlainText(http.StatusForbidden, "Your account is disabled.") - return + return nil } environ = []string{ @@ -193,23 +193,23 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { task, err := actions_model.GetTaskByID(ctx, taskID) if err != nil { ctx.ServerError("GetTaskByID", err) - return + return nil } if task.RepoID != repo.ID { ctx.PlainText(http.StatusForbidden, "User permission denied") - return + return nil } if task.IsForkPullRequest { if accessMode > perm.AccessModeRead { ctx.PlainText(http.StatusForbidden, "User permission denied") - return + return nil } environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeRead)) } else { if accessMode > perm.AccessModeWrite { ctx.PlainText(http.StatusForbidden, "User permission denied") - return + return nil } environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeWrite)) } @@ -217,18 +217,18 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) - return + return nil } if !p.CanAccess(accessMode, unitType) { ctx.PlainText(http.StatusNotFound, "Repository not found") - return + return nil } } if !isPull && repo.IsMirror { ctx.PlainText(http.StatusForbidden, "mirror repository is read-only") - return + return nil } } @@ -246,34 +246,34 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { if !repoExist { if !receivePack { ctx.PlainText(http.StatusNotFound, "Repository not found") - return + return nil } if isWiki { // you cannot send wiki operation before create the repository ctx.PlainText(http.StatusNotFound, "Repository not found") - return + return nil } if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg { ctx.PlainText(http.StatusForbidden, "Push to create is not enabled for organizations.") - return + return nil } if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser { ctx.PlainText(http.StatusForbidden, "Push to create is not enabled for users.") - return + return nil } // Return dummy payload if GET receive-pack if ctx.Req.Method == http.MethodGet { dummyInfoRefs(ctx) - return + return nil } repo, err = repo_service.PushCreateRepo(ctx, ctx.Doer, owner, reponame) if err != nil { log.Error("pushCreateRepo: %v", err) ctx.Status(http.StatusNotFound) - return + return nil } } @@ -282,11 +282,11 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { if _, err := repo.GetUnit(ctx, unit.TypeWiki); err != nil { if repo_model.IsErrUnitTypeNotExist(err) { ctx.PlainText(http.StatusForbidden, "repository wiki is disabled") - return + return nil } log.Error("Failed to get the wiki unit in %-v Error: %v", repo, err) ctx.ServerError("GetUnit(UnitTypeWiki) for "+repo.FullName(), err) - return + return nil } } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index a0dd14e3144a5..317f762fb1a21 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -189,7 +189,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti var issueIDs []int64 if len(keyword) > 0 { - issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{repo.ID}, keyword) + issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{repo.ID}, keyword, ctx.FormString("state")) if err != nil { if issue_indexer.IsAvailable(ctx) { ctx.ServerError("issueIndexer.Search", err) @@ -785,7 +785,13 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull return nil } - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) + brs, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: ctx.Repo.Repository.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) if err != nil { ctx.ServerError("GetBranches", err) return nil @@ -946,7 +952,7 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string lines = append(lines, fmt.Sprintf("%s: %v", file, errs[file])) } - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.issues.choose.ignore_invalid_templates"), "Summary": ctx.Tr("repo.issues.choose.invalid_templates", len(errs)), "Details": utils.SanitizeFlashErrorString(strings.Join(lines, "\n")), @@ -1916,9 +1922,12 @@ func ViewIssue(ctx *context.Context) { ctx.HTML(http.StatusOK, tplIssueView) } +// checkBlockedByIssues return canRead and notPermitted func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) { - var lastRepoID int64 - var lastPerm access_model.Permission + var ( + lastRepoID int64 + lastPerm access_model.Permission + ) for i, blocker := range blockers { // Get the permissions for this repository perm := lastPerm @@ -1930,7 +1939,7 @@ func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.Depende perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer) if err != nil { ctx.ServerError("GetUserRepoPermission", err) - return + return nil, nil } } lastRepoID = blocker.Repository.ID @@ -1971,7 +1980,7 @@ func GetActionIssue(ctx *context.Context) *issues_model.Issue { return nil } if err = issue.LoadAttributes(ctx); err != nil { - ctx.ServerError("LoadAttributes", nil) + ctx.ServerError("LoadAttributes", err) return nil } return issue @@ -2075,7 +2084,7 @@ func UpdateIssueTitle(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "title": issue.Title, }) } @@ -2099,7 +2108,7 @@ func UpdateIssueRef(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ref": ref, }) } @@ -2140,7 +2149,7 @@ func UpdateIssueContent(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "content": content, "attachments": attachmentsHTML(ctx, issue.Attachments, issue.Content), }) @@ -2200,7 +2209,7 @@ func UpdateIssueMilestone(ctx *context.Context) { } } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -2246,7 +2255,7 @@ func UpdateIssueAssignee(ctx *context.Context) { } } } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -2371,7 +2380,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { } } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -2457,7 +2466,7 @@ func SearchIssues(ctx *context.Context) { } var issueIDs []int64 if len(keyword) > 0 && len(repoIDs) > 0 { - if issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, repoIDs, keyword); err != nil { + if issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, repoIDs, keyword, ctx.FormString("state")); err != nil { ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err.Error()) return } @@ -2605,7 +2614,7 @@ func ListIssues(ctx *context.Context) { var issueIDs []int64 var labelIDs []int64 if len(keyword) > 0 { - issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{ctx.Repo.Repository.ID}, keyword) + issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{ctx.Repo.Repository.ID}, keyword, ctx.FormString("state")) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) return @@ -2757,7 +2766,7 @@ func UpdateIssueStatus(ctx *context.Context) { if issue.IsClosed != isClosed { if err := issue_service.ChangeStatus(issue, ctx.Doer, "", isClosed); err != nil { if issues_model.IsErrDependenciesLeft(err) { - ctx.JSON(http.StatusPreconditionFailed, map[string]interface{}{ + ctx.JSON(http.StatusPreconditionFailed, map[string]any{ "error": ctx.Tr("repo.issues.dependency.issue_batch_close_blocked", issue.Index), }) return @@ -2972,7 +2981,7 @@ func UpdateCommentContent(ctx *context.Context) { oldContent := comment.Content comment.Content = ctx.FormString("content") if len(comment.Content) == 0 { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "content": "", }) return @@ -3006,7 +3015,7 @@ func UpdateCommentContent(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "content": content, "attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content), }) @@ -3116,14 +3125,14 @@ func ChangeIssueReaction(ctx *context.Context) { } if len(issue.Reactions) == 0 { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "empty": true, "html": "", }) return } - html, err := ctx.RenderToString(tplReactions, map[string]interface{}{ + html, err := ctx.RenderToString(tplReactions, map[string]any{ "ctxData": ctx.Data, "ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index), "Reactions": issue.Reactions.GroupByType(), @@ -3132,7 +3141,7 @@ func ChangeIssueReaction(ctx *context.Context) { ctx.ServerError("ChangeIssueReaction.HTMLString", err) return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "html": html, }) } @@ -3218,14 +3227,14 @@ func ChangeCommentReaction(ctx *context.Context) { } if len(comment.Reactions) == 0 { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "empty": true, "html": "", }) return } - html, err := ctx.RenderToString(tplReactions, map[string]interface{}{ + html, err := ctx.RenderToString(tplReactions, map[string]any{ "ctxData": ctx.Data, "ActionURL": fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID), "Reactions": comment.Reactions.GroupByType(), @@ -3234,7 +3243,7 @@ func ChangeCommentReaction(ctx *context.Context) { ctx.ServerError("ChangeCommentReaction.HTMLString", err) return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "html": html, }) } @@ -3276,6 +3285,9 @@ func filterXRefComments(ctx *context.Context, issue *issues_model.Issue) error { // GetIssueAttachments returns attachments for the issue func GetIssueAttachments(ctx *context.Context) { issue := GetActionIssue(ctx) + if ctx.Written() { + return + } attachments := make([]*api.Attachment, len(issue.Attachments)) for i := 0; i < len(issue.Attachments); i++ { attachments[i] = convert.ToAttachment(issue.Attachments[i]) @@ -3307,7 +3319,7 @@ func GetCommentAttachments(ctx *context.Context) { ctx.JSON(http.StatusOK, attachments) } -func updateAttachments(ctx *context.Context, item interface{}, files []string) error { +func updateAttachments(ctx *context.Context, item any, files []string) error { var attachments []*repo_model.Attachment switch content := item.(type) { case *issues_model.Issue: @@ -3351,7 +3363,7 @@ func updateAttachments(ctx *context.Context, item interface{}, files []string) e } func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) string { - attachHTML, err := ctx.RenderToString(tplAttachment, map[string]interface{}{ + attachHTML, err := ctx.RenderToString(tplAttachment, map[string]any{ "ctxData": ctx.Data, "Attachments": attachments, "Content": content, diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index 7e5295e757b7c..3dd7725c21506 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -24,13 +24,13 @@ import ( // GetContentHistoryOverview get overview func GetContentHistoryOverview(ctx *context.Context) { issue := GetActionIssue(ctx) - if issue == nil { + if ctx.Written() { return } editedHistoryCountMap, _ := issues_model.QueryIssueContentHistoryEditedCountMap(ctx, issue.ID) - ctx.JSON(http.StatusOK, map[string]interface{}{ - "i18n": map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ + "i18n": map[string]any{ "textEdited": ctx.Tr("repo.issues.content_history.edited"), "textDeleteFromHistory": ctx.Tr("repo.issues.content_history.delete_from_history"), "textDeleteFromHistoryConfirm": ctx.Tr("repo.issues.content_history.delete_from_history_confirm"), @@ -43,17 +43,17 @@ func GetContentHistoryOverview(ctx *context.Context) { // GetContentHistoryList get list func GetContentHistoryList(ctx *context.Context) { issue := GetActionIssue(ctx) - commentID := ctx.FormInt64("comment_id") - if issue == nil { + if ctx.Written() { return } + commentID := ctx.FormInt64("comment_id") items, _ := issues_model.FetchIssueContentHistoryList(ctx, issue.ID, commentID) // render history list to HTML for frontend dropdown items: (name, value) // name is HTML of "avatar + userName + userAction + timeSince" // value is historyId - var results []map[string]interface{} + var results []map[string]any for _, item := range items { var actionText string if item.IsDeleted { @@ -76,13 +76,13 @@ func GetContentHistoryList(ctx *context.Context) { avatarHTML := string(templates.AvatarHTML(src, 28, class, username)) timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale)) - results = append(results, map[string]interface{}{ + results = append(results, map[string]any{ "name": avatarHTML + "" + name + " " + actionText + " " + timeSinceText, "value": item.HistoryID, }) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "results": results, }) } @@ -113,14 +113,14 @@ func canSoftDeleteContentHistory(ctx *context.Context, issue *issues_model.Issue // GetContentHistoryDetail get detail func GetContentHistoryDetail(ctx *context.Context) { issue := GetActionIssue(ctx) - if issue == nil { + if ctx.Written() { return } historyID := ctx.FormInt64("history_id") history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, historyID) if err != nil { - ctx.JSON(http.StatusNotFound, map[string]interface{}{ + ctx.JSON(http.StatusNotFound, map[string]any{ "message": "Can not find the content history", }) return @@ -168,7 +168,7 @@ func GetContentHistoryDetail(ctx *context.Context) { } diffHTMLBuf.WriteString("") - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "canSoftDelete": canSoftDeleteContentHistory(ctx, issue, comment, history), "historyId": historyID, "prevHistoryId": prevHistoryID, @@ -179,7 +179,7 @@ func GetContentHistoryDetail(ctx *context.Context) { // SoftDeleteContentHistory soft delete func SoftDeleteContentHistory(ctx *context.Context) { issue := GetActionIssue(ctx) - if issue == nil { + if ctx.Written() { return } @@ -202,7 +202,7 @@ func SoftDeleteContentHistory(ctx *context.Context) { canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history) if !canSoftDelete { - ctx.JSON(http.StatusForbidden, map[string]interface{}{ + ctx.JSON(http.StatusForbidden, map[string]any{ "message": "Can not delete the content history", }) return @@ -210,7 +210,7 @@ func SoftDeleteContentHistory(ctx *context.Context) { err = issues_model.SoftDeleteIssueContentHistory(ctx, historyID) log.Debug("soft delete issue content history. issue=%d, comment=%d, history=%d", issue.ID, commentID, historyID) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": err == nil, }) } diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index 002acbf1d3c85..af5db83bd5d24 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -157,7 +157,7 @@ func DeleteLabel(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/labels", }) } @@ -226,7 +226,7 @@ func UpdateIssueLabel(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } diff --git a/routers/web/repo/issue_lock.go b/routers/web/repo/issue_lock.go index 08b76e555f112..93f5a588d9120 100644 --- a/routers/web/repo/issue_lock.go +++ b/routers/web/repo/issue_lock.go @@ -20,14 +20,12 @@ func LockIssue(ctx *context.Context) { } if issue.IsLocked { - ctx.Flash.Error(ctx.Tr("repo.issues.lock_duplicate")) - ctx.Redirect(issue.Link()) + ctx.JSONError(ctx.Tr("repo.issues.lock_duplicate")) return } if !form.HasValidReason() { - ctx.Flash.Error(ctx.Tr("repo.issues.lock.unknown_reason")) - ctx.Redirect(issue.Link()) + ctx.JSONError(ctx.Tr("repo.issues.lock.unknown_reason")) return } @@ -40,7 +38,7 @@ func LockIssue(ctx *context.Context) { return } - ctx.Redirect(issue.Link()) + ctx.JSONRedirect(issue.Link()) } // UnlockIssue unlocks a previously locked issue. @@ -51,8 +49,7 @@ func UnlockIssue(ctx *context.Context) { } if !issue.IsLocked { - ctx.Flash.Error(ctx.Tr("repo.issues.unlock_error")) - ctx.Redirect(issue.Link()) + ctx.JSONError(ctx.Tr("repo.issues.unlock_error")) return } @@ -64,5 +61,5 @@ func UnlockIssue(ctx *context.Context) { return } - ctx.Redirect(issue.Link()) + ctx.JSONRedirect(issue.Link()) } diff --git a/routers/web/repo/issue_pin.go b/routers/web/repo/issue_pin.go index 6586372fc5310..871ca3b333aee 100644 --- a/routers/web/repo/issue_pin.go +++ b/routers/web/repo/issue_pin.go @@ -15,6 +15,9 @@ import ( // IssuePinOrUnpin pin or unpin a Issue func IssuePinOrUnpin(ctx *context.Context) { issue := GetActionIssue(ctx) + if ctx.Written() { + return + } // If we don't do this, it will crash when trying to add the pin event to the comment history err := issue.LoadRepo(ctx) @@ -31,7 +34,7 @@ func IssuePinOrUnpin(ctx *context.Context) { return } - ctx.Redirect(issue.Link()) + ctx.JSONRedirect(issue.Link()) } // IssueUnpin unpins a Issue diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index 4b33fbcb16b70..38ef6939676f9 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -256,7 +256,7 @@ func DeleteMilestone(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/milestones", }) } diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index efb4662496c33..5faf9f4fa9bc2 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -7,6 +7,7 @@ import ( "strings" "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -94,9 +95,9 @@ func NewDiffPatchPost(ctx *context.Context) { Content: strings.ReplaceAll(form.Content, "\r", ""), }) if err != nil { - if models.IsErrBranchAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) { // User has specified a branch that already exists - branchErr := err.(models.ErrBranchAlreadyExists) + branchErr := err.(git_model.ErrBranchAlreadyExists) ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) return diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 6da9edfd0b802..066cdbc5fd889 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -203,7 +203,7 @@ func DeleteProject(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/projects", }) } @@ -385,9 +385,11 @@ func UpdateIssueProject(ctx *context.Context) { projectID := ctx.FormInt64("id") for _, issue := range issues { - oldProjectID := issue.Project.ID - if oldProjectID == projectID { - continue + if issue.Project != nil { + oldProjectID := issue.Project.ID + if oldProjectID == projectID { + continue + } } if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil { @@ -396,7 +398,7 @@ func UpdateIssueProject(ctx *context.Context) { } } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -451,7 +453,7 @@ func DeleteProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -486,7 +488,7 @@ func AddBoardToProjectPost(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -560,7 +562,7 @@ func EditProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -577,7 +579,7 @@ func SetDefaultProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -594,7 +596,7 @@ func UnSetDefaultProjectBoard(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -698,7 +700,7 @@ func MoveIssues(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index f2a58a35a78a8..e0a618bf596c7 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -356,12 +356,46 @@ func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) { ctx.Data["BaseBranchLink"] = pull.GetBaseBranchLink() } -// PrepareMergedViewPullInfo show meta information for a merged pull request view page -func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo { +// GetPullDiffStats get Pull Requests diff stats +func GetPullDiffStats(ctx *context.Context) { + issue := checkPullInfo(ctx) pull := issue.PullRequest - setMergeTarget(ctx, pull) - ctx.Data["HasMerged"] = true + mergeBaseCommitID := GetMergedBaseCommitID(ctx, issue) + + if ctx.Written() { + return + } else if mergeBaseCommitID == "" { + ctx.NotFound("PullFiles", nil) + return + } + + headCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitRefName()) + if err != nil { + ctx.ServerError("GetRefCommitID", err) + return + } + + diffOptions := &gitdiff.DiffOptions{ + BeforeCommitID: mergeBaseCommitID, + AfterCommitID: headCommitID, + MaxLines: setting.Git.MaxGitDiffLines, + MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, + MaxFiles: setting.Git.MaxGitDiffFiles, + WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), + } + + diff, err := gitdiff.GetPullDiffStats(ctx.Repo.GitRepo, diffOptions) + if err != nil { + ctx.ServerError("GetPullDiffStats", err) + return + } + + ctx.Data["Diff"] = diff +} + +func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) string { + pull := issue.PullRequest var baseCommit string // Some migrated PR won't have any Base SHA and lose history, try to get one @@ -401,6 +435,18 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) baseCommit = pull.MergeBase } + return baseCommit +} + +// PrepareMergedViewPullInfo show meta information for a merged pull request view page +func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo { + pull := issue.PullRequest + + setMergeTarget(ctx, pull) + ctx.Data["HasMerged"] = true + + baseCommit := GetMergedBaseCommitID(ctx, issue) + compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(), baseCommit, pull.GetGitRefName(), false, false) if err != nil { @@ -756,7 +802,7 @@ func ViewPullFiles(ctx *context.Context) { return } - ctx.PageData["prReview"] = map[string]interface{}{ + ctx.PageData["prReview"] = map[string]any{ "numberOfFiles": diff.NumFiles, "numberOfViewedFiles": diff.NumViewedFiles, } @@ -891,7 +937,7 @@ func UpdatePullRequest(ctx *context.Context) { if err = pull_service.Update(ctx, issue.PullRequest, ctx.Doer, message, rebase); err != nil { if models.IsErrMergeConflicts(err) { conflictError := err.(models.ErrMergeConflicts) - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.merge_conflict"), "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"), "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut), @@ -905,7 +951,7 @@ func UpdatePullRequest(ctx *context.Context) { return } else if models.IsErrRebaseConflicts(err) { conflictError := err.(models.ErrRebaseConflicts) - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut), @@ -1040,7 +1086,7 @@ func MergePullRequest(ctx *context.Context) { ctx.Redirect(issue.Link()) } else if models.IsErrMergeConflicts(err) { conflictError := err.(models.ErrMergeConflicts) - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.editor.merge_conflict"), "Summary": ctx.Tr("repo.editor.merge_conflict_summary"), "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut), @@ -1053,7 +1099,7 @@ func MergePullRequest(ctx *context.Context) { ctx.Redirect(issue.Link()) } else if models.IsErrRebaseConflicts(err) { conflictError := err.(models.ErrRebaseConflicts) - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut), @@ -1083,7 +1129,7 @@ func MergePullRequest(ctx *context.Context) { if len(message) == 0 { ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message")) } else { - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.push_rejected"), "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), @@ -1256,7 +1302,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { ctx.JSONError(ctx.Tr("repo.pulls.push_rejected_no_message")) return } - flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{ + flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.push_rejected"), "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), @@ -1361,7 +1407,7 @@ func CleanUpPullRequest(ctx *context.Context) { } defer func() { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": issue.Link(), }) }() @@ -1445,10 +1491,10 @@ func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) { // UpdatePullRequestTarget change pull request's target branch func UpdatePullRequestTarget(ctx *context.Context) { issue := GetActionIssue(ctx) - pr := issue.PullRequest if ctx.Written() { return } + pr := issue.PullRequest if !issue.IsPull { ctx.Error(http.StatusNotFound) return @@ -1473,7 +1519,7 @@ func UpdatePullRequestTarget(ctx *context.Context) { errorMessage := ctx.Tr("repo.pulls.has_pull_request", html.EscapeString(ctx.Repo.RepoLink+"/pulls/"+strconv.FormatInt(err.IssueID, 10)), html.EscapeString(RepoRelPath), err.IssueID) // FIXME: Creates url inside locale string ctx.Flash.Error(errorMessage) - ctx.JSON(http.StatusConflict, map[string]interface{}{ + ctx.JSON(http.StatusConflict, map[string]any{ "error": err.Error(), "user_error": errorMessage, }) @@ -1481,7 +1527,7 @@ func UpdatePullRequestTarget(ctx *context.Context) { errorMessage := ctx.Tr("repo.pulls.is_closed") ctx.Flash.Error(errorMessage) - ctx.JSON(http.StatusConflict, map[string]interface{}{ + ctx.JSON(http.StatusConflict, map[string]any{ "error": err.Error(), "user_error": errorMessage, }) @@ -1489,15 +1535,15 @@ func UpdatePullRequestTarget(ctx *context.Context) { errorMessage := ctx.Tr("repo.pulls.has_merged") ctx.Flash.Error(errorMessage) - ctx.JSON(http.StatusConflict, map[string]interface{}{ + ctx.JSON(http.StatusConflict, map[string]any{ "error": err.Error(), "user_error": errorMessage, }) - } else if models.IsErrBranchesEqual(err) { + } else if git_model.IsErrBranchesEqual(err) { errorMessage := ctx.Tr("repo.pulls.nothing_to_compare") ctx.Flash.Error(errorMessage) - ctx.JSON(http.StatusBadRequest, map[string]interface{}{ + ctx.JSON(http.StatusBadRequest, map[string]any{ "error": err.Error(), "user_error": errorMessage, }) @@ -1508,7 +1554,7 @@ func UpdatePullRequestTarget(ctx *context.Context) { } notification.NotifyPullRequestChangeTargetBranch(ctx, ctx.Doer, pr, targetBranch) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "base_branch": pr.BaseBranch, }) } @@ -1536,7 +1582,7 @@ func SetAllowEdits(ctx *context.Context) { return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "allow_maintainer_edit": pr.AllowMaintainerEdit, }) } diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index 5aa5811367056..f7c962d1ae8f2 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -28,6 +28,9 @@ const ( // RenderNewCodeCommentForm will render the form for creating a new review comment func RenderNewCodeCommentForm(ctx *context.Context) { issue := GetActionIssue(ctx) + if ctx.Written() { + return + } if !issue.IsPull { return } @@ -52,10 +55,10 @@ func RenderNewCodeCommentForm(ctx *context.Context) { func CreateCodeComment(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CodeCommentForm) issue := GetActionIssue(ctx) - if !issue.IsPull { + if ctx.Written() { return } - if ctx.Written() { + if !issue.IsPull { return } @@ -153,7 +156,7 @@ func UpdateResolveConversation(ctx *context.Context) { renderConversation(ctx, comment) return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, }) } @@ -185,10 +188,10 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment) { func SubmitReview(ctx *context.Context) { form := web.GetForm(ctx).(*forms.SubmitReviewForm) issue := GetActionIssue(ctx) - if !issue.IsPull { + if ctx.Written() { return } - if ctx.Written() { + if !issue.IsPull { return } if ctx.HasError() { diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 5fddddb344080..c9f12a0f18553 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -203,6 +203,7 @@ func releasesOrTags(ctx *context.Context, isTagList bool) { ctx.Data["Page"] = pager if isTagList { + ctx.Data["PageIsViewCode"] = !ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeReleases) ctx.HTML(http.StatusOK, tplTagsList) } else { ctx.HTML(http.StatusOK, tplReleasesList) @@ -607,13 +608,13 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) { } if isDelTag { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/tags", }) return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/releases", }) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index a1e1346b38c4b..d1ccb011bb1f0 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -181,7 +181,7 @@ func Create(ctx *context.Context) { ctx.HTML(http.StatusOK, tplCreate) } -func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form interface{}) { +func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form any) { switch { case repo_model.IsErrReachLimitOfRepo(err): maxCreationLimit := owner.MaxCreationLimit() @@ -482,7 +482,7 @@ func InitiateDownload(ctx *context.Context) { completed = true } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "complete": completed, }) } @@ -579,13 +579,15 @@ func SearchRepo(ctx *context.Context) { // collect the latest commit of each repo // at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment - repoIDsToLatestCommitSHAs := make(map[int64]string, len(repos)) + repoBranchNames := make(map[int64]string, len(repos)) for _, repo := range repos { - commitID, err := repo_service.GetBranchCommitID(ctx, repo, repo.DefaultBranch) - if err != nil { - continue - } - repoIDsToLatestCommitSHAs[repo.ID] = commitID + repoBranchNames[repo.ID] = repo.DefaultBranch + } + + repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames) + if err != nil { + log.Error("FindBranchesByRepoAndBranchName: %v", err) + return } // call the database O(1) times to get the commit statuses for all repos diff --git a/routers/web/repo/setting/avatar.go b/routers/web/repo/setting/avatar.go new file mode 100644 index 0000000000000..ec673ca28868b --- /dev/null +++ b/routers/web/repo/setting/avatar.go @@ -0,0 +1,76 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "errors" + "fmt" + "io" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/typesniffer" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/forms" + repo_service "code.gitea.io/gitea/services/repository" +) + +// UpdateAvatarSetting update repo's avatar +func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error { + ctxRepo := ctx.Repo.Repository + + if form.Avatar == nil { + // No avatar is uploaded and we not removing it here. + // No random avatar generated here. + // Just exit, no action. + if ctxRepo.CustomAvatarRelativePath() == "" { + log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID) + } + return nil + } + + r, err := form.Avatar.Open() + if err != nil { + return fmt.Errorf("Avatar.Open: %w", err) + } + defer r.Close() + + if form.Avatar.Size > setting.Avatar.MaxFileSize { + return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big")) + } + + data, err := io.ReadAll(r) + if err != nil { + return fmt.Errorf("io.ReadAll: %w", err) + } + st := typesniffer.DetectContentType(data) + if !(st.IsImage() && !st.IsSvgImage()) { + return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) + } + if err = repo_service.UploadAvatar(ctx, ctxRepo, data); err != nil { + return fmt.Errorf("UploadAvatar: %w", err) + } + return nil +} + +// SettingsAvatar save new POSTed repository avatar +func SettingsAvatar(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.AvatarForm) + form.Source = forms.AvatarLocal + if err := UpdateAvatarSetting(ctx, *form); err != nil { + ctx.Flash.Error(err.Error()) + } else { + ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success")) + } + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} + +// SettingsDeleteAvatar delete repository avatar +func SettingsDeleteAvatar(ctx *context.Context) { + if err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository); err != nil { + ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err)) + } + ctx.Redirect(ctx.Repo.RepoLink + "/settings") +} diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go new file mode 100644 index 0000000000000..8f2d30686207c --- /dev/null +++ b/routers/web/repo/setting/collaboration.go @@ -0,0 +1,210 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers/utils" + "code.gitea.io/gitea/services/mailer" + org_service "code.gitea.io/gitea/services/org" +) + +// Collaboration render a repository's collaboration page +func Collaboration(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.settings.collaboration") + ctx.Data["PageIsSettingsCollaboration"] = true + + users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{}) + if err != nil { + ctx.ServerError("GetCollaborators", err) + return + } + ctx.Data["Collaborators"] = users + + teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository) + if err != nil { + ctx.ServerError("GetRepoTeams", err) + return + } + ctx.Data["Teams"] = teams + ctx.Data["Repo"] = ctx.Repo.Repository + ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID + ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName + ctx.Data["Org"] = ctx.Repo.Repository.Owner + ctx.Data["Units"] = unit_model.Units + + ctx.HTML(http.StatusOK, tplCollaboration) +} + +// CollaborationPost response for actions for a collaboration of a repository +func CollaborationPost(ctx *context.Context) { + name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator"))) + if len(name) == 0 || ctx.Repo.Owner.LowerName == name { + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) + return + } + + u, err := user_model.GetUserByName(ctx, name) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Flash.Error(ctx.Tr("form.user_not_exist")) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) + } else { + ctx.ServerError("GetUserByName", err) + } + return + } + + if !u.IsActive { + ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user")) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) + return + } + + // Organization is not allowed to be added as a collaborator. + if u.IsOrganization() { + ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator")) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) + return + } + + if got, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, u.ID); err == nil && got { + ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_duplicate")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } + + // find the owner team of the organization the repo belongs too and + // check if the user we're trying to add is an owner. + if ctx.Repo.Repository.Owner.IsOrganization() { + if isOwner, err := organization.IsOrganizationOwner(ctx, ctx.Repo.Repository.Owner.ID, u.ID); err != nil { + ctx.ServerError("IsOrganizationOwner", err) + return + } else if isOwner { + ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_owner")) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) + return + } + } + + if err = repo_module.AddCollaborator(ctx, ctx.Repo.Repository, u); err != nil { + ctx.ServerError("AddCollaborator", err) + return + } + + if setting.Service.EnableNotifyMail { + mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository) + } + + ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) +} + +// ChangeCollaborationAccessMode response for changing access of a collaboration +func ChangeCollaborationAccessMode(ctx *context.Context) { + if err := repo_model.ChangeCollaborationAccessMode( + ctx, + ctx.Repo.Repository, + ctx.FormInt64("uid"), + perm.AccessMode(ctx.FormInt("mode"))); err != nil { + log.Error("ChangeCollaborationAccessMode: %v", err) + } +} + +// DeleteCollaboration delete a collaboration for a repository +func DeleteCollaboration(ctx *context.Context) { + if err := models.DeleteCollaboration(ctx.Repo.Repository, ctx.FormInt64("id")); err != nil { + ctx.Flash.Error("DeleteCollaboration: " + err.Error()) + } else { + ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) + } + + ctx.JSON(http.StatusOK, map[string]any{ + "redirect": ctx.Repo.RepoLink + "/settings/collaboration", + }) +} + +// AddTeamPost response for adding a team to a repository +func AddTeamPost(ctx *context.Context) { + if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() { + ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } + + name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("team"))) + if len(name) == 0 { + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } + + team, err := organization.OrgFromUser(ctx.Repo.Owner).GetTeam(ctx, name) + if err != nil { + if organization.IsErrTeamNotExist(err) { + ctx.Flash.Error(ctx.Tr("form.team_not_exist")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + } else { + ctx.ServerError("GetTeam", err) + } + return + } + + if team.OrgID != ctx.Repo.Repository.OwnerID { + ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } + + if organization.HasTeamRepo(ctx, ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) { + ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } + + if err = org_service.TeamAddRepository(team, ctx.Repo.Repository); err != nil { + ctx.ServerError("TeamAddRepository", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") +} + +// DeleteTeam response for deleting a team from a repository +func DeleteTeam(ctx *context.Context) { + if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() { + ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } + + team, err := organization.GetTeamByID(ctx, ctx.FormInt64("id")) + if err != nil { + ctx.ServerError("GetTeamByID", err) + return + } + + if err = models.RemoveRepository(team, ctx.Repo.Repository.ID); err != nil { + ctx.ServerError("team.RemoveRepositorys", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success")) + ctx.JSON(http.StatusOK, map[string]any{ + "redirect": ctx.Repo.RepoLink + "/settings/collaboration", + }) +} diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go new file mode 100644 index 0000000000000..d08c51f5e5c3b --- /dev/null +++ b/routers/web/repo/setting/deploy_key.go @@ -0,0 +1,111 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web" + asymkey_service "code.gitea.io/gitea/services/asymkey" + "code.gitea.io/gitea/services/forms" +) + +// DeployKeys render the deploy keys list of a repository page +func DeployKeys(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") + " / " + ctx.Tr("secrets.secrets") + ctx.Data["PageIsSettingsKeys"] = true + ctx.Data["DisableSSH"] = setting.SSH.Disabled + + keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) + if err != nil { + ctx.ServerError("ListDeployKeys", err) + return + } + ctx.Data["Deploykeys"] = keys + + ctx.HTML(http.StatusOK, tplDeployKeys) +} + +// DeployKeysPost response for adding a deploy key of a repository +func DeployKeysPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.AddKeyForm) + ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") + ctx.Data["PageIsSettingsKeys"] = true + ctx.Data["DisableSSH"] = setting.SSH.Disabled + + keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) + if err != nil { + ctx.ServerError("ListDeployKeys", err) + return + } + ctx.Data["Deploykeys"] = keys + + if ctx.HasError() { + ctx.HTML(http.StatusOK, tplDeployKeys) + return + } + + content, err := asymkey_model.CheckPublicKeyString(form.Content) + if err != nil { + if db.IsErrSSHDisabled(err) { + ctx.Flash.Info(ctx.Tr("settings.ssh_disabled")) + } else if asymkey_model.IsErrKeyUnableVerify(err) { + ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key")) + } else if err == asymkey_model.ErrKeyIsPrivate { + ctx.Data["HasError"] = true + ctx.Data["Err_Content"] = true + ctx.Flash.Error(ctx.Tr("form.must_use_public_key")) + } else { + ctx.Data["HasError"] = true + ctx.Data["Err_Content"] = true + ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error())) + } + ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") + return + } + + key, err := asymkey_model.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable) + if err != nil { + ctx.Data["HasError"] = true + switch { + case asymkey_model.IsErrDeployKeyAlreadyExist(err): + ctx.Data["Err_Content"] = true + ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form) + case asymkey_model.IsErrKeyAlreadyExist(err): + ctx.Data["Err_Content"] = true + ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form) + case asymkey_model.IsErrKeyNameAlreadyUsed(err): + ctx.Data["Err_Title"] = true + ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) + case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err): + ctx.Data["Err_Title"] = true + ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) + default: + ctx.ServerError("AddDeployKey", err) + } + return + } + + log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) + ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") +} + +// DeleteDeployKey response for deleting a deploy key +func DeleteDeployKey(ctx *context.Context) { + if err := asymkey_service.DeleteDeployKey(ctx.Doer, ctx.FormInt64("id")); err != nil { + ctx.Flash.Error("DeleteDeployKey: " + err.Error()) + } else { + ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success")) + } + + ctx.JSON(http.StatusOK, map[string]any{ + "redirect": ctx.Repo.RepoLink + "/settings/keys", + }) +} diff --git a/routers/web/repo/setting/git_hooks.go b/routers/web/repo/setting/git_hooks.go new file mode 100644 index 0000000000000..551327d44b479 --- /dev/null +++ b/routers/web/repo/setting/git_hooks.go @@ -0,0 +1,65 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" +) + +// GitHooks hooks of a repository +func GitHooks(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.settings.githooks") + ctx.Data["PageIsSettingsGitHooks"] = true + + hooks, err := ctx.Repo.GitRepo.Hooks() + if err != nil { + ctx.ServerError("Hooks", err) + return + } + ctx.Data["Hooks"] = hooks + + ctx.HTML(http.StatusOK, tplGithooks) +} + +// GitHooksEdit render for editing a hook of repository page +func GitHooksEdit(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.settings.githooks") + ctx.Data["PageIsSettingsGitHooks"] = true + + name := ctx.Params(":name") + hook, err := ctx.Repo.GitRepo.GetHook(name) + if err != nil { + if err == git.ErrNotValidHook { + ctx.NotFound("GetHook", err) + } else { + ctx.ServerError("GetHook", err) + } + return + } + ctx.Data["Hook"] = hook + ctx.HTML(http.StatusOK, tplGithookEdit) +} + +// GitHooksEditPost response for editing a git hook of a repository +func GitHooksEditPost(ctx *context.Context) { + name := ctx.Params(":name") + hook, err := ctx.Repo.GitRepo.GetHook(name) + if err != nil { + if err == git.ErrNotValidHook { + ctx.NotFound("GetHook", err) + } else { + ctx.ServerError("GetHook", err) + } + return + } + hook.Content = ctx.FormString("content") + if err = hook.Update(); err != nil { + ctx.ServerError("hook.Update", err) + return + } + ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git") +} diff --git a/routers/web/repo/lfs.go b/routers/web/repo/setting/lfs.go similarity index 99% rename from routers/web/repo/lfs.go rename to routers/web/repo/setting/lfs.go index 9957869c999a3..d478acdde0dbe 100644 --- a/routers/web/repo/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package repo +package setting import ( "bytes" diff --git a/routers/web/repo/setting/main_test.go b/routers/web/repo/setting/main_test.go new file mode 100644 index 0000000000000..5a6fa5621775b --- /dev/null +++ b/routers/web/repo/setting/main_test.go @@ -0,0 +1,17 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + GiteaRootPath: filepath.Join("..", "..", "..", ".."), + }) +} diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting/protected_branch.go similarity index 97% rename from routers/web/repo/setting_protected_branch.go rename to routers/web/repo/setting/protected_branch.go index 1a944799c23fc..777d52c858061 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -1,7 +1,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package repo +package setting import ( "fmt" @@ -286,7 +286,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { } // FIXME: since we only need to recheck files protected rules, we could improve this - matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) + matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName) if err != nil { ctx.ServerError("FindAllMatchedBranches", err) return @@ -307,7 +307,7 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { ruleID := ctx.ParamsInt64("id") if ruleID <= 0 { ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), }) return @@ -316,7 +316,7 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID) if err != nil { ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), }) return @@ -324,7 +324,7 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { if rule == nil { ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), }) return @@ -332,14 +332,14 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, ruleID); err != nil { ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName)) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), }) return } ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), }) } diff --git a/routers/web/repo/tag.go b/routers/web/repo/setting/protected_tag.go similarity index 97% rename from routers/web/repo/tag.go rename to routers/web/repo/setting/protected_tag.go index 95bc6dfce7f80..aafbd19e80ed2 100644 --- a/routers/web/repo/tag.go +++ b/routers/web/repo/setting/protected_tag.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package repo +package setting import ( "fmt" @@ -19,8 +19,12 @@ import ( "code.gitea.io/gitea/services/forms" ) +const ( + tplTags base.TplName = "repo/settings/tags" +) + // Tags render the page to protect tags -func Tags(ctx *context.Context) { +func ProtectedTags(ctx *context.Context) { if setTagsContext(ctx) != nil { return } diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting/setting.go similarity index 70% rename from routers/web/repo/setting.go rename to routers/web/repo/setting/setting.go index 4481ff4f04f11..b33660ffc9cc6 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting/setting.go @@ -2,22 +2,18 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package repo +package setting import ( - "errors" "fmt" - "io" "net/http" "strconv" "strings" "time" "code.gitea.io/gitea/models" - asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -32,17 +28,13 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/routers/utils" asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/forms" - "code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/migrations" mirror_service "code.gitea.io/gitea/services/mirror" - org_service "code.gitea.io/gitea/services/org" repo_service "code.gitea.io/gitea/services/repository" wiki_service "code.gitea.io/gitea/services/wiki" ) @@ -51,7 +43,6 @@ const ( tplSettingsOptions base.TplName = "repo/settings/options" tplCollaboration base.TplName = "repo/settings/collaboration" tplBranches base.TplName = "repo/settings/branches" - tplTags base.TplName = "repo/settings/tags" tplGithooks base.TplName = "repo/settings/githooks" tplGithookEdit base.TplName = "repo/settings/githook_edit" tplDeployKeys base.TplName = "repo/settings/deploy_keys" @@ -895,398 +886,6 @@ func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.R ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form) } -// Collaboration render a repository's collaboration page -func Collaboration(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("repo.settings.collaboration") - ctx.Data["PageIsSettingsCollaboration"] = true - - users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{}) - if err != nil { - ctx.ServerError("GetCollaborators", err) - return - } - ctx.Data["Collaborators"] = users - - teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository) - if err != nil { - ctx.ServerError("GetRepoTeams", err) - return - } - ctx.Data["Teams"] = teams - ctx.Data["Repo"] = ctx.Repo.Repository - ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID - ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName - ctx.Data["Org"] = ctx.Repo.Repository.Owner - ctx.Data["Units"] = unit_model.Units - - ctx.HTML(http.StatusOK, tplCollaboration) -} - -// CollaborationPost response for actions for a collaboration of a repository -func CollaborationPost(ctx *context.Context) { - name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator"))) - if len(name) == 0 || ctx.Repo.Owner.LowerName == name { - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) - return - } - - u, err := user_model.GetUserByName(ctx, name) - if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.Flash.Error(ctx.Tr("form.user_not_exist")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) - } else { - ctx.ServerError("GetUserByName", err) - } - return - } - - if !u.IsActive { - ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) - return - } - - // Organization is not allowed to be added as a collaborator. - if u.IsOrganization() { - ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) - return - } - - if got, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, u.ID); err == nil && got { - ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_duplicate")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - return - } - - // find the owner team of the organization the repo belongs too and - // check if the user we're trying to add is an owner. - if ctx.Repo.Repository.Owner.IsOrganization() { - if isOwner, err := organization.IsOrganizationOwner(ctx, ctx.Repo.Repository.Owner.ID, u.ID); err != nil { - ctx.ServerError("IsOrganizationOwner", err) - return - } else if isOwner { - ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_owner")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) - return - } - } - - if err = repo_module.AddCollaborator(ctx, ctx.Repo.Repository, u); err != nil { - ctx.ServerError("AddCollaborator", err) - return - } - - if setting.Service.EnableNotifyMail { - mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository) - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) -} - -// ChangeCollaborationAccessMode response for changing access of a collaboration -func ChangeCollaborationAccessMode(ctx *context.Context) { - if err := repo_model.ChangeCollaborationAccessMode( - ctx, - ctx.Repo.Repository, - ctx.FormInt64("uid"), - perm.AccessMode(ctx.FormInt("mode"))); err != nil { - log.Error("ChangeCollaborationAccessMode: %v", err) - } -} - -// DeleteCollaboration delete a collaboration for a repository -func DeleteCollaboration(ctx *context.Context) { - if err := models.DeleteCollaboration(ctx.Repo.Repository, ctx.FormInt64("id")); err != nil { - ctx.Flash.Error("DeleteCollaboration: " + err.Error()) - } else { - ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) - } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Repo.RepoLink + "/settings/collaboration", - }) -} - -// AddTeamPost response for adding a team to a repository -func AddTeamPost(ctx *context.Context) { - if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() { - ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - return - } - - name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("team"))) - if len(name) == 0 { - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - return - } - - team, err := organization.OrgFromUser(ctx.Repo.Owner).GetTeam(ctx, name) - if err != nil { - if organization.IsErrTeamNotExist(err) { - ctx.Flash.Error(ctx.Tr("form.team_not_exist")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - } else { - ctx.ServerError("GetTeam", err) - } - return - } - - if team.OrgID != ctx.Repo.Repository.OwnerID { - ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - return - } - - if organization.HasTeamRepo(ctx, ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) { - ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - return - } - - if err = org_service.TeamAddRepository(team, ctx.Repo.Repository); err != nil { - ctx.ServerError("TeamAddRepository", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") -} - -// DeleteTeam response for deleting a team from a repository -func DeleteTeam(ctx *context.Context) { - if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() { - ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - return - } - - team, err := organization.GetTeamByID(ctx, ctx.FormInt64("id")) - if err != nil { - ctx.ServerError("GetTeamByID", err) - return - } - - if err = models.RemoveRepository(team, ctx.Repo.Repository.ID); err != nil { - ctx.ServerError("team.RemoveRepositorys", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Repo.RepoLink + "/settings/collaboration", - }) -} - -// GitHooks hooks of a repository -func GitHooks(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("repo.settings.githooks") - ctx.Data["PageIsSettingsGitHooks"] = true - - hooks, err := ctx.Repo.GitRepo.Hooks() - if err != nil { - ctx.ServerError("Hooks", err) - return - } - ctx.Data["Hooks"] = hooks - - ctx.HTML(http.StatusOK, tplGithooks) -} - -// GitHooksEdit render for editing a hook of repository page -func GitHooksEdit(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("repo.settings.githooks") - ctx.Data["PageIsSettingsGitHooks"] = true - - name := ctx.Params(":name") - hook, err := ctx.Repo.GitRepo.GetHook(name) - if err != nil { - if err == git.ErrNotValidHook { - ctx.NotFound("GetHook", err) - } else { - ctx.ServerError("GetHook", err) - } - return - } - ctx.Data["Hook"] = hook - ctx.HTML(http.StatusOK, tplGithookEdit) -} - -// GitHooksEditPost response for editing a git hook of a repository -func GitHooksEditPost(ctx *context.Context) { - name := ctx.Params(":name") - hook, err := ctx.Repo.GitRepo.GetHook(name) - if err != nil { - if err == git.ErrNotValidHook { - ctx.NotFound("GetHook", err) - } else { - ctx.ServerError("GetHook", err) - } - return - } - hook.Content = ctx.FormString("content") - if err = hook.Update(); err != nil { - ctx.ServerError("hook.Update", err) - return - } - ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git") -} - -// DeployKeys render the deploy keys list of a repository page -func DeployKeys(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") + " / " + ctx.Tr("secrets.secrets") - ctx.Data["PageIsSettingsKeys"] = true - ctx.Data["DisableSSH"] = setting.SSH.Disabled - - keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) - if err != nil { - ctx.ServerError("ListDeployKeys", err) - return - } - ctx.Data["Deploykeys"] = keys - - ctx.HTML(http.StatusOK, tplDeployKeys) -} - -// DeployKeysPost response for adding a deploy key of a repository -func DeployKeysPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.AddKeyForm) - ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") - ctx.Data["PageIsSettingsKeys"] = true - ctx.Data["DisableSSH"] = setting.SSH.Disabled - - keys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) - if err != nil { - ctx.ServerError("ListDeployKeys", err) - return - } - ctx.Data["Deploykeys"] = keys - - if ctx.HasError() { - ctx.HTML(http.StatusOK, tplDeployKeys) - return - } - - content, err := asymkey_model.CheckPublicKeyString(form.Content) - if err != nil { - if db.IsErrSSHDisabled(err) { - ctx.Flash.Info(ctx.Tr("settings.ssh_disabled")) - } else if asymkey_model.IsErrKeyUnableVerify(err) { - ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key")) - } else if err == asymkey_model.ErrKeyIsPrivate { - ctx.Data["HasError"] = true - ctx.Data["Err_Content"] = true - ctx.Flash.Error(ctx.Tr("form.must_use_public_key")) - } else { - ctx.Data["HasError"] = true - ctx.Data["Err_Content"] = true - ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error())) - } - ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") - return - } - - key, err := asymkey_model.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable) - if err != nil { - ctx.Data["HasError"] = true - switch { - case asymkey_model.IsErrDeployKeyAlreadyExist(err): - ctx.Data["Err_Content"] = true - ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form) - case asymkey_model.IsErrKeyAlreadyExist(err): - ctx.Data["Err_Content"] = true - ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form) - case asymkey_model.IsErrKeyNameAlreadyUsed(err): - ctx.Data["Err_Title"] = true - ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) - case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err): - ctx.Data["Err_Title"] = true - ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) - default: - ctx.ServerError("AddDeployKey", err) - } - return - } - - log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) - ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") -} - -// DeleteDeployKey response for deleting a deploy key -func DeleteDeployKey(ctx *context.Context) { - if err := asymkey_service.DeleteDeployKey(ctx.Doer, ctx.FormInt64("id")); err != nil { - ctx.Flash.Error("DeleteDeployKey: " + err.Error()) - } else { - ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success")) - } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Repo.RepoLink + "/settings/keys", - }) -} - -// UpdateAvatarSetting update repo's avatar -func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error { - ctxRepo := ctx.Repo.Repository - - if form.Avatar == nil { - // No avatar is uploaded and we not removing it here. - // No random avatar generated here. - // Just exit, no action. - if ctxRepo.CustomAvatarRelativePath() == "" { - log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID) - } - return nil - } - - r, err := form.Avatar.Open() - if err != nil { - return fmt.Errorf("Avatar.Open: %w", err) - } - defer r.Close() - - if form.Avatar.Size > setting.Avatar.MaxFileSize { - return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big")) - } - - data, err := io.ReadAll(r) - if err != nil { - return fmt.Errorf("io.ReadAll: %w", err) - } - st := typesniffer.DetectContentType(data) - if !(st.IsImage() && !st.IsSvgImage()) { - return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) - } - if err = repo_service.UploadAvatar(ctx, ctxRepo, data); err != nil { - return fmt.Errorf("UploadAvatar: %w", err) - } - return nil -} - -// SettingsAvatar save new POSTed repository avatar -func SettingsAvatar(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.AvatarForm) - form.Source = forms.AvatarLocal - if err := UpdateAvatarSetting(ctx, *form); err != nil { - ctx.Flash.Error(err.Error()) - } else { - ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success")) - } - ctx.Redirect(ctx.Repo.RepoLink + "/settings") -} - -// SettingsDeleteAvatar delete repository avatar -func SettingsDeleteAvatar(ctx *context.Context) { - if err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository); err != nil { - ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err)) - } - ctx.Redirect(ctx.Repo.RepoLink + "/settings") -} - func selectPushMirrorByForm(ctx *context.Context, form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) { id, err := strconv.ParseInt(form.PushMirrorID, 10, 64) if err != nil { diff --git a/routers/web/repo/settings_test.go b/routers/web/repo/setting/settings_test.go similarity index 99% rename from routers/web/repo/settings_test.go rename to routers/web/repo/setting/settings_test.go index a33e92c82113c..6f7c844ce7a0d 100644 --- a/routers/web/repo/settings_test.go +++ b/routers/web/repo/setting/settings_test.go @@ -1,7 +1,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package repo +package setting import ( "net/http" diff --git a/routers/web/repo/webhook.go b/routers/web/repo/setting/webhook.go similarity index 99% rename from routers/web/repo/webhook.go rename to routers/web/repo/setting/webhook.go index b358c7260da9f..d85d5c8b07ff6 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -2,7 +2,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package repo +package setting import ( "errors" @@ -145,7 +145,7 @@ func WebhooksNew(ctx *context.Context) { return } if hookType == "discord" { - ctx.Data["DiscordHook"] = map[string]interface{}{ + ctx.Data["DiscordHook"] = map[string]any{ "Username": "Gitea", } } @@ -196,7 +196,7 @@ type webhookParams struct { Secret string HTTPMethod string WebhookForm forms.WebhookForm - Meta interface{} + Meta any } func createWebhook(ctx *context.Context, params webhookParams) { @@ -729,7 +729,7 @@ func DeleteWebhook(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/settings/hooks", }) } diff --git a/routers/web/repo/topic.go b/routers/web/repo/topic.go index 4c0b38bd91027..d22c3c6aa3748 100644 --- a/routers/web/repo/topic.go +++ b/routers/web/repo/topic.go @@ -15,7 +15,7 @@ import ( // TopicsPost response for creating repository func TopicsPost(ctx *context.Context) { if ctx.Doer == nil { - ctx.JSON(http.StatusForbidden, map[string]interface{}{ + ctx.JSON(http.StatusForbidden, map[string]any{ "message": "Only owners could change the topics.", }) return @@ -30,7 +30,7 @@ func TopicsPost(ctx *context.Context) { validTopics, invalidTopics := repo_model.SanitizeAndValidateTopics(topics) if len(validTopics) > 25 { - ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + ctx.JSON(http.StatusUnprocessableEntity, map[string]any{ "invalidTopics": nil, "message": ctx.Tr("repo.topic.count_prompt"), }) @@ -38,7 +38,7 @@ func TopicsPost(ctx *context.Context) { } if len(invalidTopics) > 0 { - ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + ctx.JSON(http.StatusUnprocessableEntity, map[string]any{ "invalidTopics": invalidTopics, "message": ctx.Tr("repo.topic.format_prompt"), }) @@ -48,13 +48,13 @@ func TopicsPost(ctx *context.Context) { err := repo_model.SaveTopics(ctx.Repo.Repository.ID, validTopics...) if err != nil { log.Error("SaveTopics failed: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + ctx.JSON(http.StatusInternalServerError, map[string]any{ "message": "Save topics failed.", }) return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "status": "ok", }) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index ad87bae9b8fa0..acea08d6297ec 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -977,6 +977,18 @@ func renderCode(ctx *context.Context) { return } + if ctx.Doer != nil { + if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil { + ctx.ServerError("GetBaseRepo", err) + return + } + ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID) + if err != nil { + ctx.ServerError("GetRecentlyPushedBranches", err) + return + } + } + var treeNames []string paths := make([]string, 0, 5) if len(ctx.Repo.TreePath) > 0 { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 22cfe6dd25873..4773e25c70664 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -12,7 +12,6 @@ import ( "net/url" "path/filepath" "strings" - "time" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" @@ -671,7 +670,7 @@ func WikiRaw(ctx *context.Context) { } if entry != nil { - if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, entry.Blob(), time.Time{}); err != nil { + if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, entry.Blob(), nil); err != nil { ctx.ServerError("ServeBlob", err) } return @@ -791,7 +790,7 @@ func DeleteWikiPagePost(ctx *context.Context) { notification.NotifyDeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName)) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.Repo.RepoLink + "/wiki/", }) } diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index d85879d1e5cf8..e1284fad6707f 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -62,7 +62,7 @@ func assertWikiNotExists(t *testing.T, repo *repo_model.Repository, wikiName wik assert.Nil(t, wikiEntry(t, repo, wikiName)) } -func assertPagesMetas(t *testing.T, expectedNames []string, metas interface{}) { +func assertPagesMetas(t *testing.T, expectedNames []string, metas any) { pageMetas, ok := metas.([]PageMeta) if !assert.True(t, ok) { return diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index c212c4ff25d4b..21e5a90d8f206 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -160,7 +160,7 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64, log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL) ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": failedRedirectTo, }) return @@ -170,7 +170,7 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64, ctx.Flash.Success(ctx.Tr("actions.runners.delete_runner_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": successRedirectTo, }) } diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 9594e6975a8ef..516c853b02e3e 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -4,35 +4,109 @@ package user import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" ) -func RenderUserHeader(ctx *context.Context) { - ctx.Data["IsProjectEnabled"] = true +// prepareContextForCommonProfile store some common data into context data for user's profile related pages (including the nav menu) +// It is designed to be fast and safe to be called multiple times in one request +func prepareContextForCommonProfile(ctx *context.Context) { ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["ContextUser"] = ctx.ContextUser - tab := ctx.FormString("tab") - ctx.Data["TabName"] = tab - repo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile") - if err == nil && !repo.IsEmpty { - gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) + ctx.Data["EnableFeed"] = setting.Other.EnableFeed + ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() +} + +// PrepareContextForProfileBigAvatar set the context for big avatar view on the profile page +func PrepareContextForProfileBigAvatar(ctx *context.Context) { + prepareContextForCommonProfile(ctx) + + ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx.Doer.ID, ctx.ContextUser.ID) + ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate + + // Show OpenID URIs + openIDs, err := user_model.GetUserOpenIDs(ctx.ContextUser.ID) + if err != nil { + ctx.ServerError("GetUserOpenIDs", err) + return + } + ctx.Data["OpenIDs"] = openIDs + + if len(ctx.ContextUser.Description) != 0 { + content, err := markdown.RenderString(&markup.RenderContext{ + URLPrefix: ctx.Repo.RepoLink, + Metas: map[string]string{"mode": "document"}, + GitRepo: ctx.Repo.GitRepo, + Ctx: ctx, + }, ctx.ContextUser.Description) if err != nil { - ctx.ServerError("OpenRepository", err) + ctx.ServerError("RenderString", err) return } - defer gitRepo.Close() - commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return + ctx.Data["RenderedDescription"] = content + } + + showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) + orgs, err := organization.FindOrgs(organization.FindOrgOptions{ + UserID: ctx.ContextUser.ID, + IncludePrivate: showPrivate, + }) + if err != nil { + ctx.ServerError("FindOrgs", err) + return + } + ctx.Data["Orgs"] = orgs + ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(orgs, ctx.Doer) + + badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser) + if err != nil { + ctx.ServerError("GetUserBadges", err) + return + } + ctx.Data["Badges"] = badges + + // in case the numbers are already provided by other functions, no need to query again (which is slow) + if _, ok := ctx.Data["NumFollowers"]; !ok { + _, ctx.Data["NumFollowers"], _ = user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1}) + } + if _, ok := ctx.Data["NumFollowing"]; !ok { + _, ctx.Data["NumFollowing"], _ = user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1}) + } +} + +func FindUserProfileReadme(ctx *context.Context) (profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) { + profileDbRepo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile") + if err == nil && !profileDbRepo.IsEmpty { + if profileGitRepo, err = git.OpenRepository(ctx, profileDbRepo.RepoPath()); err != nil { + log.Error("FindUserProfileReadme failed to OpenRepository: %v", err) + } else { + if commit, err := profileGitRepo.GetBranchCommit(profileDbRepo.DefaultBranch); err != nil { + log.Error("FindUserProfileReadme failed to GetBranchCommit: %v", err) + } else { + profileReadmeBlob, _ = commit.GetBlobByPath("README.md") + } } - blob, err := commit.GetBlobByPath("README.md") - if err == nil && blob != nil { - ctx.Data["ProfileReadme"] = true + } + return profileGitRepo, profileReadmeBlob, func() { + if profileGitRepo != nil { + _ = profileGitRepo.Close() } } } + +func RenderUserHeader(ctx *context.Context) { + prepareContextForCommonProfile(ctx) + + _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx) + defer profileClose() + ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil +} diff --git a/routers/web/user/code.go b/routers/web/user/code.go index 15524de7d651e..033f65c9c06c2 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/context" code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/setting" + shared_user "code.gitea.io/gitea/routers/web/shared/user" ) const ( @@ -23,8 +24,9 @@ func CodeSearch(ctx *context.Context) { ctx.Redirect(ctx.ContextUser.HomeLink()) return } + shared_user.PrepareContextForProfileBigAvatar(ctx) + shared_user.RenderUserHeader(ctx) - ctx.Data["IsProjectEnabled"] = true ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["Title"] = ctx.Tr("explore.code") diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 2513fc9a98383..5f1e0eb4277b8 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -97,7 +97,7 @@ func Dashboard(ctx *context.Context) { uid = ctxUser.ID } - ctx.PageData["dashboardRepoList"] = map[string]interface{}{ + ctx.PageData["dashboardRepoList"] = map[string]any{ "searchLimit": setting.UI.User.RepoPagingNum, "uid": uid, } @@ -725,7 +725,7 @@ func issueIDsFromSearch(ctx *context.Context, ctxUser *user_model.User, keyword if err != nil { return nil, fmt.Errorf("GetRepoIDsForIssuesOptions: %w", err) } - issueIDsFromSearch, err := issue_indexer.SearchIssuesByKeyword(ctx, searchRepoIDs, keyword) + issueIDsFromSearch, err := issue_indexer.SearchIssuesByKeyword(ctx, searchRepoIDs, keyword, ctx.FormString("state")) if err != nil { return nil, fmt.Errorf("SearchIssuesByKeyword: %w", err) } @@ -857,7 +857,7 @@ func UsernameSubRoute(ctx *context.Context) { context_service.UserAssignmentWeb()(ctx) if !ctx.Written() { ctx.Data["EnableFeed"] = setting.Other.EnableFeed - Profile(ctx) + OwnerProfile(ctx) } } } diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 81a26da827283..2e2c2a6e1f9db 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + packages_helper "code.gitea.io/gitea/routers/api/packages/helper" shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/forms" packages_service "code.gitea.io/gitea/services/packages" @@ -36,6 +37,7 @@ const ( // ListPackages displays a list of all packages of the context user func ListPackages(ctx *context.Context) { + shared_user.PrepareContextForProfileBigAvatar(ctx) page := ctx.FormInt("page") if page <= 1 { page = 1 @@ -258,6 +260,7 @@ func ViewPackageVersion(ctx *context.Context) { // ListPackageVersions lists all versions of a package func ListPackageVersions(ctx *context.Context) { + shared_user.PrepareContextForProfileBigAvatar(ctx) p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.Params("type")), ctx.Params("name")) if err != nil { if err == packages_model.ErrPackageNotExist { @@ -420,7 +423,13 @@ func PackageSettingsPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("packages.settings.delete.success")) } - ctx.Redirect(ctx.Package.Owner.HomeLink() + "/-/packages") + redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages" + // redirect to the package if there are still versions available + if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID}); has { + redirectURL = ctx.Package.Descriptor.PackageWebLink() + } + + ctx.Redirect(redirectURL) return } } @@ -437,18 +446,11 @@ func DownloadPackageFile(ctx *context.Context) { return } - s, _, err := packages_service.GetPackageFileStream( - ctx, - pf, - ) + s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) if err != nil { ctx.ServerError("GetPackageFileStream", err) return } - defer s.Close() - ctx.ServeContent(s, &context.ServeHeaderOptions{ - Filename: pf.Name, - LastModified: pf.CreatedUnix.AsLocalTime(), - }) + packages_helper.ServePackageFile(ctx, s, u, pf) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 6f9f84d60dbdf..07a2261c967d3 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -11,22 +11,22 @@ import ( activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/organization" - project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/routers/web/org" + shared_user "code.gitea.io/gitea/routers/web/shared/user" ) -// Profile render user's profile page -func Profile(ctx *context.Context) { +// OwnerProfile render profile page for a user or a organization (aka, repo owner) +func OwnerProfile(ctx *context.Context) { if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { feed.ShowUserFeedRSS(ctx) return @@ -38,36 +38,22 @@ func Profile(ctx *context.Context) { if ctx.ContextUser.IsOrganization() { org.Home(ctx) - return + } else { + userProfile(ctx) } +} +func userProfile(ctx *context.Context) { // check view permissions if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) { ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name)) return } - // advertise feed via meta tag - ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() - - // Show OpenID URIs - openIDs, err := user_model.GetUserOpenIDs(ctx.ContextUser.ID) - if err != nil { - ctx.ServerError("GetUserOpenIDs", err) - return - } - - var isFollowing bool - if ctx.Doer != nil { - isFollowing = user_model.IsFollowing(ctx.Doer.ID, ctx.ContextUser.ID) - } - ctx.Data["Title"] = ctx.ContextUser.DisplayName() ctx.Data["PageIsUserProfile"] = true - ctx.Data["ContextUser"] = ctx.ContextUser - ctx.Data["OpenIDs"] = openIDs - ctx.Data["IsFollowing"] = isFollowing + // prepare heatmap data if setting.Service.EnableUserHeatmap { data, err := activities_model.GetUserHeatmapDataByUser(ctx.ContextUser, ctx.Doer) if err != nil { @@ -78,75 +64,28 @@ func Profile(ctx *context.Context) { ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } - if len(ctx.ContextUser.Description) != 0 { - content, err := markdown.RenderString(&markup.RenderContext{ - URLPrefix: ctx.Repo.RepoLink, - Metas: map[string]string{"mode": "document"}, - GitRepo: ctx.Repo.GitRepo, - Ctx: ctx, - }, ctx.ContextUser.Description) - if err != nil { - ctx.ServerError("RenderString", err) - return - } - ctx.Data["RenderedDescription"] = content - } - - repo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile") - if err == nil && !repo.IsEmpty { - gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) - if err != nil { - ctx.ServerError("OpenRepository", err) - return - } - defer gitRepo.Close() - commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return - } - blob, err := commit.GetBlobByPath("README.md") - if err == nil { - bytes, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize) - if err != nil { - ctx.ServerError("GetBlobContent", err) - return - } - profileContent, err := markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - GitRepo: gitRepo, - }, bytes) - if err != nil { - ctx.ServerError("RenderString", err) - return - } - ctx.Data["ProfileReadme"] = profileContent - } - } + profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx) + defer profileClose() showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) + prepareUserProfileTabData(ctx, showPrivate, profileGitRepo, profileReadmeBlob) + // call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing + shared_user.PrepareContextForProfileBigAvatar(ctx) + ctx.HTML(http.StatusOK, tplProfile) +} - orgs, err := organization.FindOrgs(organization.FindOrgOptions{ - UserID: ctx.ContextUser.ID, - IncludePrivate: showPrivate, - }) - if err != nil { - ctx.ServerError("FindOrgs", err) - return - } - - ctx.Data["Orgs"] = orgs - ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(orgs, ctx.Doer) - - badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser) - if err != nil { - ctx.ServerError("GetUserBadges", err) - return - } - ctx.Data["Badges"] = badges - +func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGitRepo *git.Repository, profileReadme *git.Blob) { + // if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page tab := ctx.FormString("tab") + if tab == "" { + if profileReadme != nil { + tab = "overview" + } else { + tab = "repositories" + } + } ctx.Data["TabName"] = tab + ctx.Data["HasProfileReadme"] = profileReadme != nil page := ctx.FormInt("page") if page <= 0 { @@ -154,12 +93,7 @@ func Profile(ctx *context.Context) { } pagingNum := setting.UI.User.RepoPagingNum - if tab == "activity" { - pagingNum = setting.UI.FeedPagingNum - } - topicOnly := ctx.FormBool("topic") - var ( repos []*repo_model.Repository count int64 @@ -228,6 +162,7 @@ func Profile(ctx *context.Context) { total = int(count) case "activity": date := ctx.FormString("date") + pagingNum = setting.UI.FeedPagingNum items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ RequestedUser: ctx.ContextUser, Actor: ctx.Doer, @@ -271,16 +206,6 @@ func Profile(ctx *context.Context) { } total = int(count) - case "projects": - ctx.Data["OpenProjects"], _, err = project_model.FindProjects(ctx, project_model.SearchOptions{ - Page: -1, - IsClosed: util.OptionalBoolFalse, - Type: project_model.TypeIndividual, - }) - if err != nil { - ctx.ServerError("GetProjects", err) - return - } case "watching": repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ @@ -303,7 +228,17 @@ func Profile(ctx *context.Context) { } total = int(count) - default: + case "overview": + if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { + log.Error("failed to GetBlobContent: %v", err) + } else { + if profileContent, err := markdown.RenderString(&markup.RenderContext{Ctx: ctx, GitRepo: profileGitRepo}, bytes); err != nil { + log.Error("failed to RenderString: %v", err) + } else { + ctx.Data["ProfileReadme"] = profileContent + } + } + default: // default to "repositories" repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: pagingNum, @@ -339,13 +274,6 @@ func Profile(ctx *context.Context) { pager.AddParam(ctx, "date", "Date") } ctx.Data["Page"] = pager - ctx.Data["IsProjectEnabled"] = true - ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled - ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled - - ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate - - ctx.HTML(http.StatusOK, tplProfile) } // Action response for follow/unfollow user request @@ -359,9 +287,9 @@ func Action(ctx *context.Context) { } if err != nil { - ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.FormString("action")), err) + log.Error("Failed to apply action %q: %v", ctx.FormString("action"), err) + ctx.JSONError(fmt.Sprintf("Action %q failed", ctx.FormString("action"))) return } - // FIXME: We should check this URL and make sure that it's a valid Gitea URL - ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.ContextUser.HomeLink()) + ctx.JSONOK() } diff --git a/routers/web/user/search.go b/routers/web/user/search.go index bdc4116e3776b..fa2e52dd4194f 100644 --- a/routers/web/user/search.go +++ b/routers/web/user/search.go @@ -28,7 +28,7 @@ func Search(ctx *context.Context) { ListOptions: listOptions, }) if err != nil { - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + ctx.JSON(http.StatusInternalServerError, map[string]any{ "ok": false, "error": err.Error(), }) @@ -37,7 +37,7 @@ func Search(ctx *context.Context) { ctx.SetTotalCountHeader(maxResults) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "ok": true, "data": convert.ToUsers(ctx, ctx.Doer, users), }) diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index a67c2398fb0b7..532f0d8e39126 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -227,7 +227,7 @@ func DeleteEmail(ctx *context.Context) { log.Trace("Email address deleted: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.email_deletion_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/user/settings/account", }) } diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index f9e9ca5e52d31..81209376960d5 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -83,7 +83,7 @@ func DeleteApplication(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/user/settings/applications", }) } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 6debf95bbce06..d9412cae7cae8 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -256,7 +256,7 @@ func DeleteKey(ctx *context.Context) { ctx.Flash.Warning("Function not implemented") ctx.Redirect(setting.AppSubURL + "/user/settings/keys") } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/user/settings/keys", }) } diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index 5de0f0e22f427..923ce4b4367b9 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -138,7 +138,7 @@ func (oa *OAuth2CommonHandlers) DeleteApp(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("settings.remove_oauth2_application_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{"redirect": oa.BasePathList}) + ctx.JSON(http.StatusOK, map[string]any{"redirect": oa.BasePathList}) } // RevokeGrant revokes the grant @@ -149,5 +149,5 @@ func (oa *OAuth2CommonHandlers) RevokeGrant(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{"redirect": oa.BasePathList}) + ctx.JSON(http.StatusOK, map[string]any{"redirect": oa.BasePathList}) } diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index 08fcb6b62343c..f4133f3916026 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -112,7 +112,7 @@ func DeleteOpenID(ctx *context.Context) { log.Trace("OpenID address deleted: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success")) - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/user/settings/security", }) } diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index 6e6e7efb0b2c1..cc5f817a9d428 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -48,7 +48,7 @@ func DeleteAccountLink(ctx *context.Context) { } } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/user/settings/security", }) } diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index 826562f15748d..89ac184a47e7c 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -116,7 +116,7 @@ func WebauthnDelete(ctx *context.Context) { ctx.ServerError("GetWebAuthnCredentialByID", err) return } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/user/settings/security", }) } diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go index 9b0b0c9611c1a..db03d7b1ed20d 100644 --- a/routers/web/user/setting/webhooks.go +++ b/routers/web/user/setting/webhooks.go @@ -42,7 +42,7 @@ func DeleteWebhook(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": setting.AppSubURL + "/user/settings/hooks", }) } diff --git a/routers/web/user/task.go b/routers/web/user/task.go index 3818682403d0f..d92bf64af0fb3 100644 --- a/routers/web/user/task.go +++ b/routers/web/user/task.go @@ -17,12 +17,12 @@ func TaskStatus(ctx *context.Context) { task, opts, err := admin_model.GetMigratingTaskByID(ctx.ParamsInt64("task"), ctx.Doer.ID) if err != nil { if admin_model.IsErrTaskDoesNotExist(err) { - ctx.JSON(http.StatusNotFound, map[string]interface{}{ + ctx.JSON(http.StatusNotFound, map[string]any{ "error": "task `" + strconv.FormatInt(ctx.ParamsInt64("task"), 10) + "` does not exist", }) return } - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + ctx.JSON(http.StatusInternalServerError, map[string]any{ "err": err, }) return @@ -36,13 +36,13 @@ func TaskStatus(ctx *context.Context) { if err := json.Unmarshal([]byte(message), &translatableMessage); err != nil { translatableMessage = admin_model.TranslatableMessage{ Format: "migrate.migrating_failed.error", - Args: []interface{}{task.Message}, + Args: []any{task.Message}, } } message = ctx.Tr(translatableMessage.Format, translatableMessage.Args...) } - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "status": task.Status, "message": message, "repo-id": task.RepoID, diff --git a/routers/web/web.go b/routers/web/web.go index 26ad2d54c3a32..4f5901c0ec661 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -279,32 +279,32 @@ func registerRoutes(m *web.Route) { } addWebhookAddRoutes := func() { - m.Get("/{type}/new", repo.WebhooksNew) - m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost) - m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost) - m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost) - m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost) - m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost) - m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost) - m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost) - m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) - m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) - m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) - m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost) + m.Get("/{type}/new", repo_setting.WebhooksNew) + m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksNewPost) + m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksNewPost) + m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksNewPost) + m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo_setting.DiscordHooksNewPost) + m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo_setting.DingtalkHooksNewPost) + m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo_setting.TelegramHooksNewPost) + m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo_setting.MatrixHooksNewPost) + m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo_setting.MSTeamsHooksNewPost) + m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksNewPost) + m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksNewPost) + m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksNewPost) } addWebhookEditRoutes := func() { - m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) - m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) - m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) - m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) - m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) - m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost) - m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost) - m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) - m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost) - m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost) - m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost) + m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksEditPost) + m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksEditPost) + m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksEditPost) + m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo_setting.DiscordHooksEditPost) + m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo_setting.DingtalkHooksEditPost) + m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo_setting.TelegramHooksEditPost) + m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo_setting.MatrixHooksEditPost) + m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo_setting.MSTeamsHooksEditPost) + m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksEditPost) + m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksEditPost) + m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksEditPost) } addSettingVariablesRoutes := func() { @@ -515,8 +515,8 @@ func registerRoutes(m *web.Route) { m.Post("/delete", user_setting.DeleteWebhook) addWebhookAddRoutes() m.Group("/{id}", func() { - m.Get("", repo.WebHooksEdit) - m.Post("/replay/{uuid}", repo.ReplayWebhook) + m.Get("", repo_setting.WebHooksEdit) + m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) }) addWebhookEditRoutes() }, webhooksEnabled) @@ -604,8 +604,8 @@ func registerRoutes(m *web.Route) { m.Get("", admin.DefaultOrSystemWebhooks) m.Post("/delete", admin.DeleteDefaultOrSystemWebhook) m.Group("/{id}", func() { - m.Get("", repo.WebHooksEdit) - m.Post("/replay/{uuid}", repo.ReplayWebhook) + m.Get("", repo_setting.WebHooksEdit) + m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) }) addWebhookEditRoutes() }, webhooksEnabled) @@ -752,8 +752,8 @@ func registerRoutes(m *web.Route) { m.Post("/delete", org.DeleteWebhook) addWebhookAddRoutes() m.Group("/{id}", func() { - m.Get("", repo.WebHooksEdit) - m.Post("/replay/{uuid}", repo.ReplayWebhook) + m.Get("", repo_setting.WebHooksEdit) + m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) }) addWebhookEditRoutes() }, webhooksEnabled) @@ -874,78 +874,78 @@ func registerRoutes(m *web.Route) { m.Group("/{username}/{reponame}", func() { m.Group("/settings", func() { m.Group("", func() { - m.Combo("").Get(repo.Settings). - Post(web.Bind(forms.RepoSettingForm{}), repo.SettingsPost) - }, repo.SettingsCtxData) - m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo.SettingsAvatar) - m.Post("/avatar/delete", repo.SettingsDeleteAvatar) + m.Combo("").Get(repo_setting.Settings). + Post(web.Bind(forms.RepoSettingForm{}), repo_setting.SettingsPost) + }, repo_setting.SettingsCtxData) + m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar) + m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar) m.Group("/collaboration", func() { - m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost) - m.Post("/access_mode", repo.ChangeCollaborationAccessMode) - m.Post("/delete", repo.DeleteCollaboration) + m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost) + m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode) + m.Post("/delete", repo_setting.DeleteCollaboration) m.Group("/team", func() { - m.Post("", repo.AddTeamPost) - m.Post("/delete", repo.DeleteTeam) + m.Post("", repo_setting.AddTeamPost) + m.Post("/delete", repo_setting.DeleteTeam) }) }) m.Group("/branches", func() { - m.Post("/", repo.SetDefaultBranchPost) + m.Post("/", repo_setting.SetDefaultBranchPost) }, repo.MustBeNotEmpty) m.Group("/branches", func() { - m.Get("/", repo.ProtectedBranchRules) - m.Combo("/edit").Get(repo.SettingsProtectedBranch). - Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost) - m.Post("/{id}/delete", repo.DeleteProtectedBranchRulePost) + m.Get("/", repo_setting.ProtectedBranchRules) + m.Combo("/edit").Get(repo_setting.SettingsProtectedBranch). + Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.SettingsProtectedBranchPost) + m.Post("/{id}/delete", repo_setting.DeleteProtectedBranchRulePost) }, repo.MustBeNotEmpty) - m.Post("/rename_branch", web.Bind(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo.RenameBranchPost) + m.Post("/rename_branch", web.Bind(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.RenameBranchPost) m.Group("/tags", func() { - m.Get("", repo.Tags) - m.Post("", web.Bind(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo.NewProtectedTagPost) - m.Post("/delete", context.RepoMustNotBeArchived(), repo.DeleteProtectedTagPost) - m.Get("/{id}", repo.EditProtectedTag) - m.Post("/{id}", web.Bind(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo.EditProtectedTagPost) + m.Get("", repo_setting.ProtectedTags) + m.Post("", web.Bind(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo_setting.NewProtectedTagPost) + m.Post("/delete", context.RepoMustNotBeArchived(), repo_setting.DeleteProtectedTagPost) + m.Get("/{id}", repo_setting.EditProtectedTag) + m.Post("/{id}", web.Bind(forms.ProtectTagForm{}), context.RepoMustNotBeArchived(), repo_setting.EditProtectedTagPost) }) m.Group("/hooks/git", func() { - m.Get("", repo.GitHooks) - m.Combo("/{name}").Get(repo.GitHooksEdit). - Post(repo.GitHooksEditPost) + m.Get("", repo_setting.GitHooks) + m.Combo("/{name}").Get(repo_setting.GitHooksEdit). + Post(repo_setting.GitHooksEditPost) }, context.GitHookService()) m.Group("/hooks", func() { - m.Get("", repo.Webhooks) - m.Post("/delete", repo.DeleteWebhook) + m.Get("", repo_setting.Webhooks) + m.Post("/delete", repo_setting.DeleteWebhook) addWebhookAddRoutes() m.Group("/{id}", func() { - m.Get("", repo.WebHooksEdit) - m.Post("/test", repo.TestWebhook) - m.Post("/replay/{uuid}", repo.ReplayWebhook) + m.Get("", repo_setting.WebHooksEdit) + m.Post("/test", repo_setting.TestWebhook) + m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) }) addWebhookEditRoutes() }, webhooksEnabled) m.Group("/keys", func() { - m.Combo("").Get(repo.DeployKeys). - Post(web.Bind(forms.AddKeyForm{}), repo.DeployKeysPost) - m.Post("/delete", repo.DeleteDeployKey) + m.Combo("").Get(repo_setting.DeployKeys). + Post(web.Bind(forms.AddKeyForm{}), repo_setting.DeployKeysPost) + m.Post("/delete", repo_setting.DeleteDeployKey) }) m.Group("/lfs", func() { - m.Get("/", repo.LFSFiles) - m.Get("/show/{oid}", repo.LFSFileGet) - m.Post("/delete/{oid}", repo.LFSDelete) - m.Get("/pointers", repo.LFSPointerFiles) - m.Post("/pointers/associate", repo.LFSAutoAssociate) - m.Get("/find", repo.LFSFileFind) + m.Get("/", repo_setting.LFSFiles) + m.Get("/show/{oid}", repo_setting.LFSFileGet) + m.Post("/delete/{oid}", repo_setting.LFSDelete) + m.Get("/pointers", repo_setting.LFSPointerFiles) + m.Post("/pointers/associate", repo_setting.LFSAutoAssociate) + m.Get("/find", repo_setting.LFSFileFind) m.Group("/locks", func() { - m.Get("/", repo.LFSLocks) - m.Post("/", repo.LFSLockFile) - m.Post("/{lid}/unlock", repo.LFSUnlock) + m.Get("/", repo_setting.LFSLocks) + m.Post("/", repo_setting.LFSLockFile) + m.Post("/{lid}/unlock", repo_setting.LFSUnlock) }) }) m.Group("/actions", func() { @@ -1207,6 +1207,7 @@ func registerRoutes(m *web.Route) { Get(actions.View). Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) m.Post("/rerun", reqRepoActionsWriter, actions.RerunOne) + m.Get("/logs", actions.Logs) }) m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) m.Post("/approve", reqRepoActionsWriter, actions.Approve) @@ -1255,20 +1256,20 @@ func registerRoutes(m *web.Route) { m.Group("/blob_excerpt", func() { m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob) - }, func(ctx *context.Context) (cancel gocontext.CancelFunc) { + }, func(ctx *context.Context) gocontext.CancelFunc { if ctx.FormBool("wiki") { ctx.Data["PageIsWiki"] = true repo.MustEnableWiki(ctx) - return + return nil } reqRepoCodeReader(ctx) if ctx.Written() { - return + return nil } - cancel = context.RepoRef()(ctx) + cancel := context.RepoRef()(ctx) if ctx.Written() { - return + return cancel } repo.MustBeNotEmpty(ctx) @@ -1276,9 +1277,10 @@ func registerRoutes(m *web.Route) { }) m.Group("/pulls/{index}", func() { + m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue) m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) - m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) + m.Get("/commits", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) m.Post("/update", repo.UpdatePullRequest) diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go index 1442e09a31431..0966b04951a9b 100644 --- a/routers/web/webfinger.go +++ b/routers/web/webfinger.go @@ -18,18 +18,18 @@ import ( // https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4 type webfingerJRD struct { - Subject string `json:"subject,omitempty"` - Aliases []string `json:"aliases,omitempty"` - Properties map[string]interface{} `json:"properties,omitempty"` - Links []*webfingerLink `json:"links,omitempty"` + Subject string `json:"subject,omitempty"` + Aliases []string `json:"aliases,omitempty"` + Properties map[string]any `json:"properties,omitempty"` + Links []*webfingerLink `json:"links,omitempty"` } type webfingerLink struct { - Rel string `json:"rel,omitempty"` - Type string `json:"type,omitempty"` - Href string `json:"href,omitempty"` - Titles map[string]string `json:"titles,omitempty"` - Properties map[string]interface{} `json:"properties,omitempty"` + Rel string `json:"rel,omitempty"` + Type string `json:"type,omitempty"` + Href string `json:"href,omitempty"` + Titles map[string]string `json:"titles,omitempty"` + Properties map[string]any `json:"properties,omitempty"` } // WebfingerQuery returns information about a resource diff --git a/services/actions/clear_tasks.go b/services/actions/clear_tasks.go index 0616a5fc0dcb2..d2893e4f23e02 100644 --- a/services/actions/clear_tasks.go +++ b/services/actions/clear_tasks.go @@ -56,12 +56,20 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error { return nil }); err != nil { log.Warn("Cannot stop task %v: %v", task.ID, err) - // go on - } else if remove, err := actions.TransferLogs(ctx, task.LogFilename); err != nil { + continue + } + + remove, err := actions.TransferLogs(ctx, task.LogFilename) + if err != nil { log.Warn("Cannot transfer logs of task %v: %v", task.ID, err) - } else { - remove() + continue + } + task.LogInStorage = true + if err := actions_model.UpdateTask(ctx, task, "log_in_storage"); err != nil { + log.Warn("Cannot update task %v: %v", task.ID, err) + continue } + remove() } CreateCommitStatus(ctx, jobs...) diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 8e6cdcf680d1b..c4c2a0df29b74 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -152,7 +152,6 @@ func notify(ctx context.Context, input *notifyInput) error { } else { for _, wf := range workflows { if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget { - wf.Ref = ref detectedWorkflows = append(detectedWorkflows, wf) } } @@ -174,7 +173,6 @@ func notify(ctx context.Context, input *notifyInput) error { } else { for _, wf := range baseWorkflows { if wf.TriggerEvent == actions_module.GithubEventPullRequestTarget { - wf.Ref = baseRef detectedWorkflows = append(detectedWorkflows, wf) } } @@ -212,8 +210,8 @@ func notify(ctx context.Context, input *notifyInput) error { OwnerID: input.Repo.OwnerID, WorkflowID: dwf.EntryName, TriggerUserID: input.Doer.ID, - Ref: dwf.Ref, - CommitSHA: dwf.Commit.ID.String(), + Ref: ref, + CommitSHA: commit.ID.String(), IsForkPullRequest: isForkPullRequest, Event: input.Event, EventPayload: string(p), diff --git a/services/auth/source/db/authenticate.go b/services/auth/source/db/authenticate.go index 773ec601ba338..34a0459149a9e 100644 --- a/services/auth/source/db/authenticate.go +++ b/services/auth/source/db/authenticate.go @@ -4,19 +4,54 @@ package db import ( + "fmt" + "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) +// ErrUserPasswordNotSet represents a "ErrUserPasswordNotSet" kind of error. +type ErrUserPasswordNotSet struct { + UID int64 + Name string +} + +func (err ErrUserPasswordNotSet) Error() string { + return fmt.Sprintf("user's password isn't set [uid: %d, name: %s]", err.UID, err.Name) +} + +// Unwrap unwraps this error as a ErrInvalidArgument error +func (err ErrUserPasswordNotSet) Unwrap() error { + return util.ErrInvalidArgument +} + +// ErrUserPasswordInvalid represents a "ErrUserPasswordInvalid" kind of error. +type ErrUserPasswordInvalid struct { + UID int64 + Name string +} + +func (err ErrUserPasswordInvalid) Error() string { + return fmt.Sprintf("user's password is invalid [uid: %d, name: %s]", err.UID, err.Name) +} + +// Unwrap unwraps this error as a ErrInvalidArgument error +func (err ErrUserPasswordInvalid) Unwrap() error { + return util.ErrInvalidArgument +} + // Authenticate authenticates the provided user against the DB func Authenticate(user *user_model.User, login, password string) (*user_model.User, error) { if user == nil { return nil, user_model.ErrUserNotExist{Name: login} } - if !user.IsPasswordSet() || !user.ValidatePassword(password) { - return nil, user_model.ErrUserNotExist{UID: user.ID, Name: user.Name} + if !user.IsPasswordSet() { + return nil, ErrUserPasswordNotSet{UID: user.ID, Name: user.Name} + } else if !user.ValidatePassword(password) { + return nil, ErrUserPasswordInvalid{UID: user.ID, Name: user.Name} } // Update password hash if server password hash algorithm have changed diff --git a/services/auth/source/oauth2/jwtsigningkey.go b/services/auth/source/oauth2/jwtsigningkey.go index ed0fc67ca0f94..33bd3648e717f 100644 --- a/services/auth/source/oauth2/jwtsigningkey.go +++ b/services/auth/source/oauth2/jwtsigningkey.go @@ -38,8 +38,8 @@ func (err ErrInvalidAlgorithmType) Error() string { type JWTSigningKey interface { IsSymmetric() bool SigningMethod() jwt.SigningMethod - SignKey() interface{} - VerifyKey() interface{} + SignKey() any + VerifyKey() any ToJWK() (map[string]string, error) PreProcessToken(*jwt.Token) } @@ -57,11 +57,11 @@ func (key hmacSigningKey) SigningMethod() jwt.SigningMethod { return key.signingMethod } -func (key hmacSigningKey) SignKey() interface{} { +func (key hmacSigningKey) SignKey() any { return key.secret } -func (key hmacSigningKey) VerifyKey() interface{} { +func (key hmacSigningKey) VerifyKey() any { return key.secret } @@ -101,11 +101,11 @@ func (key rsaSingingKey) SigningMethod() jwt.SigningMethod { return key.signingMethod } -func (key rsaSingingKey) SignKey() interface{} { +func (key rsaSingingKey) SignKey() any { return key.key } -func (key rsaSingingKey) VerifyKey() interface{} { +func (key rsaSingingKey) VerifyKey() any { return key.key.Public() } @@ -152,11 +152,11 @@ func (key eddsaSigningKey) SigningMethod() jwt.SigningMethod { return key.signingMethod } -func (key eddsaSigningKey) SignKey() interface{} { +func (key eddsaSigningKey) SignKey() any { return key.key } -func (key eddsaSigningKey) VerifyKey() interface{} { +func (key eddsaSigningKey) VerifyKey() any { return key.key.Public() } @@ -203,11 +203,11 @@ func (key ecdsaSingingKey) SigningMethod() jwt.SigningMethod { return key.signingMethod } -func (key ecdsaSingingKey) SignKey() interface{} { +func (key ecdsaSingingKey) SignKey() any { return key.key } -func (key ecdsaSingingKey) VerifyKey() interface{} { +func (key ecdsaSingingKey) VerifyKey() any { return key.key.Public() } @@ -229,7 +229,7 @@ func (key ecdsaSingingKey) PreProcessToken(token *jwt.Token) { } // CreateJWTSigningKey creates a signing key from an algorithm / key pair. -func CreateJWTSigningKey(algorithm string, key interface{}) (JWTSigningKey, error) { +func CreateJWTSigningKey(algorithm string, key any) (JWTSigningKey, error) { var signingMethod jwt.SigningMethod switch algorithm { case "HS256": @@ -292,7 +292,7 @@ var DefaultSigningKey JWTSigningKey // InitSigningKey creates the default signing key from settings or creates a random key. func InitSigningKey() error { var err error - var key interface{} + var key any switch setting.OAuth2.JWTSigningAlgorithm { case "HS256": @@ -335,7 +335,7 @@ func InitSigningKey() error { // loadSymmetricKey checks if the configured secret is valid. // If it is not valid, it will return an error. -func loadSymmetricKey() (interface{}, error) { +func loadSymmetricKey() (any, error) { key := make([]byte, 32) n, err := base64.RawURLEncoding.Decode(key, []byte(setting.OAuth2.JWTSecretBase64)) if err != nil { @@ -350,7 +350,7 @@ func loadSymmetricKey() (interface{}, error) { // loadOrCreateAsymmetricKey checks if the configured private key exists. // If it does not exist a new random key gets generated and saved on the configured path. -func loadOrCreateAsymmetricKey() (interface{}, error) { +func loadOrCreateAsymmetricKey() (any, error) { keyPath := setting.OAuth2.JWTSigningPrivateKeyFile isExist, err := util.IsExist(keyPath) @@ -359,7 +359,7 @@ func loadOrCreateAsymmetricKey() (interface{}, error) { } if !isExist { err := func() error { - key, err := func() (interface{}, error) { + key, err := func() (any, error) { switch { case strings.HasPrefix(setting.OAuth2.JWTSigningAlgorithm, "RS"): return rsa.GenerateKey(rand.Reader, 4096) diff --git a/services/auth/source/oauth2/token.go b/services/auth/source/oauth2/token.go index c5a064054e5a2..612c9db8cf5f0 100644 --- a/services/auth/source/oauth2/token.go +++ b/services/auth/source/oauth2/token.go @@ -41,7 +41,7 @@ type Token struct { // ParseToken parses a signed jwt string func ParseToken(jwtToken string, signingKey JWTSigningKey) (*Token, error) { - parsedToken, err := jwt.ParseWithClaims(jwtToken, &Token{}, func(token *jwt.Token) (interface{}, error) { + parsedToken, err := jwt.ParseWithClaims(jwtToken, &Token{}, func(token *jwt.Token) (any, error) { if token.Method == nil || token.Method.Alg() != signingKey.SigningMethod().Alg() { return nil, fmt.Errorf("unexpected signing algo: %v", token.Header["alg"]) } diff --git a/services/context/user.go b/services/context/user.go index 4e74aa50bd1e2..62d2dc0aa2347 100644 --- a/services/context/user.go +++ b/services/context/user.go @@ -15,7 +15,7 @@ import ( // UserAssignmentWeb returns a middleware to handle context-user assignment for web routes func UserAssignmentWeb() func(ctx *context.Context) { return func(ctx *context.Context) { - errorFn := func(status int, title string, obj interface{}) { + errorFn := func(status int, title string, obj any) { err, ok := obj.(error) if !ok { err = fmt.Errorf("%s", obj) @@ -58,7 +58,7 @@ func UserAssignmentAPI() func(ctx *context.APIContext) { } } -func userAssignment(ctx *context.Base, doer *user_model.User, errCb func(int, string, interface{})) (contextUser *user_model.User) { +func userAssignment(ctx *context.Base, doer *user_model.User, errCb func(int, string, any)) (contextUser *user_model.User) { username := ctx.Params(":username") if doer != nil && doer.LowerName == strings.ToLower(username) { diff --git a/services/convert/convert.go b/services/convert/convert.go index bce0e7ba214b6..25c89747e3048 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -50,7 +50,7 @@ func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email { } // ToBranch convert a git.Commit and git.Branch to an api.Branch -func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { +func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { if bp == nil { var hasPerm bool var canPush bool @@ -65,11 +65,11 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c if err != nil { return nil, err } - canPush = issues_model.CanMaintainerWriteToBranch(perms, b.Name, user) + canPush = issues_model.CanMaintainerWriteToBranch(perms, branchName, user) } return &api.Branch{ - Name: b.Name, + Name: branchName, Commit: ToPayloadCommit(ctx, repo, c), Protected: false, RequiredApprovals: 0, @@ -81,7 +81,7 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c } branch := &api.Branch{ - Name: b.Name, + Name: branchName, Commit: ToPayloadCommit(ctx, repo, c), Protected: true, RequiredApprovals: bp.RequiredApprovals, diff --git a/services/cron/setting.go b/services/cron/setting.go index 952a4d17ab658..0656307cba0e9 100644 --- a/services/cron/setting.go +++ b/services/cron/setting.go @@ -14,7 +14,7 @@ type Config interface { IsEnabled() bool DoRunAtStart() bool GetSchedule() string - FormatMessage(locale translation.Locale, name, status, doer string, args ...interface{}) string + FormatMessage(locale translation.Locale, name, status, doer string, args ...any) string DoNoticeOnSuccess() bool } @@ -68,8 +68,8 @@ func (b *BaseConfig) DoNoticeOnSuccess() bool { // FormatMessage returns a message for the task // Please note the `status` string will be concatenated with `admin.dashboard.cron.` and `admin.dashboard.task.` to provide locale messages. Similarly `name` will be composed with `admin.dashboard.` to provide the locale name for the task. -func (b *BaseConfig) FormatMessage(locale translation.Locale, name, status, doer string, args ...interface{}) string { - realArgs := make([]interface{}, 0, len(args)+2) +func (b *BaseConfig) FormatMessage(locale translation.Locale, name, status, doer string, args ...any) string { + realArgs := make([]any, 0, len(args)+2) realArgs = append(realArgs, locale.Tr("admin.dashboard."+name)) if doer == "" { realArgs = append(realArgs, "(Cron)") diff --git a/services/externalaccount/link.go b/services/externalaccount/link.go index dcdc57ee489d8..a19d4c5ab3eb7 100644 --- a/services/externalaccount/link.go +++ b/services/externalaccount/link.go @@ -13,8 +13,8 @@ import ( // Store represents a thing that stores things type Store interface { - Get(interface{}) interface{} - Set(interface{}, interface{}) error + Get(any) any + Set(any, any) error Release() error } diff --git a/services/forms/user_form_hidden_comments.go b/services/forms/user_form_hidden_comments.go index 7eb800a020185..03e629a553ac0 100644 --- a/services/forms/user_form_hidden_comments.go +++ b/services/forms/user_form_hidden_comments.go @@ -90,7 +90,7 @@ func IsUserHiddenCommentTypeGroupChecked(group string, hiddenCommentTypes *big.I commentTypes, ok := hiddenCommentTypeGroups[group] if !ok { log.Critical("the group map for hidden comment types is out of sync, unknown group: %v", group) - return + return false } if hiddenCommentTypes == nil { return false diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 9adf3b940093e..9e1db6fd4353c 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -779,7 +779,7 @@ func skipToNextDiffHead(input *bufio.Reader) (line string, err error) { for { lineBytes, isFragment, err = input.ReadLine() if err != nil { - return + return "", err } if wasFragment { wasFragment = isFragment @@ -795,7 +795,7 @@ func skipToNextDiffHead(input *bufio.Reader) (line string, err error) { var tail string tail, err = input.ReadString('\n') if err != nil { - return + return "", err } line += tail } @@ -821,22 +821,21 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio _, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF - err = fmt.Errorf("unable to ReadLine: %w", err) - return + return nil, false, fmt.Errorf("unable to ReadLine: %w", err) } } sb.Reset() lineBytes, isFragment, err = input.ReadLine() if err != nil { if err == io.EOF { - return + return lineBytes, isFragment, err } err = fmt.Errorf("unable to ReadLine: %w", err) - return + return nil, false, err } if lineBytes[0] == 'd' { // End of hunks - return + return lineBytes, isFragment, err } switch lineBytes[0] { @@ -853,8 +852,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF - err = fmt.Errorf("unable to ReadLine: %w", err) - return + return nil, false, fmt.Errorf("unable to ReadLine: %w", err) } _, _ = sb.Write(lineBytes) } @@ -884,8 +882,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio } // This is used only to indicate that the current file does not have a terminal newline if !bytes.Equal(lineBytes, []byte("\\ No newline at end of file")) { - err = fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) - return + return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) } // Technically this should be the end the file! // FIXME: we should be putting a marker at the end of the file if there is no terminal new line @@ -953,8 +950,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio curSection.Lines = append(curSection.Lines, diffLine) default: // This is unexpected - err = fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) - return + return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) } line := string(lineBytes) @@ -965,8 +961,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF - err = fmt.Errorf("unable to ReadLine: %w", err) - return + return lineBytes, isFragment, fmt.Errorf("unable to ReadLine: %w", err) } } } @@ -1229,6 +1224,42 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff return diff, nil } +type PullDiffStats struct { + TotalAddition, TotalDeletion int +} + +// GetPullDiffStats +func GetPullDiffStats(gitRepo *git.Repository, opts *DiffOptions) (*PullDiffStats, error) { + repoPath := gitRepo.Path + + diff := &PullDiffStats{} + + separator := "..." + if opts.DirectComparison { + separator = ".." + } + + diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} + if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA { + diffPaths = []string{git.EmptyTreeSHA, opts.AfterCommitID} + } + + var err error + + _, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) + if err != nil && strings.Contains(err.Error(), "no merge base") { + // git >= 2.28 now returns an error if base and head have become unrelated. + // previously it would return the results of git diff --shortstat base head so let's try that... + diffPaths = []string{opts.BeforeCommitID, opts.AfterCommitID} + _, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) + } + if err != nil { + return nil, err + } + + return diff, nil +} + // SyncAndGetUserSpecificDiff is like GetDiff, except that user specific data such as which files the given user has already viewed on the given PR will also be set // Additionally, the database asynchronously is updated if files have changed since the last review func SyncAndGetUserSpecificDiff(ctx context.Context, userID int64, pull *issues_model.PullRequest, gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff, error) { diff --git a/services/issue/assignee.go b/services/issue/assignee.go index 943a761e28290..8fe35b560203e 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -46,13 +46,12 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID) if err != nil { - return + return false, nil, err } - assignee, err1 := user_model.GetUserByID(ctx, assigneeID) - if err1 != nil { - err = err1 - return + assignee, err := user_model.GetUserByID(ctx, assigneeID) + if err != nil { + return false, nil, err } notification.NotifyIssueChangeAssignee(ctx, doer, issue, assignee, removed, comment) @@ -236,23 +235,23 @@ func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *use } if err != nil { - return + return nil, err } if comment == nil || !isAdd { - return + return nil, nil } // notify all user in this team - if err = comment.LoadIssue(ctx); err != nil { - return + if err := comment.LoadIssue(ctx); err != nil { + return nil, err } members, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ TeamID: reviewer.ID, }) if err != nil { - return + return nil, err } for _, member := range members { diff --git a/services/issue/issue.go b/services/issue/issue.go index 61890c75def09..b6c6a26cbdc93 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -49,17 +49,17 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo } // ChangeTitle changes the title of this issue, as the given user. -func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, title string) (err error) { +func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, title string) error { oldTitle := issue.Title issue.Title = title - if err = issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil { - return + if err := issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil { + return err } if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) { - if err = issues_model.PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest); err != nil { - return + if err := issues_model.PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest); err != nil { + return err } } diff --git a/services/issue/label.go b/services/issue/label.go index c18abbfcda3d2..ee821a49c94d3 100644 --- a/services/issue/label.go +++ b/services/issue/label.go @@ -12,9 +12,9 @@ import ( ) // ClearLabels clears all of an issue's labels -func ClearLabels(issue *issues_model.Issue, doer *user_model.User) (err error) { - if err = issues_model.ClearIssueLabels(issue, doer); err != nil { - return +func ClearLabels(issue *issues_model.Issue, doer *user_model.User) error { + if err := issues_model.ClearIssueLabels(issue, doer); err != nil { + return err } notification.NotifyIssueClearLabels(db.DefaultContext, doer, issue) diff --git a/services/lfs/server.go b/services/lfs/server.go index b32f218785edc..cc0ec73105bd7 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -375,7 +375,7 @@ func VerifyHandler(ctx *context.Context) { writeStatus(ctx, status) } -func decodeJSON(req *http.Request, v interface{}) error { +func decodeJSON(req *http.Request, v any) error { defer req.Body.Close() dec := json.NewDecoder(req.Body) @@ -552,7 +552,7 @@ func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repo if !strings.Contains(tokenSHA, ".") { return nil, nil } - token, err := jwt.ParseWithClaims(tokenSHA, &Claims{}, func(t *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenSHA, &Claims{}, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go index 2653e80586d71..eade0cf271bf8 100644 --- a/services/mailer/incoming/incoming.go +++ b/services/mailer/incoming/incoming.go @@ -205,7 +205,7 @@ loop: if err := c.Store( handledSet, imap.FormatFlagsOp(imap.AddFlags, true), - []interface{}{imap.DeletedFlag}, + []any{imap.DeletedFlag}, nil, ); err != nil { return fmt.Errorf("imap store failed: %w", err) diff --git a/services/mailer/incoming/payload/payload.go b/services/mailer/incoming/payload/payload.go index eb82f5c3ed321..00ada7826bdaf 100644 --- a/services/mailer/incoming/payload/payload.go +++ b/services/mailer/incoming/payload/payload.go @@ -20,7 +20,7 @@ const ( ) // CreateReferencePayload creates data which GetReferenceFromPayload resolves to the reference again. -func CreateReferencePayload(reference interface{}) ([]byte, error) { +func CreateReferencePayload(reference any) ([]byte, error) { var refType payloadReferenceType var refID int64 @@ -44,7 +44,7 @@ func CreateReferencePayload(reference interface{}) ([]byte, error) { } // GetReferenceFromPayload resolves the reference from the payload -func GetReferenceFromPayload(ctx context.Context, payload []byte) (interface{}, error) { +func GetReferenceFromPayload(ctx context.Context, payload []byte) (any, error) { if len(payload) < 1 { return nil, util.NewInvalidArgumentErrorf("payload to small") } diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 351b79b5df284..50d59a44527d1 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -67,7 +67,7 @@ func SendTestMail(email string) error { // sendUserMail sends a mail to the user func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, subject, info string) { locale := translation.NewLocale(language) - data := map[string]interface{}{ + data := map[string]any{ "DisplayName": u.DisplayName(), "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale), "ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, locale), @@ -118,7 +118,7 @@ func SendActivateEmailMail(u *user_model.User, email *user_model.EmailAddress) { return } locale := translation.NewLocale(u.Language) - data := map[string]interface{}{ + data := map[string]any{ "DisplayName": u.DisplayName(), "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale), "Code": u.GenerateEmailActivateCode(email.Email), @@ -151,7 +151,7 @@ func SendRegisterNotifyMail(u *user_model.User) { } locale := translation.NewLocale(u.Language) - data := map[string]interface{}{ + data := map[string]any{ "DisplayName": u.DisplayName(), "Username": u.Name, "Language": locale.Language(), @@ -184,7 +184,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) repoName := repo.FullName() subject := locale.Tr("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName) - data := map[string]interface{}{ + data := map[string]any{ "Subject": subject, "RepoName": repoName, "Link": repo.HTMLURL(), @@ -258,7 +258,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient } locale := translation.NewLocale(lang) - mailMeta := map[string]interface{}{ + mailMeta := map[string]any{ "FallbackSubject": fallback, "Body": body, "Link": link, diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index ebf9285b0a2a1..fb638ebd42cf1 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -68,7 +68,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo } subject := locale.Tr("mail.release.new.subject", rel.TagName, rel.Repo.FullName()) - mailMeta := map[string]interface{}{ + mailMeta := map[string]any{ "Release": rel, "Subject": subject, "Language": locale.Language(), diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 9b2f24faa812d..e9c1991b5b80e 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -64,7 +64,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U subject = locale.Tr("mail.repo.transfer.subject_to", doer.DisplayName(), repo.FullName(), destination) } - data := map[string]interface{}{ + data := map[string]any{ "Doer": doer, "User": repo.Owner, "Repo": repo.FullName(), diff --git a/services/mailer/mail_team_invite.go b/services/mailer/mail_team_invite.go index 917e1844356d0..b6f47ee9215bc 100644 --- a/services/mailer/mail_team_invite.go +++ b/services/mailer/mail_team_invite.go @@ -34,7 +34,7 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod locale := translation.NewLocale(inviter.Language) subject := locale.Tr("mail.team_invite.subject", inviter.DisplayName(), org.DisplayName()) - mailMeta := map[string]interface{}{ + mailMeta := map[string]any{ "Inviter": inviter, "Organization": org, "Team": team, diff --git a/services/migrations/codebase.go b/services/migrations/codebase.go index 22bf0f73de715..492fc908e904e 100644 --- a/services/migrations/codebase.go +++ b/services/migrations/codebase.go @@ -127,7 +127,7 @@ func (d *CodebaseDownloader) FormatCloneURL(opts base.MigrateOptions, remoteAddr return opts.CloneAddr, nil } -func (d *CodebaseDownloader) callAPI(endpoint string, parameter map[string]string, result interface{}) error { +func (d *CodebaseDownloader) callAPI(endpoint string, parameter map[string]string, result any) error { u, err := d.baseURL.Parse(endpoint) if err != nil { return err diff --git a/services/migrations/common.go b/services/migrations/common.go index 34d7c93ddf0b7..4f9837472d9dc 100644 --- a/services/migrations/common.go +++ b/services/migrations/common.go @@ -14,7 +14,7 @@ import ( ) // WarnAndNotice will log the provided message and send a repository notice -func WarnAndNotice(fmtStr string, args ...interface{}) { +func WarnAndNotice(fmtStr string, args ...any) { log.Warn(fmtStr, args...) if err := system_model.CreateRepositoryNotice(fmt.Sprintf(fmtStr, args...)); err != nil { log.Error("create repository notice failed: ", err) diff --git a/services/migrations/dump.go b/services/migrations/dump.go index cc8518d4a25c6..603954810cffa 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -112,7 +112,7 @@ func (g *RepositoryDumper) CreateRepo(repo *base.Repository, opts base.MigrateOp } defer f.Close() - bs, err := yaml.Marshal(map[string]interface{}{ + bs, err := yaml.Marshal(map[string]any{ "name": repo.Name, "owner": repo.Owner, "description": repo.Description, @@ -227,7 +227,7 @@ func (g *RepositoryDumper) CreateTopics(topics ...string) error { } defer f.Close() - bs, err := yaml.Marshal(map[string]interface{}{ + bs, err := yaml.Marshal(map[string]any{ "topics": topics, }) if err != nil { @@ -380,7 +380,7 @@ func (g *RepositoryDumper) CreateIssues(issues ...*base.Issue) error { return nil } -func (g *RepositoryDumper) createItems(dir string, itemFiles map[int64]*os.File, itemsMap map[int64][]interface{}) error { +func (g *RepositoryDumper) createItems(dir string, itemFiles map[int64]*os.File, itemsMap map[int64][]any) error { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return err } @@ -394,7 +394,7 @@ func (g *RepositoryDumper) createItems(dir string, itemFiles map[int64]*os.File, return nil } -func (g *RepositoryDumper) encodeItems(number int64, items []interface{}, dir string, itemFiles map[int64]*os.File) error { +func (g *RepositoryDumper) encodeItems(number int64, items []any, dir string, itemFiles map[int64]*os.File) error { itemFile := itemFiles[number] if itemFile == nil { var err error @@ -413,7 +413,7 @@ func (g *RepositoryDumper) encodeItems(number int64, items []interface{}, dir st // CreateComments creates comments of issues func (g *RepositoryDumper) CreateComments(comments ...*base.Comment) error { - commentsMap := make(map[int64][]interface{}, len(comments)) + commentsMap := make(map[int64][]any, len(comments)) for _, comment := range comments { commentsMap[comment.IssueIndex] = append(commentsMap[comment.IssueIndex], comment) } @@ -621,7 +621,7 @@ func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error { // CreateReviews create pull request reviews func (g *RepositoryDumper) CreateReviews(reviews ...*base.Review) error { - reviewsMap := make(map[int64][]interface{}, len(reviews)) + reviewsMap := make(map[int64][]any, len(reviews)) for _, review := range reviews { reviewsMap[review.IssueIndex] = append(reviewsMap[review.IssueIndex], review) } @@ -642,7 +642,7 @@ func (g *RepositoryDumper) Finish() error { // DumpRepository dump repository according MigrateOptions to a local directory func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error { - doer, err := user_model.GetAdminUser() + doer, err := user_model.GetAdminUser(ctx) if err != nil { return err } @@ -705,7 +705,7 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) error { // RestoreRepository restore a repository from the disk directory func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error { - doer, err := user_model.GetAdminUser() + doer, err := user_model.GetAdminUser(ctx) if err != nil { return err } diff --git a/services/migrations/onedev.go b/services/migrations/onedev.go index 33fc43c349619..e2f7b771f369e 100644 --- a/services/migrations/onedev.go +++ b/services/migrations/onedev.go @@ -121,7 +121,7 @@ func (d *OneDevDownloader) LogString() string { return fmt.Sprintf("", d.baseURL, d.repoID, d.repoName) } -func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, result interface{}) error { +func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, result any) error { u, err := d.baseURL.Parse(endpoint) if err != nil { return err @@ -400,9 +400,9 @@ func (d *OneDevDownloader) GetComments(commentable base.Commentable) ([]*base.Co } rawChanges := make([]struct { - Date time.Time `json:"date"` - UserID int64 `json:"userId"` - Data map[string]interface{} `json:"data"` + Date time.Time `json:"date"` + UserID int64 `json:"userId"` + Data map[string]any `json:"data"` }, 0, 100) if context.IsPullRequest { diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 35ba09521b663..abce1d3c2d45e 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -41,7 +41,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { } log.Trace("Doing: Update") - handler := func(idx int, bean interface{}) error { + handler := func(idx int, bean any) error { var repo *repo_model.Repository var mirrorType mirror_module.SyncType var referenceID int64 @@ -91,7 +91,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { pullMirrorsRequested := 0 if pullLimit != 0 { - if err := repo_model.MirrorsIterate(pullLimit, func(idx int, bean interface{}) error { + if err := repo_model.MirrorsIterate(pullLimit, func(idx int, bean any) error { if err := handler(idx, bean); err != nil { return err } @@ -105,7 +105,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { pushMirrorsRequested := 0 if pushLimit != 0 { - if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean interface{}) error { + if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean any) error { if err := handler(idx, bean); err != nil { return err } diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 53ab632b01c19..51c7de58b645f 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -307,6 +307,11 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo return nil, false } + log.Trace("SyncMirrors [repo: %-v]: syncing branches...", m.Repo) + if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, m.Repo, gitRepo, 0); err != nil { + log.Error("SyncMirrors [repo: %-v]: failed to synchronize branches: %v", m.Repo, err) + } + log.Trace("SyncMirrors [repo: %-v]: syncing releases with tags...", m.Repo) if err = repo_module.SyncReleasesWithTags(m.Repo, gitRepo); err != nil { log.Error("SyncMirrors [repo: %-v]: failed to synchronize tags to releases: %v", m.Repo, err) diff --git a/services/packages/auth.go b/services/packages/auth.go index a7acdaf1c3a9b..41d3a0a82511e 100644 --- a/services/packages/auth.go +++ b/services/packages/auth.go @@ -53,7 +53,7 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) { return 0, fmt.Errorf("split token failed") } - token, err := jwt.ParseWithClaims(parts[1], &packageClaims{}, func(t *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(parts[1], &packageClaims{}, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } diff --git a/services/packages/packages.go b/services/packages/packages.go index 23aa8a5c31526..bdc56efeefa81 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "net/url" "strings" "code.gitea.io/gitea/models/db" @@ -20,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/notification" packages_module "code.gitea.io/gitea/modules/packages" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" ) @@ -42,7 +44,7 @@ type PackageCreationInfo struct { PackageInfo SemverCompatible bool Creator *user_model.User - Metadata interface{} + Metadata any PackageProperties map[string]string VersionProperties map[string]string } @@ -562,70 +564,62 @@ func DeletePackageFile(ctx context.Context, pf *packages_model.PackageFile) erro } // GetFileStreamByPackageNameAndVersion returns the content of the specific package file -func GetFileStreamByPackageNameAndVersion(ctx context.Context, pvi *PackageInfo, pfi *PackageFileInfo) (io.ReadSeekCloser, *packages_model.PackageFile, error) { +func GetFileStreamByPackageNameAndVersion(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) if err != nil { if err == packages_model.ErrPackageNotExist { - return nil, nil, err + return nil, nil, nil, err } log.Error("Error getting package: %v", err) - return nil, nil, err + return nil, nil, nil, err } return GetFileStreamByPackageVersion(ctx, pv, pfi) } -// GetFileStreamByPackageVersionAndFileID returns the content of the specific package file -func GetFileStreamByPackageVersionAndFileID(ctx context.Context, owner *user_model.User, versionID, fileID int64) (io.ReadSeekCloser, *packages_model.PackageFile, error) { - log.Trace("Getting package file stream: %v, %v, %v", owner.ID, versionID, fileID) - - pv, err := packages_model.GetVersionByID(ctx, versionID) - if err != nil { - if err != packages_model.ErrPackageNotExist { - log.Error("Error getting package version: %v", err) - } - return nil, nil, err - } - - p, err := packages_model.GetPackageByID(ctx, pv.PackageID) - if err != nil { - log.Error("Error getting package: %v", err) - return nil, nil, err - } - - if p.OwnerID != owner.ID { - return nil, nil, packages_model.ErrPackageNotExist - } - - pf, err := packages_model.GetFileForVersionByID(ctx, versionID, fileID) - if err != nil { - log.Error("Error getting file: %v", err) - return nil, nil, err - } - - return GetPackageFileStream(ctx, pf) -} - // GetFileStreamByPackageVersion returns the content of the specific package file -func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pfi *PackageFileInfo) (io.ReadSeekCloser, *packages_model.PackageFile, error) { +func GetFileStreamByPackageVersion(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, err + return nil, nil, nil, err } return GetPackageFileStream(ctx, pf) } // GetPackageFileStream returns the content of the specific package file -func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadSeekCloser, *packages_model.PackageFile, error) { +func GetPackageFileStream(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, err + return nil, nil, nil, err + } + + return GetPackageBlobStream(ctx, pf, pb) +} + +// GetPackageBlobStream returns the content of the specific package blob +// 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) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { + key := packages_module.BlobHash256Key(pb.HashSHA256) + + cs := packages_module.NewContentStore() + + var s io.ReadSeekCloser + var u *url.URL + var err error + + if cs.ShouldServeDirect() { + u, err = cs.GetServeDirectURL(key, pf.Name) + if err != nil && !errors.Is(err, storage.ErrURLNotSupported) { + log.Error("Error getting serve direct url: %v", err) + } + } + if u == nil { + s, err = cs.Get(key) } - s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256)) if err == nil { if pf.IsLead { if err := packages_model.IncrementDownloadCounter(ctx, pf.VersionID); err != nil { @@ -633,7 +627,7 @@ func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) ( } } } - return s, pf, err + return s, u, pf, err } // RemoveAllPackages for User diff --git a/services/pull/pull.go b/services/pull/pull.go index f44e690ab7087..0f562b9ee35da 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -170,7 +170,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer return err } if branchesEqual { - return models.ErrBranchesEqual{ + return git_model.ErrBranchesEqual{ HeadBranchName: pr.HeadBranch, BaseBranchName: targetBranch, } @@ -338,7 +338,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, for _, pr := range prs { divergence, err := GetDiverging(ctx, pr) if err != nil { - if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { + if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch) } else { log.Error("GetDiverging: %v", err) diff --git a/services/pull/review.go b/services/pull/review.go index 6feffe4ec4e7a..e920bc8c1652b 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -320,7 +320,7 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos func DismissReview(ctx context.Context, reviewID, repoID int64, message string, doer *user_model.User, isDismiss, dismissPriors bool) (comment *issues_model.Comment, err error) { review, err := issues_model.GetReviewByID(ctx, reviewID) if err != nil { - return + return nil, err } if review.Type != issues_model.ReviewTypeApprove && review.Type != issues_model.ReviewTypeReject { @@ -328,7 +328,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, } // load data for notify - if err = review.LoadAttributes(ctx); err != nil { + if err := review.LoadAttributes(ctx); err != nil { return nil, err } @@ -337,8 +337,8 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, return nil, fmt.Errorf("reviews's repository is not the same as the one we expect") } - if err = issues_model.DismissReview(review, isDismiss); err != nil { - return + if err := issues_model.DismissReview(review, isDismiss); err != nil { + return nil, err } if dismissPriors { @@ -361,11 +361,11 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, return nil, nil } - if err = review.Issue.LoadPullRequest(ctx); err != nil { - return + if err := review.Issue.LoadPullRequest(ctx); err != nil { + return nil, err } - if err = review.Issue.LoadAttributes(ctx); err != nil { - return + if err := review.Issue.LoadAttributes(ctx); err != nil { + return nil, err } comment, err = issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{ @@ -377,7 +377,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, Repo: review.Issue.Repo, }) if err != nil { - return + return nil, err } comment.Review = review @@ -386,5 +386,5 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, notification.NotifyPullReviewDismiss(ctx, doer, review, comment) - return comment, err + return comment, nil } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 146470780671e..db32940e3835a 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" + git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" @@ -181,7 +181,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) Run(prCtx.RunOpts()); err != nil { cancel() if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { - return nil, nil, models.ErrBranchDoesNotExist{ + return nil, nil, git_model.ErrBranchNotExist{ BranchName: pr.HeadBranch, } } diff --git a/services/pull/update.go b/services/pull/update.go index b977dbdba9fe2..bc8c4a25e5f61 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" @@ -168,7 +167,7 @@ func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.Diver log.Trace("GetDiverging[%-v]: compare commits", pr) prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) if err != nil { - if !models.IsErrBranchDoesNotExist(err) { + if !git_model.IsErrBranchNotExist(err) { log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err) } return nil, err diff --git a/services/release/release.go b/services/release/release.go index c1190305b6688..1ccbd9c81163e 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -187,7 +187,7 @@ func CreateNewTag(ctx context.Context, doer *user_model.User, repo *repo_model.R // editAttachments accept a map of attachment uuid to new attachment name which will be updated with attachments. func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_model.Release, addAttachmentUUIDs, delAttachmentUUIDs []string, editAttachments map[string]string, -) (err error) { +) error { if rel.ID == 0 { return errors.New("UpdateRelease only accepts an exist release") } @@ -264,8 +264,8 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod } } - if err = committer.Commit(); err != nil { - return + if err := committer.Commit(); err != nil { + return err } for _, uuid := range delAttachmentUUIDs { @@ -280,14 +280,14 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod if !isCreated { notification.NotifyUpdateRelease(gitRepo.Ctx, doer, rel) - return + return nil } if !rel.IsDraft { notification.NotifyNewRelease(gitRepo.Ctx, rel) } - return err + return nil } // DeleteReleaseByID deletes a release and corresponding Git tag by given ID. diff --git a/services/repository/adopt.go b/services/repository/adopt.go index e07ff35041915..f95fb5988f66b 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -12,6 +12,7 @@ import ( "strings" "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" @@ -146,7 +147,15 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r } } } - branches, _, _ := gitRepo.GetBranchNames(0, 0) + + branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ + RepoID: repo.ID, + ListOptions: db.ListOptions{ + ListAll: true, + }, + IsDeletedBranch: util.OptionalBoolFalse, + }) + found := false hasDefault := false hasMaster := false diff --git a/services/repository/branch.go b/services/repository/branch.go index 4e560786dbc56..11a8b20531578 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -10,13 +10,21 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" 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" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" + "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/util" + files_service "code.gitea.io/gitea/services/repository/files" + + "xorm.io/builder" ) // CreateNewBranch creates a new repository branch @@ -27,7 +35,7 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode } if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) { - return models.ErrBranchDoesNotExist{ + return git_model.ErrBranchNotExist{ BranchName: oldBranchName, } } @@ -40,16 +48,165 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } - return fmt.Errorf("Push: %w", err) + return fmt.Errorf("push: %w", err) } return nil } -// GetBranches returns branches from the repository, skipping skip initial branches and -// returning at most limit branches, or all branches if limit is 0. -func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) { - return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit) +// Branch contains the branch information +type Branch struct { + DBBranch *git_model.Branch + IsProtected bool + IsIncluded bool + CommitsAhead int + CommitsBehind int + LatestPullRequest *issues_model.PullRequest + MergeMovedOn bool +} + +// LoadBranches loads branches from the repository limited by page & pageSize. +func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, page, pageSize int) (*Branch, []*Branch, int64, error) { + defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch) + if err != nil { + return nil, nil, 0, err + } + + branchOpts := git_model.FindBranchOptions{ + RepoID: repo.ID, + IsDeletedBranch: isDeletedBranch, + ListOptions: db.ListOptions{ + Page: page, + PageSize: pageSize, + }, + } + + totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts) + if err != nil { + return nil, nil, 0, err + } + + branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch} + + dbBranches, err := git_model.FindBranches(ctx, branchOpts) + if err != nil { + return nil, nil, 0, err + } + + if err := dbBranches.LoadDeletedBy(ctx); err != nil { + return nil, nil, 0, err + } + if err := dbBranches.LoadPusher(ctx); err != nil { + return nil, nil, 0, err + } + + rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID) + if err != nil { + return nil, nil, 0, err + } + + repoIDToRepo := map[int64]*repo_model.Repository{} + repoIDToRepo[repo.ID] = repo + + repoIDToGitRepo := map[int64]*git.Repository{} + repoIDToGitRepo[repo.ID] = gitRepo + + branches := make([]*Branch, 0, len(dbBranches)) + for i := range dbBranches { + branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo) + if err != nil { + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) + } + + branches = append(branches, branch) + } + + // Always add the default branch + log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name) + defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo) + if err != nil { + return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err) + } + + return defaultBranch, branches, totalNumOfBranches, nil +} + +func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules, + repoIDToRepo map[int64]*repo_model.Repository, + repoIDToGitRepo map[int64]*git.Repository, +) (*Branch, error) { + log.Trace("loadOneBranch: '%s'", dbBranch.Name) + + branchName := dbBranch.Name + p := protectedBranches.GetFirstMatched(branchName) + isProtected := p != nil + + divergence := &git.DivergeObject{ + Ahead: -1, + Behind: -1, + } + + // it's not default branch + if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted { + var err error + divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName) + if err != nil { + log.Error("CountDivergingCommits: %v", err) + } + } + + pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName) + if err != nil { + return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err) + } + headCommit := dbBranch.CommitID + + mergeMovedOn := false + if pr != nil { + pr.HeadRepo = repo + if err := pr.LoadIssue(ctx); err != nil { + return nil, fmt.Errorf("LoadIssue: %v", err) + } + if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { + pr.BaseRepo = repo + } else if err := pr.LoadBaseRepo(ctx); err != nil { + return nil, fmt.Errorf("LoadBaseRepo: %v", err) + } else { + repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo + } + pr.Issue.Repo = pr.BaseRepo + + if pr.HasMerged { + baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] + if !ok { + baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %v", err) + } + defer baseGitRepo.Close() + repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo + } + pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil && !git.IsErrNotExist(err) { + return nil, fmt.Errorf("GetBranchCommitID: %v", err) + } + if err == nil && headCommit != pullCommit { + // the head has moved on from the merge - we shouldn't delete + mergeMovedOn = true + } + } + } + + isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName + return &Branch{ + DBBranch: dbBranch, + IsProtected: isProtected, + IsIncluded: isIncluded, + CommitsAhead: divergence.Ahead, + CommitsBehind: divergence.Behind, + LatestPullRequest: pr, + MergeMovedOn: mergeMovedOn, + }, nil } func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) { @@ -62,17 +219,17 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri branchRefName := strings.TrimPrefix(refName, git.BranchPrefix) switch { case branchRefName == name: - return models.ErrBranchAlreadyExists{ + return git_model.ErrBranchAlreadyExists{ BranchName: name, } // If branchRefName like a/b but we want to create a branch named a then we have a conflict case strings.HasPrefix(branchRefName, name+"/"): - return models.ErrBranchNameConflict{ + return git_model.ErrBranchNameConflict{ BranchName: branchRefName, } // Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict case strings.HasPrefix(name, branchRefName+"/"): - return models.ErrBranchNameConflict{ + return git_model.ErrBranchNameConflict{ BranchName: branchRefName, } case refName == git.TagPrefix+name: @@ -101,7 +258,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } - return fmt.Errorf("Push: %w", err) + return fmt.Errorf("push: %w", err) } return nil @@ -169,13 +326,28 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return git_model.ErrBranchIsProtected } + rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName) + if err != nil { + return fmt.Errorf("GetBranch: %vc", err) + } + + if rawBranch.IsDeleted { + return nil + } + commit, err := gitRepo.GetBranchCommit(branchName) if err != nil { return err } - if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ - Force: true, + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { + return err + } + + return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ + Force: true, + }) }); err != nil { return err } @@ -196,3 +368,45 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return nil } + +type BranchSyncOptions struct { + RepoID int64 +} + +// branchSyncQueue represents a queue to handle branch sync jobs. +var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions] + +func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions { + for _, opts := range items { + _, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0) + if err != nil { + log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err) + } + } + return nil +} + +func addRepoToBranchSyncQueue(repoID, doerID int64) error { + return branchSyncQueue.Push(&BranchSyncOptions{ + RepoID: repoID, + }) +} + +func initBranchSyncQueue(ctx context.Context) error { + branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync) + if branchSyncQueue == nil { + return errors.New("unable to create branch_sync queue") + } + go graceful.GetManager().RunWithCancel(branchSyncQueue) + + return nil +} + +func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error { + if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error { + return addRepoToBranchSyncQueue(repo.ID, doerID) + }); err != nil { + return fmt.Errorf("run sync all branches failed: %v", err) + } + return nil +} diff --git a/services/repository/files/content.go b/services/repository/files/content.go index c701431d6785d..30d62fbcdf7c0 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -40,9 +40,9 @@ func (ct *ContentType) String() string { // GetContentsOrList gets the meta data 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, treePath, ref string) (interface{}, error) { +func GetContentsOrList(ctx context.Context, repo *repo_model.Repository, treePath, ref string) (any, error) { if repo.IsEmpty { - return make([]interface{}, 0), nil + return make([]any, 0), nil } if ref == "" { ref = repo.DefaultBranch diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 19d089b9e49c2..fdf0b32f1a1d3 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -58,7 +58,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode if opts.NewBranch != opts.OldBranch { existingBranch, err := gitRepo.GetBranch(opts.NewBranch) if existingBranch != nil { - return models.ErrBranchAlreadyExists{ + return git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 01bf2ace00933..737f914dd6845 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -197,7 +197,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use if opts.NewBranch != opts.OldBranch { existingBranch, err := gitRepo.GetBranch(opts.NewBranch) if existingBranch != nil { - return nil, models.ErrBranchAlreadyExists{ + return nil, git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } diff --git a/services/repository/fork.go b/services/repository/fork.go index fb93b10f1c312..59aa173373314 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -157,7 +157,15 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork if err = repo_module.CreateDelegateHooks(repoPath); err != nil { return fmt.Errorf("createDelegateHooks: %w", err) } - return nil + + gitRepo, err := git.OpenRepository(txCtx, repo.RepoPath()) + if err != nil { + return fmt.Errorf("OpenRepository: %w", err) + } + defer gitRepo.Close() + + _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID) + return err }) needsRollbackInPanic = false if err != nil { diff --git a/services/repository/lfs.go b/services/repository/lfs.go index aeb808a72f330..0bd4d53a5c4da 100644 --- a/services/repository/lfs.go +++ b/services/repository/lfs.go @@ -19,7 +19,7 @@ import ( // GarbageCollectLFSMetaObjectsOptions provides options for GarbageCollectLFSMetaObjects function type GarbageCollectLFSMetaObjectsOptions struct { - Logger log.Logger + LogDetail func(format string, v ...any) AutoFix bool OlderThan time.Time UpdatedLessRecentlyThan time.Time @@ -32,10 +32,12 @@ func GarbageCollectLFSMetaObjects(ctx context.Context, opts GarbageCollectLFSMet log.Trace("Doing: GarbageCollectLFSMetaObjects") defer log.Trace("Finished: GarbageCollectLFSMetaObjects") + if opts.LogDetail == nil { + opts.LogDetail = log.Debug + } + if !setting.LFS.StartServer { - if opts.Logger != nil { - opts.Logger.Info("LFS support is disabled") - } + opts.LogDetail("LFS support is disabled") return nil } @@ -54,21 +56,17 @@ func GarbageCollectLFSMetaObjects(ctx context.Context, opts GarbageCollectLFSMet // GarbageCollectLFSMetaObjectsForRepo garbage collects LFS objects for a specific repository func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.Repository, opts GarbageCollectLFSMetaObjectsOptions) error { - if opts.Logger != nil { - opts.Logger.Info("Checking %-v", repo) - } + opts.LogDetail("Checking %-v", repo) total, orphaned, collected, deleted := int64(0), 0, 0, 0 - if opts.Logger != nil { - defer func() { - if orphaned == 0 { - opts.Logger.Info("Found %d total LFSMetaObjects in %-v", total, repo) - } else if !opts.AutoFix { - opts.Logger.Info("Found %d/%d orphaned LFSMetaObjects in %-v", orphaned, total, repo) - } else { - opts.Logger.Info("Collected %d/%d orphaned/%d total LFSMetaObjects in %-v. %d removed from storage.", collected, orphaned, total, repo, deleted) - } - }() - } + defer func() { + if orphaned == 0 { + opts.LogDetail("Found %d total LFSMetaObjects in %-v", total, repo) + } else if !opts.AutoFix { + opts.LogDetail("Found %d/%d orphaned LFSMetaObjects in %-v", orphaned, total, repo) + } else { + opts.LogDetail("Collected %d/%d orphaned/%d total LFSMetaObjects in %-v. %d removed from storage.", collected, orphaned, total, repo, deleted) + } + }() gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) if err != nil { @@ -129,9 +127,7 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R }) if err == errStop { - if opts.Logger != nil { - opts.Logger.Info("Processing stopped at %d total LFSMetaObjects in %-v", total, repo) - } + opts.LogDetail("Processing stopped at %d total LFSMetaObjects in %-v", total, repo) return nil } else if err != nil { return err diff --git a/services/repository/push.go b/services/repository/push.go index e559d3f904cbe..8e4bab156293c 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -93,7 +93,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { defer gitRepo.Close() if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) + return fmt.Errorf("Failed to update size for repository: %v", err) } addTags := make([]string, 0, len(optsList)) @@ -259,8 +259,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { notification.NotifyPushCommits(ctx, pusher, repo, opts, commits) - if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil { - log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) + if err = git_model.UpdateBranch(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil { + return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err) } // Cache for big repository @@ -273,8 +273,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { // close all related pulls log.Error("close related pull request failed: %v", err) } - if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, opts.OldCommitID, pusher.ID); err != nil { - log.Warn("AddDeletedBranch: %v", err) + + if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, pusher.ID); err != nil { + return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err) } } diff --git a/services/repository/repository.go b/services/repository/repository.go index 0914a8f6ec6ad..cd3658dcd8e06 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -17,6 +17,7 @@ import ( 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/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" @@ -100,7 +101,10 @@ func Init() error { } system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) - return initPushQueue() + if err := initPushQueue(); err != nil { + return err + } + return initBranchSyncQueue(graceful.GetManager().ShutdownContext()) } // UpdateRepository updates a repository diff --git a/services/task/migrate.go b/services/task/migrate.go index 98454482b5a6a..bebdb5078b26f 100644 --- a/services/task/migrate.go +++ b/services/task/migrate.go @@ -121,7 +121,7 @@ func runMigrateTask(t *admin_model.Task) (err error) { } }() - t.Repo, err = migrations.MigrateRepository(ctx, t.Doer, t.Owner.Name, *opts, func(format string, args ...interface{}) { + t.Repo, err = migrations.MigrateRepository(ctx, t.Doer, t.Owner.Name, *opts, func(format string, args ...any) { message := admin_model.TranslatableMessage{ Format: format, Args: args, diff --git a/templates/admin/base/search.tmpl b/templates/admin/base/search.tmpl index bc684e76579aa..19977f05a9bcc 100644 --- a/templates/admin/base/search.tmpl +++ b/templates/admin/base/search.tmpl @@ -1,6 +1,12 @@ -