diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 04eb0236345..0dd9a6687d8 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -16,10 +16,10 @@ parserOptions: parser: "@typescript-eslint/parser" # for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser settings: - import/extensions: [".js", ".ts"] - import/parsers: + import-x/extensions: [".js", ".ts"] + import-x/parsers: "@typescript-eslint/parser": [".js", ".ts"] - import/resolver: + import-x/resolver: typescript: true plugins: @@ -28,7 +28,7 @@ plugins: - "@typescript-eslint/eslint-plugin" - eslint-plugin-array-func - eslint-plugin-github - - eslint-plugin-i + - eslint-plugin-import-x - eslint-plugin-no-jquery - eslint-plugin-no-use-extend-native - eslint-plugin-regexp @@ -58,15 +58,15 @@ overrides: no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top] - files: ["*.config.*"] rules: - i/no-unused-modules: [0] + import-x/no-unused-modules: [0] - files: ["**/*.d.ts"] rules: - i/no-unused-modules: [0] + import-x/no-unused-modules: [0] "@typescript-eslint/consistent-type-definitions": [0] "@typescript-eslint/consistent-type-imports": [0] - files: ["web_src/js/types.ts"] rules: - i/no-unused-modules: [0] + import-x/no-unused-modules: [0] - files: ["**/*.test.*", "web_src/js/test/setup.ts"] env: vitest-globals/env: true @@ -394,49 +394,49 @@ rules: id-blacklist: [0] id-length: [0] id-match: [0] - i/consistent-type-specifier-style: [0] - i/default: [0] - i/dynamic-import-chunkname: [0] - i/export: [2] - i/exports-last: [0] - i/extensions: [2, always, {ignorePackages: true}] - i/first: [2] - i/group-exports: [0] - i/max-dependencies: [0] - i/named: [2] - i/namespace: [0] - i/newline-after-import: [0] - i/no-absolute-path: [0] - i/no-amd: [2] - i/no-anonymous-default-export: [0] - i/no-commonjs: [2] - i/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] - i/no-default-export: [0] - i/no-deprecated: [0] - i/no-dynamic-require: [0] - i/no-empty-named-blocks: [2] - i/no-extraneous-dependencies: [2] - i/no-import-module-exports: [0] - i/no-internal-modules: [0] - i/no-mutable-exports: [0] - i/no-named-as-default-member: [0] - i/no-named-as-default: [0] - i/no-named-default: [0] - i/no-named-export: [0] - i/no-namespace: [0] - i/no-nodejs-modules: [0] - i/no-relative-packages: [0] - i/no-relative-parent-imports: [0] - i/no-restricted-paths: [0] - i/no-self-import: [2] - i/no-unassigned-import: [0] - i/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}] - i/no-unused-modules: [2, {unusedExports: true}] - i/no-useless-path-segments: [2, {commonjs: true}] - i/no-webpack-loader-syntax: [2] - i/order: [0] - i/prefer-default-export: [0] - i/unambiguous: [0] + import-x/consistent-type-specifier-style: [0] + import-x/default: [0] + import-x/dynamic-import-chunkname: [0] + import-x/export: [2] + import-x/exports-last: [0] + import-x/extensions: [2, always, {ignorePackages: true}] + import-x/first: [2] + import-x/group-exports: [0] + import-x/max-dependencies: [0] + import-x/named: [2] + import-x/namespace: [0] + import-x/newline-after-import: [0] + import-x/no-absolute-path: [0] + import-x/no-amd: [2] + import-x/no-anonymous-default-export: [0] + import-x/no-commonjs: [2] + import-x/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] + import-x/no-default-export: [0] + import-x/no-deprecated: [0] + import-x/no-dynamic-require: [0] + import-x/no-empty-named-blocks: [2] + import-x/no-extraneous-dependencies: [2] + import-x/no-import-module-exports: [0] + import-x/no-internal-modules: [0] + import-x/no-mutable-exports: [0] + import-x/no-named-as-default-member: [0] + import-x/no-named-as-default: [0] + import-x/no-named-default: [0] + import-x/no-named-export: [0] + import-x/no-namespace: [0] + import-x/no-nodejs-modules: [0] + import-x/no-relative-packages: [0] + import-x/no-relative-parent-imports: [0] + import-x/no-restricted-paths: [0] + import-x/no-self-import: [2] + import-x/no-unassigned-import: [0] + import-x/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}] + import-x/no-unused-modules: [2, {unusedExports: true}] + import-x/no-useless-path-segments: [2, {commonjs: true}] + import-x/no-webpack-loader-syntax: [2] + import-x/order: [0] + import-x/prefer-default-export: [0] + import-x/unambiguous: [0] init-declarations: [0] line-comment-position: [0] logical-assignment-operators: [0] diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 023fb05a296..2adea23aa40 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -3,3 +3,5 @@ self-hosted-runner: - actuated-4cpu-8gb - actuated-4cpu-16gb - nscloud + - namespace-profile-gitea-release-docker + - namespace-profile-gitea-release-binary diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 6e1b6e07584..2264c9e822d 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -10,7 +10,7 @@ concurrency: jobs: nightly-binary: - runs-on: nscloud + runs-on: namespace-profile-gitea-release-binary steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -58,7 +58,7 @@ jobs: run: | aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress nightly-docker-rootful: - runs-on: ubuntu-latest + runs-on: namespace-profile-gitea-release-docker steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -95,7 +95,7 @@ jobs: push: true tags: gitea/gitea:${{ steps.clean_name.outputs.branch }} nightly-docker-rootless: - runs-on: ubuntu-latest + runs-on: namespace-profile-gitea-release-docker steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 41037df29cb..a406602dc0a 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -11,7 +11,7 @@ concurrency: jobs: binary: - runs-on: nscloud + runs-on: namespace-profile-gitea-release-binary steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -68,7 +68,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} docker-rootful: - runs-on: ubuntu-latest + runs-on: namespace-profile-gitea-release-docker steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -99,7 +99,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} docker-rootless: - runs-on: ubuntu-latest + runs-on: namespace-profile-gitea-release-docker steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index a23e6982000..f67b76f4087 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -13,7 +13,7 @@ concurrency: jobs: binary: - runs-on: nscloud + runs-on: namespace-profile-gitea-release-binary steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -70,7 +70,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} docker-rootful: - runs-on: ubuntu-latest + runs-on: namespace-profile-gitea-release-docker steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -105,7 +105,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} docker-rootless: - runs-on: ubuntu-latest + runs-on: namespace-profile-gitea-release-docker steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions diff --git a/go.mod b/go.mod index 80b62ce83f8..671151d4b63 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/ethantkoenig/rupture v1.0.1 github.com/felixge/fgprof v0.9.5 github.com/fsnotify/fsnotify v1.7.0 - github.com/gliderlabs/ssh v0.3.7 + github.com/gliderlabs/ssh v0.3.8 github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-chi/chi/v5 v5.1.0 @@ -121,13 +121,13 @@ require ( github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.31.0 golang.org/x/image v0.21.0 golang.org/x/net v0.30.0 golang.org/x/oauth2 v0.23.0 - golang.org/x/sync v0.8.0 - golang.org/x/sys v0.26.0 - golang.org/x/text v0.19.0 + golang.org/x/sync v0.10.0 + golang.org/x/sys v0.28.0 + golang.org/x/text v0.21.0 golang.org/x/tools v0.26.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 diff --git a/go.sum b/go.sum index d1b7890fb68..afa3abece8f 100644 --- a/go.sum +++ b/go.sum @@ -293,8 +293,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A= -github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= -github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c h1:82lzmsy5Nr6JA6HcLRVxGfbdSoWfW45C6jnY3zFS7Ks= @@ -893,8 +893,9 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= @@ -946,8 +947,9 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -982,8 +984,9 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -996,8 +999,9 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1009,8 +1013,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/models/actions/run.go b/models/actions/run.go index 732fb48bb9a..f40bc1eb3db 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -37,6 +37,7 @@ type ActionRun struct { TriggerUser *user_model.User `xorm:"-"` ScheduleID int64 Ref string `xorm:"index"` // the commit/tag/… that caused the run + IsRefDeleted bool `xorm:"-"` CommitSHA string IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow. NeedApproval bool // may need approval if it's a fork pull request diff --git a/models/db/context_committer_test.go b/models/db/context_committer_test.go index 38e91f22edb..849c5dea411 100644 --- a/models/db/context_committer_test.go +++ b/models/db/context_committer_test.go @@ -4,7 +4,7 @@ package db // it's not db_test, because this file is for testing the private type halfCommitter import ( - "fmt" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -80,7 +80,7 @@ func Test_halfCommitter(t *testing.T) { testWithCommitter(mockCommitter, func(committer Committer) error { defer committer.Close() if true { - return fmt.Errorf("error") + return errors.New("error") } return committer.Commit() }) @@ -94,7 +94,7 @@ func Test_halfCommitter(t *testing.T) { testWithCommitter(mockCommitter, func(committer Committer) error { committer.Close() committer.Commit() - return fmt.Errorf("error") + return errors.New("error") }) mockCommitter.Assert(t) diff --git a/models/git/branch.go b/models/git/branch.go index ba1ada5517d..e683ce47e65 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -12,6 +12,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -169,9 +170,22 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e return &branch, nil } -func GetBranches(ctx context.Context, repoID int64, branchNames []string) ([]*Branch, error) { +func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) { branches := make([]*Branch, 0, len(branchNames)) - return branches, db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames).Find(&branches) + + sess := db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames) + if !includeDeleted { + sess.And("is_deleted=?", false) + } + return branches, sess.Find(&branches) +} + +func BranchesToNamesSet(branches []*Branch) container.Set[string] { + names := make(container.Set[string], len(branches)) + for _, branch := range branches { + names.Add(branch.Name) + } + return names } func AddBranches(ctx context.Context, branches []*Branch) error { diff --git a/models/issues/issue.go b/models/issues/issue.go index 64fc20cc05e..fe347c27156 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -125,8 +125,11 @@ type Issue struct { IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. PullRequest *PullRequest `xorm:"-"` NumComments int - Ref string - PinOrder int `xorm:"DEFAULT 0"` + + // TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl" + Ref string + + PinOrder int `xorm:"DEFAULT 0"` DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` diff --git a/modules/git/commit.go b/modules/git/commit.go index 010b56948ef..0ed268e3469 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -474,3 +474,17 @@ func (c *Commit) GetRepositoryDefaultPublicGPGKey(forceUpdate bool) (*GPGSetting } return c.repo.GetDefaultPublicGPGKey(forceUpdate) } + +func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) bool { + minLen := util.OptionalArg(minLength, objFmt.FullLength()) + if len(s) < minLen || len(s) > objFmt.FullLength() { + return false + } + for _, c := range s { + isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') + if !isHex { + return false + } + } + return true +} diff --git a/modules/git/ref.go b/modules/git/ref.go index 2db630e2ea9..aab4c5d77d7 100644 --- a/modules/git/ref.go +++ b/modules/git/ref.go @@ -142,7 +142,6 @@ func (ref RefName) RemoteName() string { // ShortName returns the short name of the reference name func (ref RefName) ShortName() string { - refName := string(ref) if ref.IsBranch() { return ref.BranchName() } @@ -158,8 +157,7 @@ func (ref RefName) ShortName() string { if ref.IsFor() { return ref.ForBranchName() } - - return refName + return string(ref) // usually it is a commit ID } // RefGroup returns the group type of the reference diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 8eaa17cb041..850ec655029 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -61,3 +61,31 @@ func parseTags(refs []string) []string { } return results } + +// UnstableGuessRefByShortName does the best guess to see whether a "short name" provided by user is a branch, tag or commit. +// It could guess wrongly if the input is already ambiguous. For example: +// * "refs/heads/the-name" vs "refs/heads/refs/heads/the-name" +// * "refs/tags/1234567890" vs commit "1234567890" +// In most cases, it SHOULD AVOID using this function, unless there is an irresistible reason (eg: make API friendly to end users) +// If the function is used, the caller SHOULD CHECK the ref type carefully. +func (repo *Repository) UnstableGuessRefByShortName(shortName string) RefName { + if repo.IsBranchExist(shortName) { + return RefNameFromBranch(shortName) + } + if repo.IsTagExist(shortName) { + return RefNameFromTag(shortName) + } + if strings.HasPrefix(shortName, "refs/") { + if repo.IsReferenceExist(shortName) { + return RefName(shortName) + } + } + commit, err := repo.GetCommit(shortName) + if err == nil { + commitIDString := commit.ID.String() + if strings.HasPrefix(commitIDString, shortName) { + return RefName(commitIDString) + } + } + return "" +} diff --git a/modules/globallock/globallock_test.go b/modules/globallock/globallock_test.go index 88a555c86f3..f14c7d656b6 100644 --- a/modules/globallock/globallock_test.go +++ b/modules/globallock/globallock_test.go @@ -64,7 +64,7 @@ func TestLockAndDo(t *testing.T) { } func testLockAndDo(t *testing.T) { - const concurrency = 1000 + const concurrency = 50 ctx := context.Background() count := 0 diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index 7ef370e89c5..bf51bd6c148 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 = 4 + issueIndexerLatestVersion = 5 ) const unicodeNormalizeName = "unicodeNormalize" @@ -75,6 +75,7 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) { docMapping.AddFieldMappingsAt("is_pull", boolFieldMapping) docMapping.AddFieldMappingsAt("is_closed", boolFieldMapping) + docMapping.AddFieldMappingsAt("is_archived", boolFieldMapping) docMapping.AddFieldMappingsAt("label_ids", numberFieldMapping) docMapping.AddFieldMappingsAt("no_label", boolFieldMapping) docMapping.AddFieldMappingsAt("milestone_id", numberFieldMapping) @@ -185,6 +186,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.IsClosed.Has() { queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.Value(), "is_closed")) } + if options.IsArchived.Has() { + queries = append(queries, inner_bleve.BoolFieldQuery(options.IsArchived.Value(), "is_archived")) + } if options.NoLabelOnly { queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_label")) diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 98b097f8713..42834f6e886 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -72,7 +72,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m UpdatedAfterUnix: options.UpdatedAfterUnix.Value(), UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(), PriorityRepoID: 0, - IsArchived: optional.None[bool](), + IsArchived: options.IsArchived, Org: nil, Team: nil, User: nil, diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go index 1a0f241e615..4f6ad96d222 100644 --- a/modules/indexer/issues/dboptions.go +++ b/modules/indexer/issues/dboptions.go @@ -11,11 +11,12 @@ import ( func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions { searchOpt := &SearchOptions{ - Keyword: keyword, - RepoIDs: opts.RepoIDs, - AllPublic: opts.AllPublic, - IsPull: opts.IsPull, - IsClosed: opts.IsClosed, + Keyword: keyword, + RepoIDs: opts.RepoIDs, + AllPublic: opts.AllPublic, + IsPull: opts.IsPull, + IsClosed: opts.IsClosed, + IsArchived: opts.IsArchived, } if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 { diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 6f705150097..4c293f3f2a9 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -18,7 +18,7 @@ import ( ) const ( - issueIndexerLatestVersion = 1 + issueIndexerLatestVersion = 2 // multi-match-types, currently only 2 types are used // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types esMultiMatchTypeBestFields = "best_fields" @@ -58,6 +58,7 @@ const ( "is_pull": { "type": "boolean", "index": true }, "is_closed": { "type": "boolean", "index": true }, + "is_archived": { "type": "boolean", "index": true }, "label_ids": { "type": "integer", "index": true }, "no_label": { "type": "boolean", "index": true }, "milestone_id": { "type": "integer", "index": true }, @@ -168,6 +169,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.IsClosed.Has() { query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.Value())) } + if options.IsArchived.Has() { + query.Must(elastic.NewTermQuery("is_archived", options.IsArchived.Value())) + } if options.NoLabelOnly { query.Must(elastic.NewTermQuery("no_label", true)) diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index 7c3ba75bb07..06a6a46c234 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -37,6 +37,7 @@ func TestDBSearchIssues(t *testing.T) { t.Run("search issues by ID", searchIssueByID) t.Run("search issues is pr", searchIssueIsPull) t.Run("search issues is closed", searchIssueIsClosed) + t.Run("search issues is archived", searchIssueIsArchived) t.Run("search issues by milestone", searchIssueByMilestoneID) t.Run("search issues by label", searchIssueByLabelID) t.Run("search issues by time", searchIssueByTime) @@ -298,6 +299,33 @@ func searchIssueIsClosed(t *testing.T) { } } +func searchIssueIsArchived(t *testing.T) { + tests := []struct { + opts SearchOptions + expectedIDs []int64 + }{ + { + SearchOptions{ + IsArchived: optional.Some(false), + }, + []int64{22, 21, 17, 16, 15, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1}, + }, + { + SearchOptions{ + IsArchived: optional.Some(true), + }, + []int64{14}, + }, + } + for _, test := range tests { + issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.expectedIDs, issueIDs) + } +} + func searchIssueByMilestoneID(t *testing.T) { tests := []struct { opts SearchOptions diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index a43c6be0059..09dcbf4804c 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -25,6 +25,7 @@ type IndexerData struct { // Fields used for filtering IsPull bool `json:"is_pull"` IsClosed bool `json:"is_closed"` + IsArchived bool `json:"is_archived"` LabelIDs []int64 `json:"label_ids"` NoLabel bool `json:"no_label"` // True if LabelIDs is empty MilestoneID int64 `json:"milestone_id"` @@ -81,8 +82,9 @@ type SearchOptions struct { RepoIDs []int64 // repository IDs which the issues belong to AllPublic bool // if include all public repositories - IsPull optional.Option[bool] // if the issues is a pull request - IsClosed optional.Option[bool] // if the issues is closed + IsPull optional.Option[bool] // if the issues is a pull request + IsClosed optional.Option[bool] // if the issues is closed + IsArchived optional.Option[bool] // if the repo is archived IncludedLabelIDs []int64 // labels the issues have ExcludedLabelIDs []int64 // labels the issues don't have diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 93323193392..1066e962725 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -18,7 +18,7 @@ import ( ) const ( - issueIndexerLatestVersion = 3 + issueIndexerLatestVersion = 4 // TODO: make this configurable if necessary maxTotalHits = 10000 @@ -61,6 +61,7 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer { "is_public", "is_pull", "is_closed", + "is_archived", "label_ids", "no_label", "milestone_id", @@ -145,6 +146,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.IsClosed.Has() { query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.Value())) } + if options.IsArchived.Has() { + query.And(inner_meilisearch.NewFilterEq("is_archived", options.IsArchived.Value())) + } if options.NoLabelOnly { query.And(inner_meilisearch.NewFilterEq("no_label", true)) diff --git a/modules/indexer/issues/util.go b/modules/indexer/issues/util.go index e752ae6f243..deb19adc49d 100644 --- a/modules/indexer/issues/util.go +++ b/modules/indexer/issues/util.go @@ -101,6 +101,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD Comments: comments, IsPull: issue.IsPull, IsClosed: issue.IsClosed, + IsArchived: issue.Repo.IsArchived, LabelIDs: labels, NoLabel: len(labels) == 0, MilestoneID: issue.MilestoneID, diff --git a/modules/lfs/http_client.go b/modules/lfs/http_client.go index 3060e25754c..50f0e7a8d88 100644 --- a/modules/lfs/http_client.go +++ b/modules/lfs/http_client.go @@ -236,6 +236,7 @@ func createRequest(ctx context.Context, method, url string, headers map[string]s req.Header.Set(key, value) } req.Header.Set("Accept", AcceptHeader) + req.Header.Set("User-Agent", UserAgentHeader) return req, nil } diff --git a/modules/lfs/shared.go b/modules/lfs/shared.go index a4326b57b2f..40ad789c1d9 100644 --- a/modules/lfs/shared.go +++ b/modules/lfs/shared.go @@ -15,7 +15,8 @@ const ( // MediaType contains the media type for LFS server requests MediaType = "application/vnd.git-lfs+json" // Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served - AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8" + AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8" + UserAgentHeader = "git-lfs" ) // BatchRequest contains multiple requests processed in one batch operation. diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go index e371b1c74ab..a2213b2ce76 100644 --- a/modules/markup/markdown/markdown_math_test.go +++ b/modules/markup/markdown/markdown_math_test.go @@ -20,23 +20,23 @@ func TestMathRender(t *testing.T) { }{ { "$a$", - `

a

` + nl, + `

a

` + nl, }, { "$ a $", - `

a

` + nl, + `

a

` + nl, }, { "$a$ $b$", - `

a b

` + nl, + `

a b

` + nl, }, { `\(a\) \(b\)`, - `

a b

` + nl, + `

a b

` + nl, }, { `$a$.`, - `

a.

` + nl, + `

a.

` + nl, }, { `.$a$`, @@ -64,27 +64,27 @@ func TestMathRender(t *testing.T) { }, { "$a$ ($b$) [$c$] {$d$}", - `

a (b) [$c$] {$d$}

` + nl, + `

a (b) [$c$] {$d$}

` + nl, }, { "$$a$$", - `a` + nl, + `a` + nl, }, { "$$a$$ test", - `

a test

` + nl, + `

a test

` + nl, }, { "test $$a$$", - `

test a

` + nl, + `

test a

` + nl, }, { `foo $x=\$$ bar`, - `

foo x=\$ bar

` + nl, + `

foo x=\$ bar

` + nl, }, { `$\text{$b$}$`, - `

\text{$b$}

` + nl, + `

\text{$b$}

` + nl, }, } @@ -110,7 +110,7 @@ func TestMathRenderBlockIndent(t *testing.T) { \alpha \] `, - `

+			`

 \alpha
 
`, @@ -122,7 +122,7 @@ func TestMathRenderBlockIndent(t *testing.T) { \alpha \] `, - `

+			`

 \alpha
 
`, @@ -137,7 +137,7 @@ a d \] `, - `

+			`

 a
 b
 c
@@ -154,7 +154,7 @@ c
   c
   \]
 `,
-			`

+			`

 a
  b
 c
@@ -165,7 +165,7 @@ c
 			"indent-0-oneline",
 			`$$ x $$
 foo`,
-			` x 
+			` x 
 

foo

`, }, @@ -173,7 +173,7 @@ foo`, "indent-3-oneline", ` $$ x $$ foo`, - ` x + ` x

foo

`, }, @@ -188,10 +188,10 @@ foo`, > \] `, `
-

+

 a
 
-

+

 b
 
@@ -207,7 +207,7 @@ b 2. b`, `
  1. a -
    
    +
    
     x
     
  2. diff --git a/modules/markup/markdown/math/block_renderer.go b/modules/markup/markdown/math/block_renderer.go index a770efa01c7..c29f0618821 100644 --- a/modules/markup/markdown/math/block_renderer.go +++ b/modules/markup/markdown/math/block_renderer.go @@ -12,6 +12,17 @@ import ( "github.com/yuin/goldmark/util" ) +// Block render output: +//
    ...
    +// +// Keep in mind that there is another "code block" render in "func (r *GlodmarkRender) highlightingRenderer" +// "highlightingRenderer" outputs the math block with extra "chroma" class: +//
    ...
    +// +// Special classes: +// * "is-loading": show a loading indicator +// * "display": used by JS to decide to render as a block, otherwise render as inline + // BlockRenderer represents a renderer for math Blocks type BlockRenderer struct { renderInternal *internal.RenderInternal @@ -38,7 +49,7 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node) func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { n := node.(*Block) if entering { - code := giteaUtil.Iif(n.Inline, "", `
    `) + ``
    +		code := giteaUtil.Iif(n.Inline, "", `
    `) + ``
     		_ = r.renderInternal.FormatWithSafeAttrs(w, code)
     		r.writeLines(w, source, n)
     	} else {
    diff --git a/modules/markup/markdown/math/inline_renderer.go b/modules/markup/markdown/math/inline_renderer.go
    index 0cff4f1e74e..4e0531cf404 100644
    --- a/modules/markup/markdown/math/inline_renderer.go
    +++ b/modules/markup/markdown/math/inline_renderer.go
    @@ -13,6 +13,9 @@ import (
     	"github.com/yuin/goldmark/util"
     )
     
    +// Inline render output:
    +// ...
    +
     // InlineRenderer is an inline renderer
     type InlineRenderer struct {
     	renderInternal *internal.RenderInternal
    @@ -25,11 +28,7 @@ func NewInlineRenderer(renderInternal *internal.RenderInternal) renderer.NodeRen
     
     func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
     	if entering {
    -		extraClass := ""
    -		if _, ok := n.(*InlineBlock); ok {
    -			extraClass = "display "
    -		}
    -		_ = r.renderInternal.FormatWithSafeAttrs(w, ``, extraClass)
    +		_ = r.renderInternal.FormatWithSafeAttrs(w, ``)
     		for c := n.FirstChild(); c != nil; c = c.NextSibling() {
     			segment := c.(*ast.Text).Segment
     			value := util.EscapeHTML(segment.Value(source))
    diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
    index f8e4f569b87..6d0695ee163 100644
    --- a/modules/ssh/ssh.go
    +++ b/modules/ssh/ssh.go
    @@ -13,10 +13,12 @@ import (
     	"errors"
     	"fmt"
     	"io"
    +	"maps"
     	"net"
     	"os"
     	"os/exec"
     	"path/filepath"
    +	"reflect"
     	"strconv"
     	"strings"
     	"sync"
    @@ -33,9 +35,22 @@ import (
     	gossh "golang.org/x/crypto/ssh"
     )
     
    -type contextKey string
    +// The ssh auth overall works like this:
    +// NewServerConn:
    +//	serverHandshake+serverAuthenticate:
    +//		PublicKeyCallback:
    +//			PublicKeyHandler (our code):
    +//				reset(ctx.Permissions) and set ctx.Permissions.giteaKeyID = keyID
    +//		pubKey.Verify
    +//		return ctx.Permissions // only reaches here, the pub key is really authenticated
    +//	set conn.Permissions from serverAuthenticate
    +//  sessionHandler(conn)
    +//
    +// Then sessionHandler should only use the "verified keyID" from the original ssh conn, but not the ctx one.
    +// Otherwise, if a user provides 2 keys A (a correct one) and B (public key matches but no private key),
    +// then only A succeeds to authenticate, sessionHandler will see B's keyID
     
    -const giteaKeyID = contextKey("gitea-key-id")
    +const giteaPermissionExtensionKeyID = "gitea-perm-ext-key-id"
     
     func getExitStatusFromError(err error) int {
     	if err == nil {
    @@ -61,8 +76,32 @@ func getExitStatusFromError(err error) int {
     	return waitStatus.ExitStatus()
     }
     
    +// sessionPartial is the private struct from "gliderlabs/ssh/session.go"
    +// We need to read the original "conn" field from "ssh.Session interface" which contains the "*session pointer"
    +// https://github.com/gliderlabs/ssh/blob/d137aad99cd6f2d9495bfd98c755bec4e5dffb8c/session.go#L109-L113
    +// If upstream fixes the problem and/or changes the struct, we need to follow.
    +// If the struct mismatches, the builtin ssh server will fail during integration tests.
    +type sessionPartial struct {
    +	sync.Mutex
    +	gossh.Channel
    +	conn *gossh.ServerConn
    +}
    +
    +func ptr[T any](intf any) *T {
    +	// https://pkg.go.dev/unsafe#Pointer
    +	// (1) Conversion of a *T1 to Pointer to *T2.
    +	// Provided that T2 is no larger than T1 and that the two share an equivalent memory layout,
    +	// this conversion allows reinterpreting data of one type as data of another type.
    +	v := reflect.ValueOf(intf)
    +	p := v.UnsafePointer()
    +	return (*T)(p)
    +}
    +
     func sessionHandler(session ssh.Session) {
    -	keyID := fmt.Sprintf("%d", session.Context().Value(giteaKeyID).(int64))
    +	// here can't use session.Permissions() because it only uses the value from ctx, which might not be the authenticated one.
    +	// so we must use the original ssh conn, which always contains the correct (verified) keyID.
    +	sshConn := ptr[sessionPartial](session)
    +	keyID := sshConn.conn.Permissions.Extensions[giteaPermissionExtensionKeyID]
     
     	command := session.RawCommand()
     
    @@ -164,6 +203,23 @@ func sessionHandler(session ssh.Session) {
     }
     
     func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
    +	// The publicKeyHandler (PublicKeyCallback) only helps to provide the candidate keys to authenticate,
    +	// It does NOT really verify here, so we could only record the related information here.
    +	// After authentication (Verify), the "Permissions" will be assigned to the ssh conn,
    +	// then we can use it in the "session handler"
    +
    +	// first, reset the ctx permissions (just like https://github.com/gliderlabs/ssh/pull/243 does)
    +	// it shouldn't be reused across different ssh conn (sessions), each pub key should have its own "Permissions"
    +	oldCtxPerm := ctx.Permissions().Permissions
    +	ctx.Permissions().Permissions = &gossh.Permissions{}
    +	ctx.Permissions().Permissions.CriticalOptions = maps.Clone(oldCtxPerm.CriticalOptions)
    +
    +	setPermExt := func(keyID int64) {
    +		ctx.Permissions().Permissions.Extensions = map[string]string{
    +			giteaPermissionExtensionKeyID: fmt.Sprint(keyID),
    +		}
    +	}
    +
     	if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
     		log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
     	}
    @@ -238,8 +294,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
     			if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
     				log.Debug("Successfully authenticated: %s Certificate Fingerprint: %s Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key), principal)
     			}
    -			ctx.SetValue(giteaKeyID, pkey.ID)
    -
    +			setPermExt(pkey.ID)
     			return true
     		}
     
    @@ -266,8 +321,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
     	if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
     		log.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
     	}
    -	ctx.SetValue(giteaKeyID, pkey.ID)
    -
    +	setPermExt(pkey.ID)
     	return true
     }
     
    diff --git a/modules/structs/repo.go b/modules/structs/repo.go
    index 832ffa8bcc9..fb784bd8b37 100644
    --- a/modules/structs/repo.go
    +++ b/modules/structs/repo.go
    @@ -278,6 +278,16 @@ type CreateBranchRepoOption struct {
     	OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
     }
     
    +// UpdateBranchRepoOption options when updating a branch in a repository
    +// swagger:model
    +type UpdateBranchRepoOption struct {
    +	// New branch name
    +	//
    +	// required: true
    +	// unique: true
    +	Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
    +}
    +
     // TransferRepoOption options when transfer a repository's ownership
     // swagger:model
     type TransferRepoOption struct {
    diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
    index f0abce0d4bf..776d2bdc2bf 100644
    --- a/options/locale/locale_pt-PT.ini
    +++ b/options/locale/locale_pt-PT.ini
    @@ -47,7 +47,7 @@ webauthn_error_unknown=Ocorreu um erro desconhecido. Tente novamente, por favor.
     webauthn_error_insecure=`WebAuthn apenas suporta conexões seguras. Para testar sobre HTTP, pode usar a origem "localhost" ou "127.0.0.1"`
     webauthn_error_unable_to_process=O servidor não conseguiu processar o seu pedido.
     webauthn_error_duplicated=A chave de segurança não é permitida neste pedido. Certifique-se de que a chave não está já registada.
    -webauthn_error_empty=Você tem que definir um nome para esta chave.
    +webauthn_error_empty=Tem de definir um nome para esta chave.
     webauthn_error_timeout=O tempo limite foi atingido antes que a sua chave pudesse ser lida. Recarregue esta página e tente novamente.
     webauthn_reload=Recarregar
     
    @@ -1109,6 +1109,7 @@ delete_preexisting_success=Eliminados os ficheiros não adoptados em %s
     blame_prior=Ver a responsabilização anterior a esta modificação
     blame.ignore_revs=Ignorando as revisões em .git-blame-ignore-revs. Clique aqui para contornar e ver a vista normal de responsabilização.
     blame.ignore_revs.failed=Falhou ao ignorar as revisões em .git-blame-ignore-revs.
    +user_search_tooltip=Mostra um máximo de 30 utilizadores
     
     tree_path_not_found_commit=A localização %[1]s não existe no cometimento %[2]s
     tree_path_not_found_branch=A localização %[1]s não existe no ramo %[2]s
    @@ -1527,6 +1528,8 @@ issues.filter_assignee=Encarregado
     issues.filter_assginee_no_select=Todos os encarregados
     issues.filter_assginee_no_assignee=Sem encarregado
     issues.filter_poster=Autor(a)
    +issues.filter_user_placeholder=Procurar utilizadores
    +issues.filter_user_no_select=Todos os utilizadores
     issues.filter_type=Tipo
     issues.filter_type.all_issues=Todas as questões
     issues.filter_type.assigned_to_you=Atribuídas a si
    diff --git a/package-lock.json b/package-lock.json
    index e3f7a0116f7..53bd5bc4f1f 100644
    --- a/package-lock.json
    +++ b/package-lock.json
    @@ -87,7 +87,7 @@
             "eslint-import-resolver-typescript": "3.7.0",
             "eslint-plugin-array-func": "4.0.0",
             "eslint-plugin-github": "5.1.3",
    -        "eslint-plugin-i": "2.29.1",
    +        "eslint-plugin-import-x": "4.5.0",
             "eslint-plugin-no-jquery": "3.1.0",
             "eslint-plugin-no-use-extend-native": "0.5.0",
             "eslint-plugin-playwright": "2.1.0",
    @@ -8385,56 +8385,6 @@
             "node": "*"
           }
         },
    -    "node_modules/eslint-plugin-i": {
    -      "version": "2.29.1",
    -      "resolved": "https://registry.npmjs.org/eslint-plugin-i/-/eslint-plugin-i-2.29.1.tgz",
    -      "integrity": "sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==",
    -      "dev": true,
    -      "license": "MIT",
    -      "dependencies": {
    -        "debug": "^4.3.4",
    -        "doctrine": "^3.0.0",
    -        "eslint-import-resolver-node": "^0.3.9",
    -        "eslint-module-utils": "^2.8.0",
    -        "get-tsconfig": "^4.7.2",
    -        "is-glob": "^4.0.3",
    -        "minimatch": "^3.1.2",
    -        "semver": "^7.5.4"
    -      },
    -      "engines": {
    -        "node": ">=12"
    -      },
    -      "funding": {
    -        "url": "https://opencollective.com/unts"
    -      },
    -      "peerDependencies": {
    -        "eslint": "^7.2.0 || ^8"
    -      }
    -    },
    -    "node_modules/eslint-plugin-i/node_modules/brace-expansion": {
    -      "version": "1.1.11",
    -      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
    -      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
    -      "dev": true,
    -      "license": "MIT",
    -      "dependencies": {
    -        "balanced-match": "^1.0.0",
    -        "concat-map": "0.0.1"
    -      }
    -    },
    -    "node_modules/eslint-plugin-i/node_modules/minimatch": {
    -      "version": "3.1.2",
    -      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
    -      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
    -      "dev": true,
    -      "license": "ISC",
    -      "dependencies": {
    -        "brace-expansion": "^1.1.7"
    -      },
    -      "engines": {
    -        "node": "*"
    -      }
    -    },
         "node_modules/eslint-plugin-i18n-text": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/eslint-plugin-i18n-text/-/eslint-plugin-i18n-text-1.0.1.tgz",
    @@ -8479,6 +8429,48 @@
             "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
           }
         },
    +    "node_modules/eslint-plugin-import-x": {
    +      "version": "4.5.0",
    +      "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.5.0.tgz",
    +      "integrity": "sha512-l0OTfnPF8RwmSXfjT75N8d6ZYLVrVYWpaGlgvVkVqFERCI5SyBfDP7QEMr3kt0zWi2sOa9EQ47clbdFsHkF83Q==",
    +      "dev": true,
    +      "license": "MIT",
    +      "dependencies": {
    +        "@typescript-eslint/scope-manager": "^8.1.0",
    +        "@typescript-eslint/utils": "^8.1.0",
    +        "debug": "^4.3.4",
    +        "doctrine": "^3.0.0",
    +        "eslint-import-resolver-node": "^0.3.9",
    +        "get-tsconfig": "^4.7.3",
    +        "is-glob": "^4.0.3",
    +        "minimatch": "^9.0.3",
    +        "semver": "^7.6.3",
    +        "stable-hash": "^0.0.4",
    +        "tslib": "^2.6.3"
    +      },
    +      "engines": {
    +        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
    +      },
    +      "peerDependencies": {
    +        "eslint": "^8.57.0 || ^9.0.0"
    +      }
    +    },
    +    "node_modules/eslint-plugin-import-x/node_modules/minimatch": {
    +      "version": "9.0.5",
    +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
    +      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
    +      "dev": true,
    +      "license": "ISC",
    +      "dependencies": {
    +        "brace-expansion": "^2.0.1"
    +      },
    +      "engines": {
    +        "node": ">=16 || 14 >=14.17"
    +      },
    +      "funding": {
    +        "url": "https://github.com/sponsors/isaacs"
    +      }
    +    },
         "node_modules/eslint-plugin-import/node_modules/brace-expansion": {
           "version": "1.1.11",
           "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
    diff --git a/package.json b/package.json
    index d30aedc54f5..3a81e648228 100644
    --- a/package.json
    +++ b/package.json
    @@ -86,7 +86,7 @@
         "eslint-import-resolver-typescript": "3.7.0",
         "eslint-plugin-array-func": "4.0.0",
         "eslint-plugin-github": "5.1.3",
    -    "eslint-plugin-i": "2.29.1",
    +    "eslint-plugin-import-x": "4.5.0",
         "eslint-plugin-no-jquery": "3.1.0",
         "eslint-plugin-no-use-extend-native": "0.5.0",
         "eslint-plugin-playwright": "2.1.0",
    diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
    index 4e194f65fa1..47ea7137b82 100644
    --- a/routers/api/packages/api.go
    +++ b/routers/api/packages/api.go
    @@ -358,6 +358,7 @@ func CommonRoutes() *web.Router {
     					r.Get("/PACKAGES", cran.EnumerateSourcePackages)
     					r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages)
     					r.Get("/{filename}", cran.DownloadSourcePackageFile)
    +					r.Get("/Archive/{packagename}/{filename}", cran.DownloadSourcePackageFile)
     				})
     				r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadSourcePackageFile)
     			})
    diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
    index f28ee980e10..96365e7c14d 100644
    --- a/routers/api/v1/api.go
    +++ b/routers/api/v1/api.go
    @@ -1195,6 +1195,7 @@ func Routes() *web.Router {
     					m.Get("/*", repo.GetBranch)
     					m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
     					m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
    +					m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
     				}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
     				m.Group("/branch_protections", func() {
     					m.Get("", repo.ListBranchProtections)
    diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
    index 53f3b4648a5..946203e97ec 100644
    --- a/routers/api/v1/repo/branch.go
    +++ b/routers/api/v1/repo/branch.go
    @@ -386,6 +386,77 @@ func ListBranches(ctx *context.APIContext) {
     	ctx.JSON(http.StatusOK, apiBranches)
     }
     
    +// UpdateBranch updates a repository's branch.
    +func UpdateBranch(ctx *context.APIContext) {
    +	// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
    +	// ---
    +	// summary: Update a branch
    +	// consumes:
    +	// - application/json
    +	// 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: branch
    +	//   in: path
    +	//   description: name of the branch
    +	//   type: string
    +	//   required: true
    +	// - name: body
    +	//   in: body
    +	//   schema:
    +	//     "$ref": "#/definitions/UpdateBranchRepoOption"
    +	// responses:
    +	//   "204":
    +	//     "$ref": "#/responses/empty"
    +	//   "403":
    +	//     "$ref": "#/responses/forbidden"
    +	//   "404":
    +	//     "$ref": "#/responses/notFound"
    +	//   "422":
    +	//     "$ref": "#/responses/validationError"
    +
    +	opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
    +
    +	oldName := ctx.PathParam("*")
    +	repo := ctx.Repo.Repository
    +
    +	if repo.IsEmpty {
    +		ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
    +		return
    +	}
    +
    +	if repo.IsMirror {
    +		ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
    +		return
    +	}
    +
    +	msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
    +	if err != nil {
    +		ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
    +		return
    +	}
    +	if msg == "target_exist" {
    +		ctx.Error(http.StatusUnprocessableEntity, "", "Cannot rename a branch using the same name or rename to a branch that already exists.")
    +		return
    +	}
    +	if msg == "from_not_exist" {
    +		ctx.Error(http.StatusNotFound, "", "Branch doesn't exist.")
    +		return
    +	}
    +
    +	ctx.Status(http.StatusNoContent)
    +}
    +
     // GetBranchProtection gets a branch protection
     func GetBranchProtection(ctx *context.APIContext) {
     	// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
    diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go
    index 38e5330b3ac..1678bc033c6 100644
    --- a/routers/api/v1/repo/compare.go
    +++ b/routers/api/v1/repo/compare.go
    @@ -64,22 +64,19 @@ func CompareDiff(ctx *context.APIContext) {
     		}
     	}
     
    -	_, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{
    -		Base: infos[0],
    -		Head: infos[1],
    -	})
    +	compareResult, closer := parseCompareInfo(ctx, api.CreatePullRequestOption{Base: infos[0], Head: infos[1]})
     	if ctx.Written() {
     		return
     	}
    -	defer headGitRepo.Close()
    +	defer closer()
     
     	verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
     	files := ctx.FormString("files") == "" || ctx.FormBool("files")
     
    -	apiCommits := make([]*api.Commit, 0, len(ci.Commits))
    +	apiCommits := make([]*api.Commit, 0, len(compareResult.compareInfo.Commits))
     	userCache := make(map[string]*user_model.User)
    -	for i := 0; i < len(ci.Commits); i++ {
    -		apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache,
    +	for i := 0; i < len(compareResult.compareInfo.Commits); i++ {
    +		apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, compareResult.compareInfo.Commits[i], userCache,
     			convert.ToCommitOptions{
     				Stat:         true,
     				Verification: verification,
    @@ -93,7 +90,7 @@ func CompareDiff(ctx *context.APIContext) {
     	}
     
     	ctx.JSON(http.StatusOK, &api.Compare{
    -		TotalCommits: len(ci.Commits),
    +		TotalCommits: len(compareResult.compareInfo.Commits),
     		Commits:      apiCommits,
     	})
     }
    diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
    index 86b204f51e2..6f4f3efaa10 100644
    --- a/routers/api/v1/repo/pull.go
    +++ b/routers/api/v1/repo/pull.go
    @@ -389,8 +389,7 @@ func CreatePullRequest(ctx *context.APIContext) {
     
     	form := *web.GetForm(ctx).(*api.CreatePullRequestOption)
     	if form.Head == form.Base {
    -		ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame",
    -			"Invalid PullRequest: There are no changes between the head and the base")
    +		ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame", "Invalid PullRequest: There are no changes between the head and the base")
     		return
     	}
     
    @@ -401,14 +400,22 @@ func CreatePullRequest(ctx *context.APIContext) {
     	)
     
     	// Get repo/branch information
    -	headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
    +	compareResult, closer := parseCompareInfo(ctx, form)
     	if ctx.Written() {
     		return
     	}
    -	defer headGitRepo.Close()
    +	defer closer()
    +
    +	if !compareResult.baseRef.IsBranch() || !compareResult.headRef.IsBranch() {
    +		ctx.Error(http.StatusUnprocessableEntity, "BaseHeadInvalidRefType", "Invalid PullRequest: base and head must be branches")
    +		return
    +	}
     
     	// Check if another PR exists with the same targets
    -	existingPr, err := issues_model.GetUnmergedPullRequest(ctx, headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch, issues_model.PullRequestFlowGithub)
    +	existingPr, err := issues_model.GetUnmergedPullRequest(ctx, compareResult.headRepo.ID, ctx.Repo.Repository.ID,
    +		compareResult.headRef.ShortName(), compareResult.baseRef.ShortName(),
    +		issues_model.PullRequestFlowGithub,
    +	)
     	if err != nil {
     		if !issues_model.IsErrPullRequestNotExist(err) {
     			ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
    @@ -484,13 +491,13 @@ func CreatePullRequest(ctx *context.APIContext) {
     		DeadlineUnix: deadlineUnix,
     	}
     	pr := &issues_model.PullRequest{
    -		HeadRepoID: headRepo.ID,
    +		HeadRepoID: compareResult.headRepo.ID,
     		BaseRepoID: repo.ID,
    -		HeadBranch: headBranch,
    -		BaseBranch: baseBranch,
    -		HeadRepo:   headRepo,
    +		HeadBranch: compareResult.headRef.ShortName(),
    +		BaseBranch: compareResult.baseRef.ShortName(),
    +		HeadRepo:   compareResult.headRepo,
     		BaseRepo:   repo,
    -		MergeBase:  compareInfo.MergeBase,
    +		MergeBase:  compareResult.compareInfo.MergeBase,
     		Type:       issues_model.PullRequestGitea,
     	}
     
    @@ -1080,32 +1087,32 @@ func MergePullRequest(ctx *context.APIContext) {
     	ctx.Status(http.StatusOK)
     }
     
    -func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) {
    -	baseRepo := ctx.Repo.Repository
    +type parseCompareInfoResult struct {
    +	headRepo    *repo_model.Repository
    +	headGitRepo *git.Repository
    +	compareInfo *git.CompareInfo
    +	baseRef     git.RefName
    +	headRef     git.RefName
    +}
     
    +// parseCompareInfo returns non-nil if it succeeds, it always writes to the context and returns nil if it fails
    +func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (result *parseCompareInfoResult, closer func()) {
    +	var err error
     	// Get compared branches information
     	// format: ...[:]
     	// base<-head: master...head:feature
     	// same repo: master...feature
    +	baseRepo := ctx.Repo.Repository
    +	baseRefToGuess := form.Base
     
    -	// TODO: Validate form first?
    -
    -	baseBranch := form.Base
    -
    -	var (
    -		headUser   *user_model.User
    -		headBranch string
    -		isSameRepo bool
    -		err        error
    -	)
    -
    -	// If there is no head repository, it means pull request between same repository.
    -	headInfos := strings.Split(form.Head, ":")
    -	if len(headInfos) == 1 {
    -		isSameRepo = true
    -		headUser = ctx.Repo.Owner
    -		headBranch = headInfos[0]
    +	headUser := ctx.Repo.Owner
    +	headRefToGuess := form.Head
    +	if headInfos := strings.Split(form.Head, ":"); len(headInfos) == 1 {
    +		// If there is no head repository, it means pull request between same repository.
    +		// Do nothing here because the head variables have been assigned above.
     	} else if len(headInfos) == 2 {
    +		// There is a head repository (the head repository could also be the same base repo)
    +		headRefToGuess = headInfos[1]
     		headUser, err = user_model.GetUserByName(ctx, headInfos[0])
     		if err != nil {
     			if user_model.IsErrUserNotExist(err) {
    @@ -1113,38 +1120,29 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
     			} else {
     				ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
     			}
    -			return nil, nil, nil, "", ""
    +			return nil, nil
     		}
    -		headBranch = headInfos[1]
    -		// The head repository can also point to the same repo
    -		isSameRepo = ctx.Repo.Owner.ID == headUser.ID
     	} else {
     		ctx.NotFound()
    -		return nil, nil, nil, "", ""
    +		return nil, nil
     	}
     
    -	ctx.Repo.PullRequest.SameRepo = isSameRepo
    -	log.Trace("Repo path: %q, base branch: %q, head branch: %q", ctx.Repo.GitRepo.Path, baseBranch, headBranch)
    -	// Check if base branch is valid.
    -	if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
    -		ctx.NotFound("BaseNotExist")
    -		return nil, nil, nil, "", ""
    -	}
    +	isSameRepo := ctx.Repo.Owner.ID == headUser.ID
     
     	// Check if current user has fork of repository or in the same repository.
     	headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID)
     	if headRepo == nil && !isSameRepo {
    -		err := baseRepo.GetBaseRepo(ctx)
    +		err = baseRepo.GetBaseRepo(ctx)
     		if err != nil {
     			ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
    -			return nil, nil, nil, "", ""
    +			return nil, nil
     		}
     
     		// Check if baseRepo's base repository is the same as headUser's repository.
     		if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID {
     			log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
     			ctx.NotFound("GetBaseRepo")
    -			return nil, nil, nil, "", ""
    +			return nil, nil
     		}
     		// Assign headRepo so it can be used below.
     		headRepo = baseRepo.BaseRepo
    @@ -1154,67 +1152,68 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
     	if isSameRepo {
     		headRepo = ctx.Repo.Repository
     		headGitRepo = ctx.Repo.GitRepo
    +		closer = func() {} // no need to close the head repo because it shares the base repo
     	} else {
     		headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
     		if err != nil {
     			ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
    -			return nil, nil, nil, "", ""
    +			return nil, nil
     		}
    +		closer = func() { _ = headGitRepo.Close() }
     	}
    +	defer func() {
    +		if result == nil && !isSameRepo {
    +			_ = headGitRepo.Close()
    +		}
    +	}()
     
     	// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
     	permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
     	if err != nil {
    -		headGitRepo.Close()
     		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
    -		return nil, nil, nil, "", ""
    -	}
    -	if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
    -		if log.IsTrace() {
    -			log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v",
    -				ctx.Doer,
    -				baseRepo,
    -				permBase)
    -		}
    -		headGitRepo.Close()
    -		ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
    -		return nil, nil, nil, "", ""
    +		return nil, nil
     	}
     
    -	// user should have permission to read headrepo's codes
    +	if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
    +		log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
    +		ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
    +		return nil, nil
    +	}
    +
    +	// user should have permission to read headRepo's codes
    +	// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
     	permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
     	if err != nil {
    -		headGitRepo.Close()
     		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
    -		return nil, nil, nil, "", ""
    +		return nil, nil
     	}
     	if !permHead.CanRead(unit.TypeCode) {
    -		if log.IsTrace() {
    -			log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
    -				ctx.Doer,
    -				headRepo,
    -				permHead)
    -		}
    -		headGitRepo.Close()
    +		log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.Doer, headRepo, permHead)
     		ctx.NotFound("Can't read headRepo UnitTypeCode")
    -		return nil, nil, nil, "", ""
    +		return nil, nil
     	}
     
    -	// Check if head branch is valid.
    -	if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
    -		headGitRepo.Close()
    +	baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefToGuess)
    +	headRef := headGitRepo.UnstableGuessRefByShortName(headRefToGuess)
    +
    +	log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.GitRepo.Path, baseRefToGuess, baseRef, headRefToGuess, headRef)
    +
    +	baseRefValid := baseRef.IsBranch() || baseRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName), baseRef.ShortName())
    +	headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName())
    +	// Check if base&head ref are valid.
    +	if !baseRefValid || !headRefValid {
     		ctx.NotFound()
    -		return nil, nil, nil, "", ""
    +		return nil, nil
     	}
     
    -	compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
    +	compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseRef.ShortName(), headRef.ShortName(), false, false)
     	if err != nil {
    -		headGitRepo.Close()
     		ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
    -		return nil, nil, nil, "", ""
    +		return nil, nil
     	}
     
    -	return headRepo, headGitRepo, compareInfo, baseBranch, headBranch
    +	result = &parseCompareInfoResult{headRepo: headRepo, headGitRepo: headGitRepo, compareInfo: compareInfo, baseRef: baseRef, headRef: headRef}
    +	return result, closer
     }
     
     // UpdatePullRequest merge PR's baseBranch into headBranch
    diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go
    index 39c98c666e5..125605d98f5 100644
    --- a/routers/api/v1/swagger/options.go
    +++ b/routers/api/v1/swagger/options.go
    @@ -90,6 +90,8 @@ type swaggerParameterBodies struct {
     	// in:body
     	EditRepoOption api.EditRepoOption
     	// in:body
    +	UpdateBranchRepoOption api.UpdateBranchRepoOption
    +	// in:body
     	TransferRepoOption api.TransferRepoOption
     	// in:body
     	CreateForkOption api.CreateForkOption
    diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
    index ad16b9fb4e4..7ed37ea26b2 100644
    --- a/routers/web/repo/actions/actions.go
    +++ b/routers/web/repo/actions/actions.go
    @@ -245,6 +245,10 @@ func List(ctx *context.Context) {
     		return
     	}
     
    +	if err := loadIsRefDeleted(ctx, runs); err != nil {
    +		log.Error("LoadIsRefDeleted", err)
    +	}
    +
     	ctx.Data["Runs"] = runs
     
     	actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
    @@ -267,6 +271,34 @@ func List(ctx *context.Context) {
     	ctx.HTML(http.StatusOK, tplListActions)
     }
     
    +// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
    +// TODO: move this function to models/actions/run_list.go but now it will result in a circular import.
    +func loadIsRefDeleted(ctx *context.Context, runs actions_model.RunList) error {
    +	branches := make(container.Set[string], len(runs))
    +	for _, run := range runs {
    +		refName := git.RefName(run.Ref)
    +		if refName.IsBranch() {
    +			branches.Add(refName.ShortName())
    +		}
    +	}
    +	if len(branches) == 0 {
    +		return nil
    +	}
    +
    +	branchInfos, err := git_model.GetBranches(ctx, ctx.Repo.Repository.ID, branches.Values(), false)
    +	if err != nil {
    +		return err
    +	}
    +	branchSet := git_model.BranchesToNamesSet(branchInfos)
    +	for _, run := range runs {
    +		refName := git.RefName(run.Ref)
    +		if refName.IsBranch() && !branchSet.Contains(run.Ref) {
    +			run.IsRefDeleted = true
    +		}
    +	}
    +	return nil
    +}
    +
     type WorkflowDispatchInput struct {
     	Name        string   `yaml:"name"`
     	Description string   `yaml:"description"`
    diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
    index 73c6e54fbf5..b711038da06 100644
    --- a/routers/web/repo/actions/view.go
    +++ b/routers/web/repo/actions/view.go
    @@ -19,6 +19,7 @@ import (
     
     	actions_model "code.gitea.io/gitea/models/actions"
     	"code.gitea.io/gitea/models/db"
    +	git_model "code.gitea.io/gitea/models/git"
     	"code.gitea.io/gitea/models/perm"
     	access_model "code.gitea.io/gitea/models/perm/access"
     	repo_model "code.gitea.io/gitea/models/repo"
    @@ -136,8 +137,9 @@ type ViewUser struct {
     }
     
     type ViewBranch struct {
    -	Name string `json:"name"`
    -	Link string `json:"link"`
    +	Name      string `json:"name"`
    +	Link      string `json:"link"`
    +	IsDeleted bool   `json:"isDeleted"`
     }
     
     type ViewJobStep struct {
    @@ -236,6 +238,16 @@ func ViewPost(ctx *context_module.Context) {
     		Name: run.PrettyRef(),
     		Link: run.RefLink(),
     	}
    +	refName := git.RefName(run.Ref)
    +	if refName.IsBranch() {
    +		b, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, refName.ShortName())
    +		if err != nil && !git_model.IsErrBranchNotExist(err) {
    +			log.Error("GetBranch: %v", err)
    +		} else if git_model.IsErrBranchNotExist(err) || (b != nil && b.IsDeleted) {
    +			branch.IsDeleted = true
    +		}
    +	}
    +
     	resp.State.Run.Commit = ViewCommit{
     		ShortSha: base.ShortSha(run.CommitSHA),
     		Link:     fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
    diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
    index 833f59981b4..5397411b59c 100644
    --- a/routers/web/repo/issue.go
    +++ b/routers/web/repo/issue.go
    @@ -9,7 +9,6 @@ import (
     	"fmt"
     	"html/template"
     	"net/http"
    -	"net/url"
     	"strconv"
     	"strings"
     
    @@ -114,7 +113,6 @@ func MustAllowPulls(ctx *context.Context) {
     	// User can send pull request if owns a forked repository.
     	if ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
     		ctx.Repo.PullRequest.Allowed = true
    -		ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Doer.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName)
     	}
     }
     
    diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
    index e30129bb44c..717d7cbce1d 100644
    --- a/routers/web/repo/setting/setting.go
    +++ b/routers/web/repo/setting/setting.go
    @@ -22,6 +22,7 @@ import (
     	"code.gitea.io/gitea/modules/base"
     	"code.gitea.io/gitea/modules/git"
     	"code.gitea.io/gitea/modules/indexer/code"
    +	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
     	"code.gitea.io/gitea/modules/indexer/stats"
     	"code.gitea.io/gitea/modules/lfs"
     	"code.gitea.io/gitea/modules/log"
    @@ -905,6 +906,9 @@ func SettingsPost(ctx *context.Context) {
     			log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
     		}
     
    +		// update issue indexer
    +		issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
    +
     		ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
     
     		log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
    @@ -929,6 +933,9 @@ func SettingsPost(ctx *context.Context) {
     			}
     		}
     
    +		// update issue indexer
    +		issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
    +
     		ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
     
     		log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
    diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go
    index e0539f53b0c..b318c4a621a 100644
    --- a/routers/web/repo/view_home.go
    +++ b/routers/web/repo/view_home.go
    @@ -74,9 +74,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
     		schema, _, _ := strings.Cut(app.OpenURL, ":")
     		var iconHTML template.HTML
     		if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
    -			iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2")
    +			iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16)
     		} else {
    -			iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
    +			iconHTML = svg.RenderHTML("gitea-git", 16) // TODO: it could support user's customized icon in the future
     		}
     		tmplApps = append(tmplApps, map[string]any{
     			"DisplayName": app.DisplayName,
    diff --git a/services/context/repo.go b/services/context/repo.go
    index cf328ca97b7..9b544391103 100644
    --- a/services/context/repo.go
    +++ b/services/context/repo.go
    @@ -39,10 +39,9 @@ import (
     
     // PullRequest contains information to make a pull request
     type PullRequest struct {
    -	BaseRepo       *repo_model.Repository
    -	Allowed        bool
    -	SameRepo       bool
    -	HeadInfoSubURL string // [:] url segment
    +	BaseRepo *repo_model.Repository
    +	Allowed  bool // it only used by the web tmpl: "PullRequestCtx.Allowed"
    +	SameRepo bool // it only used by the web tmpl: "PullRequestCtx.SameRepo"
     }
     
     // Repository contains information to operate a repository
    @@ -401,6 +400,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
     // RepoAssignment returns a middleware to handle repository assignment
     func RepoAssignment(ctx *Context) context.CancelFunc {
     	if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
    +		// FIXME: it should panic in dev/test modes to have a clear behavior
     		log.Trace("RepoAssignment was exec already, skipping second call ...")
     		return nil
     	}
    @@ -697,7 +697,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
     		ctx.Data["BaseRepo"] = repo.BaseRepo
     		ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
     		ctx.Repo.PullRequest.Allowed = canPush
    -		ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Repo.Owner.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName)
     	} else if repo.AllowsPulls(ctx) {
     		// Or, this is repository accepts pull requests between branches.
     		canCompare = true
    @@ -705,7 +704,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
     		ctx.Repo.PullRequest.BaseRepo = repo
     		ctx.Repo.PullRequest.Allowed = canPush
     		ctx.Repo.PullRequest.SameRepo = true
    -		ctx.Repo.PullRequest.HeadInfoSubURL = util.PathEscapeSegments(ctx.Repo.BranchName)
     	}
     	ctx.Data["CanCompareOrPull"] = canCompare
     	ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
    @@ -771,20 +769,6 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
     	return ""
     }
     
    -func isStringLikelyCommitID(objFmt git.ObjectFormat, s string, minLength ...int) bool {
    -	minLen := util.OptionalArg(minLength, objFmt.FullLength())
    -	if len(s) < minLen || len(s) > objFmt.FullLength() {
    -		return false
    -	}
    -	for _, c := range s {
    -		isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
    -		if !isHex {
    -			return false
    -		}
    -	}
    -	return true
    -}
    -
     func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
     	extraRef := util.OptionalArg(optionalExtraRef)
     	reqPath := ctx.PathParam("*")
    @@ -799,7 +783,7 @@ func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (
     
     	// For legacy support only full commit sha
     	parts := strings.Split(reqPath, "/")
    -	if isStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
    +	if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
     		// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
     		repo.TreePath = strings.Join(parts[1:], "/")
     		return parts[0], RepoRefCommit
    @@ -849,7 +833,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
     		return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
     	case RepoRefCommit:
     		parts := strings.Split(path, "/")
    -		if isStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
    +		if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
     			// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
     			repo.TreePath = strings.Join(parts[1:], "/")
     			return parts[0]
    @@ -985,7 +969,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
     					return cancel
     				}
     				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
    -			} else if isStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
    +			} else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
     				ctx.Repo.IsViewCommit = true
     				ctx.Repo.CommitID = refName
     
    diff --git a/services/feed/notifier.go b/services/feed/notifier.go
    index a8820aeb777..d941027c352 100644
    --- a/services/feed/notifier.go
    +++ b/services/feed/notifier.go
    @@ -417,6 +417,12 @@ func (a *actionNotifier) SyncPushCommits(ctx context.Context, pusher *user_model
     }
     
     func (a *actionNotifier) SyncCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
    +	// ignore pull sync message for pull requests refs
    +	// TODO: it's better to have a UI to let users chose
    +	if refFullName.IsPull() {
    +		return
    +	}
    +
     	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
     		ActUserID: repo.OwnerID,
     		ActUser:   repo.MustOwner(ctx),
    @@ -431,6 +437,12 @@ func (a *actionNotifier) SyncCreateRef(ctx context.Context, doer *user_model.Use
     }
     
     func (a *actionNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName) {
    +	// ignore pull sync message for pull requests refs
    +	// TODO: it's better to have a UI to let users chose
    +	if refFullName.IsPull() {
    +		return
    +	}
    +
     	if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{
     		ActUserID: repo.OwnerID,
     		ActUser:   repo.MustOwner(ctx),
    diff --git a/services/repository/branch.go b/services/repository/branch.go
    index 508817c83e6..3a95aab2649 100644
    --- a/services/repository/branch.go
    +++ b/services/repository/branch.go
    @@ -305,7 +305,7 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames,
     	}
     
     	return db.WithTx(ctx, func(ctx context.Context) error {
    -		branches, err := git_model.GetBranches(ctx, repoID, branchNames)
    +		branches, err := git_model.GetBranches(ctx, repoID, branchNames, true)
     		if err != nil {
     			return fmt.Errorf("git_model.GetBranches: %v", err)
     		}
    diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl
    index 5537e9e6172..fa1adb3e3ba 100644
    --- a/templates/repo/actions/runs_list.tmpl
    +++ b/templates/repo/actions/runs_list.tmpl
    @@ -27,10 +27,10 @@
     				
     			
     			
    - {{if .RefLink}} - {{.PrettyRef}} + {{if .IsRefDeleted}} + {{.PrettyRef}} {{else}} - {{.PrettyRef}} + {{.PrettyRef}} {{end}}
    {{svg "octicon-calendar" 16}}{{DateUtils.TimeSince .Updated}}
    diff --git a/templates/repo/clone_buttons.tmpl b/templates/repo/clone_buttons.tmpl index 91952c8a06d..03b7a561daa 100644 --- a/templates/repo/clone_buttons.tmpl +++ b/templates/repo/clone_buttons.tmpl @@ -1,15 +1,13 @@ - -{{if $.CloneButtonShowHTTPS}} - + {{end}} + {{if $.CloneButtonShowSSH}} + + {{end}} + + -{{end}} -{{if $.CloneButtonShowSSH}} - -{{end}} - - +
    diff --git a/templates/repo/clone_panel.tmpl b/templates/repo/clone_panel.tmpl new file mode 100644 index 00000000000..8cbeda132dd --- /dev/null +++ b/templates/repo/clone_panel.tmpl @@ -0,0 +1,44 @@ + +
    +
    {{svg "octicon-terminal"}} Clone
    + +
    + + {{if $.CloneButtonShowHTTPS}} + + {{end}} + {{if $.CloneButtonShowSSH}} + + {{end}} +
    +
    + +
    +
    + +
    + {{svg "octicon-copy" 14}} +
    +
    +
    + + {{if not .PageIsWiki}} +
    + {{range .OpenWithEditorApps}} + {{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}} + {{end}} +
    + + {{if and (not $.DisableDownloadSourceArchives) $.RefName}} +
    + + {{end}} + {{end}} +
    diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl deleted file mode 100644 index 40dae76dc71..00000000000 --- a/templates/repo/clone_script.tmpl +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/templates/repo/contributors.tmpl b/templates/repo/contributors.tmpl index 6b8a63fe996..882d8b205cf 100644 --- a/templates/repo/contributors.tmpl +++ b/templates/repo/contributors.tmpl @@ -1,6 +1,7 @@ {{if .Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
    {{svg "octicon-kebab-horizontal" 18}}
    {{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}} - - + + {{end}} {{if and (not $file.IsSubmodule) (not $.PageIsWiki)}} {{if $file.IsDeleted}} diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl index d3a81bc51d2..7170fe36020 100644 --- a/templates/repo/empty.tmpl +++ b/templates/repo/empty.tmpl @@ -37,9 +37,7 @@ {{end}} {{end}} -
    - {{template "repo/clone_buttons" .}} -
    + {{template "repo/clone_buttons" .}}
    @@ -73,7 +71,6 @@ git push -u origin {{.Repository.DefaultBranch}}
    {{ctx.Locale.Tr "repo.empty_message"}} {{end}} - {{template "repo/clone_script" .}} diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 46d0398c210..4e6d375b517 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -102,27 +102,10 @@ {{end}} - {{/* by default, the row-right flex grows, but on non-root tree path, it should not because the row-left might contain a long path */}} -
    +
    {{if $isTreePathRoot}} -
    - {{template "repo/clone_buttons" .}} - - {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} -
    + {{template "repo/clone_panel" .}} {{end}} {{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}} @@ -140,6 +123,9 @@ {{template "repo/code/upstream_diverging_info" .}} {{end}} {{template "repo/view_list" .}} + {{if and .ReadmeExist (or .IsMarkup .IsPlainText)}} + {{template "repo/view_file" .}} + {{end}} {{end}}
    diff --git a/templates/repo/issue/branch_selector_field.tmpl b/templates/repo/issue/branch_selector_field.tmpl index 286ac0cd051..9183b7b46ab 100644 --- a/templates/repo/issue/branch_selector_field.tmpl +++ b/templates/repo/issue/branch_selector_field.tmpl @@ -1,3 +1,17 @@ +{{/* TODO: RemoveIssueRef: the Issue.Ref will be removed in 1.24 or 1.25 if no end user really needs it or there could be better alternative then. +PR: https://github.com/go-gitea/gitea/pull/32744 + +The Issue.Ref was added by Add possibility to record branch or tag information in an issue (#780) +After 8 years, this "branch selector" does nothing more than saving the branch/tag name into database and displays it. + +There are still users using it: +* @didim99: it is a really useful feature to specify a branch in which issue found. + +Still needs to figure out: +* Could the "recording branch/tag name" be replaced by other approaches? + * Write the branch name in the issue title/body then it will still be displayed, eg: `[bug] (fix/ui-broken-bug) there is a bug ....` +* Is "GitHub-like development sidebar (`#31899`)" good enough (or better) for your usage? +*/}} {{if and (not .Issue.IsPull) (not .PageIsComparePull)}} {{else}} -
    {{ctx.Locale.Tr "no_results_found"}}
    +
    {{ctx.Locale.Tr "no_results_found"}}
    {{end}}
    diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index ceaaebc4d54..dd4c7617ce1 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -47,7 +47,8 @@
    - {{template "repo/issue/branch_selector_field" $}} + {{template "repo/issue/branch_selector_field" $}}{{/* TODO: RemoveIssueRef: template "repo/issue/branch_selector_field" $*/}} + {{if .PageIsComparePull}} {{template "repo/issue/sidebar/reviewer_list" $.IssuePageMetaData}}
    diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 02f5d3e2df9..987a882be7b 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -1,5 +1,5 @@
    - {{template "repo/issue/branch_selector_field" $}} + {{template "repo/issue/branch_selector_field" $}}{{/* TODO: RemoveIssueRef: template "repo/issue/branch_selector_field" $*/}} {{if .Issue.IsPull}} {{template "repo/issue/sidebar/reviewer_list" $.IssuePageMetaData}} diff --git a/templates/repo/latest_commit.tmpl b/templates/repo/latest_commit.tmpl index 9d718d7197a..34a5df8f776 100644 --- a/templates/repo/latest_commit.tmpl +++ b/templates/repo/latest_commit.tmpl @@ -1,5 +1,5 @@ {{if not .LatestCommit}} -
    + … {{else}} {{if .LatestCommitUser}} {{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 3edfbb34745..0fdb45e574c 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -1,73 +1,57 @@ - - - - - - - - - {{if .HasParentPath}} - - - - {{end}} - {{range $item := .Files}} +{{/* use grid layout, still use the old ID because there are many other CSS styles depending on this ID */}} +
    +
    +
    {{template "repo/latest_commit" .}}
    +
    {{if and .LatestCommit .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}
    +
    + {{if .HasParentPath}} +
    + {{svg "octicon-reply"}} .. +
    + {{end}} + {{range $item := .Files}} +
    {{$entry := $item.Entry}} {{$commit := $item.Commit}} {{$subModuleFile := $item.SubModuleFile}} -
    - - - - - {{end}} - -
    -
    -
    - {{template "repo/latest_commit" .}} -
    -
    -
    {{if .LatestCommit}}{{if .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}{{end}}
    {{svg "octicon-reply"}}..
    - - {{if $entry.IsSubModule}} - {{svg "octicon-file-submodule"}} - {{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} - {{if $refURL}} - {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} +
    + {{if $entry.IsSubModule}} + {{svg "octicon-file-submodule"}} + {{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}} {{/* FIXME: the usage of AppUrl seems incorrect, it would be fixed in the future, use AppSubUrl instead */}} + {{if $refURL}} + {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} + {{else}} + {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} + {{end}} + {{else}} + {{if $entry.IsDir}} + {{$subJumpablePathName := $entry.GetSubJumpablePathName}} + {{svg "octicon-file-directory-fill"}} + + {{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}} + {{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}} + {{if eq $subJumpablePathFieldLast 0}} + {{$subJumpablePathName}} {{else}} - {{$entry.Name}}@{{ShortSha $subModuleFile.RefID}} + {{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}} + {{StringUtils.Join $subJumpablePathPrefixes "/"}}/{{index $subJumpablePathFields $subJumpablePathFieldLast}} {{end}} - {{else}} - {{if $entry.IsDir}} - {{$subJumpablePathName := $entry.GetSubJumpablePathName}} - {{svg "octicon-file-directory-fill"}} - - {{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}} - {{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}} - {{if eq $subJumpablePathFieldLast 0}} - {{$subJumpablePathName}} - {{else}} - {{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}} - {{StringUtils.Join $subJumpablePathPrefixes "/"}}/{{index $subJumpablePathFields $subJumpablePathFieldLast}} - {{end}} - - {{else}} - {{svg (printf "octicon-%s" (EntryIcon $entry))}} - {{$entry.Name}} - {{end}} - {{end}} - -
    - - {{if $commit}} - {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}} - {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}} - {{else}} -
    - {{end}} -
    -
    {{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}
    -{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}} - {{template "repo/view_file" .}} -{{end}} + + {{else}} + {{svg (printf "octicon-%s" (EntryIcon $entry))}} + {{$entry.Name}} + {{end}} + {{end}} +
    +
    + {{if $commit}} + {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}} + {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}} + {{else}} + … {{/* will be loaded again by LastCommitLoaderURL */}} + {{end}} +
    +
    {{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}
    +
    + {{end}} + diff --git a/templates/repo/wiki/revision.tmpl b/templates/repo/wiki/revision.tmpl index 045cc41d81e..ca8954928d2 100644 --- a/templates/repo/wiki/revision.tmpl +++ b/templates/repo/wiki/revision.tmpl @@ -15,10 +15,7 @@
    -
    - {{template "repo/clone_buttons" .}} - {{template "repo/clone_script" .}} -
    + {{template "repo/clone_panel" .}}

    {{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}

    diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index c8e0b4254ca..843a977e3e5 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -4,7 +4,7 @@ {{$title := .title}}
    -
    +
    -
    - {{template "repo/clone_buttons" .}} - {{template "repo/clone_script" .}} -
    + {{template "repo/clone_panel" .}}
    @@ -45,15 +42,15 @@
    - diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index fe5184e7d22..a2b802f2a2d 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -99,7 +99,7 @@ {{.Project.Title}} {{end}} - {{if .Ref}} + {{if .Ref}}{{/* TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl" */}} {{svg "octicon-git-branch" 14}} {{index $.IssueRefEndNames .ID}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c06c0ad1541..82a301da2fe 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -5045,6 +5045,63 @@ "$ref": "#/responses/repoArchivedError" } } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Update a branch", + "operationId": "repoUpdateBranch", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the branch", + "name": "branch", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpdateBranchRepoOption" + } + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "422": { + "$ref": "#/responses/validationError" + } + } } }, "/repos/{owner}/{repo}/collaborators": { @@ -24968,6 +25025,22 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "UpdateBranchRepoOption": { + "description": "UpdateBranchRepoOption options when updating a branch in a repository", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "description": "New branch name", + "type": "string", + "uniqueItems": true, + "x-go-name": "Name" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "UpdateFileOptions": { "description": "UpdateFileOptions options for updating files\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)", "type": "object", diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 8e49516aa72..24a041de17e 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -5,6 +5,7 @@ package integration import ( "net/http" + "net/http/httptest" "net/url" "testing" @@ -186,6 +187,37 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran return resp.Result().StatusCode == status } +func TestAPIUpdateBranch(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _ *url.URL) { + t.Run("UpdateBranchWithEmptyRepo", func(t *testing.T) { + testAPIUpdateBranch(t, "user10", "repo6", "master", "test", http.StatusNotFound) + }) + t.Run("UpdateBranchWithSameBranchNames", func(t *testing.T) { + resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "master", http.StatusUnprocessableEntity) + assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.") + }) + t.Run("UpdateBranchThatAlreadyExists", func(t *testing.T) { + resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "branch2", http.StatusUnprocessableEntity) + assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.") + }) + t.Run("UpdateBranchWithNonExistentBranch", func(t *testing.T) { + resp := testAPIUpdateBranch(t, "user2", "repo1", "i-dont-exist", "new-branch-name", http.StatusNotFound) + assert.Contains(t, resp.Body.String(), "Branch doesn't exist.") + }) + t.Run("RenameBranchNormalScenario", func(t *testing.T) { + testAPIUpdateBranch(t, "user2", "repo1", "branch2", "new-branch-name", http.StatusNoContent) + }) + }) +} + +func testAPIUpdateBranch(t *testing.T, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder { + token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.UpdateBranchRepoOption{ + Name: to, + }).AddTokenAuth(token) + return MakeRequest(t, req, expectedHTTPStatus) +} + func TestAPIBranchProtection(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/api_packages_cran_test.go b/tests/integration/api_packages_cran_test.go index d307e87d4e0..667ba0908ce 100644 --- a/tests/integration/api_packages_cran_test.go +++ b/tests/integration/api_packages_cran_test.go @@ -115,6 +115,14 @@ func TestPackageCran(t *testing.T) { MakeRequest(t, req, http.StatusOK) }) + t.Run("DownloadArchived", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/src/contrib/Archive/%s/%s_%s.tar.gz", url, packageName, packageName, packageVersion)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) + }) + t.Run("Enumerate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index f3188eb49f6..9565e4d2090 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -24,15 +24,27 @@ func TestAPICompareBranches(t *testing.T) { session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - repoName := "repo20" + t.Run("CompareBranches", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) - var apiResp *api.Compare - DecodeJSON(t, resp, &apiResp) + assert.Equal(t, 2, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 2) + }) - assert.Equal(t, 2, apiResp.TotalCommits) - assert.Len(t, apiResp.Commits, 2) + t.Run("CompareCommits", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + }) } diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 4338e196174..6b1b6b8b21a 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -95,6 +95,11 @@ func TestMain(m *testing.M) { os.Unsetenv("GIT_COMMITTER_EMAIL") os.Unsetenv("GIT_COMMITTER_DATE") + // Avoid loading the default system config. On MacOS, this config + // sets the osxkeychain credential helper, which will cause tests + // to freeze with a dialog. + os.Setenv("GIT_CONFIG_NOSYSTEM", "true") + err := unittest.InitFixtures( unittest.FixturesOptions{ Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), @@ -440,7 +445,7 @@ func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v any) { t.Helper() decoder := json.NewDecoder(resp.Body) - assert.NoError(t, decoder.Decode(v)) + require.NoError(t, decoder.Decode(v)) } func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) { diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index b967ccad1ec..7889dfaf3b7 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -49,7 +49,7 @@ func testViewRepo(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR") + files := htmlDoc.doc.Find("#repo-files-table .repo-file-item") type file struct { fileName string @@ -61,7 +61,7 @@ func testViewRepo(t *testing.T) { var items []file files.Each(func(i int, s *goquery.Selection) { - tds := s.Find("td") + tds := s.Find(".repo-file-cell") var f file tds.Each(func(i int, s *goquery.Selection) { if i == 0 { @@ -127,10 +127,10 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link") + link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) - _, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link") + _, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") assert.False(t, exists) } @@ -143,10 +143,10 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link") + link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) - link, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link") + link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") assert.True(t, exists, "The template has changed") sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port) assert.Equal(t, sshURL, link) @@ -161,7 +161,7 @@ func TestViewRepoWithSymlinks(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name > SPAN.truncate") + files := htmlDoc.doc.Find("#repo-files-table .repo-file-cell.name") items := files.Map(func(i int, s *goquery.Selection) string { cls, _ := s.Find("SVG").Attr("class") file := strings.Trim(s.Find("A").Text(), " \t\n") diff --git a/tools/generate-images.js b/tools/generate-images.js index 0bd3af29e4c..d28e0916f73 100755 --- a/tools/generate-images.js +++ b/tools/generate-images.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -import imageminZopfli from 'imagemin-zopfli'; // eslint-disable-line i/no-unresolved -import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node'; // eslint-disable-line i/no-unresolved +import imageminZopfli from 'imagemin-zopfli'; // eslint-disable-line import-x/no-unresolved +import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node'; // eslint-disable-line import-x/no-unresolved import {optimize} from 'svgo'; import {readFile, writeFile} from 'node:fs/promises'; import {argv, exit} from 'node:process'; diff --git a/web_src/css/index.css b/web_src/css/index.css index 158ae42d3e3..02513aebc1c 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -66,7 +66,10 @@ @import "./repo/wiki.css"; @import "./repo/header.css"; @import "./repo/home.css"; +@import "./repo/home-file-list.css"; @import "./repo/reactions.css"; +@import "./repo/clone.css"; +@import "./repo/commit-sign.css"; @import "./editor/fileeditor.css"; @import "./editor/combomarkdowneditor.css"; diff --git a/web_src/css/repo.css b/web_src/css/repo.css index f5785c41a77..9a43e10e826 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -101,42 +101,6 @@ margin-bottom: 12px; } -.repository .clone-panel { - display: flex; - flex: 1; -} - -.repository.wiki .clone-panel { - flex: 0; -} - -.repository.wiki .clone-panel input { - width: 20ch; -} - -.repository .clone-panel #repo-clone-url { - border-radius: 0; - flex: 1; -} - -.repository .ui.action.input.clone-panel > button + button, -.repository .ui.action.input.clone-panel > button + input { - margin-left: -1px; /* make the borders overlap to avoid double borders */ -} - -.repository .clone-panel > button:first-of-type { - border-radius: var(--border-radius) 0 0 var(--border-radius) !important; -} - -.repository .clone-panel > button:last-of-type { - border-radius: 0 var(--border-radius) var(--border-radius) 0 !important; -} - -.repository .clone-panel .dropdown .menu { - right: 0 !important; - left: auto !important; -} - .repository .repo-description { font-size: 16px; margin-bottom: 5px; @@ -177,138 +141,6 @@ td .commit-summary { overflow-wrap: anywhere; } -/* this is what limits the commit table width to a value that works on all viewport sizes */ -#repo-files-table th:first-of-type { - max-width: calc(calc(min(100vw, 1280px)) - 145px - calc(2 * var(--page-margin-x))); -} - -.repository.file.list #repo-files-table thead th { - font-weight: var(--font-weight-normal); -} - -.repository.file.list #repo-files-table tbody .svg { - margin-left: 3px; - margin-right: 5px; -} - -.repository.file.list #repo-files-table tbody .svg.octicon-reply { - margin-right: 10px; -} - -.repository.file.list #repo-files-table tbody .svg.octicon-file-directory-fill, -.repository.file.list #repo-files-table tbody .svg.octicon-file-submodule { - color: var(--color-primary); -} - -.repository.file.list #repo-files-table tbody .svg.octicon-file, -.repository.file.list #repo-files-table tbody .svg.octicon-file-symlink-file, -.repository.file.list #repo-files-table tbody .svg.octicon-file-directory-symlink { - color: var(--color-secondary-dark-7); -} - -.repository.file.list #repo-files-table td { - padding-top: 0; - padding-bottom: 0; - overflow: initial; -} - -.repository.file.list #repo-files-table td.name { - width: 33%; - max-width: calc(100vw - 140px); -} - -@media (min-width: 1201px) { - .repository.file.list #repo-files-table td.name { - max-width: 150px; - } -} - -@media (min-width: 992px) and (max-width: 1200px) { - .repository.file.list #repo-files-table td.name { - max-width: 200px; - } -} - -@media (min-width: 768px) and (max-width: 991.98px) { - .repository.file.list #repo-files-table td.name { - max-width: 300px; - } -} - -.repository.file.list #repo-files-table td.message { - color: var(--color-text-light-1); - width: 66%; -} - -@media (min-width: 1201px) { - .repository.file.list #repo-files-table td.message { - max-width: 400px; - } -} - -@media (min-width: 992px) and (max-width: 1200px) { - .repository.file.list #repo-files-table td.message { - max-width: 350px; - } -} - -@media (min-width: 768px) and (max-width: 991.98px) { - .repository.file.list #repo-files-table td.message { - max-width: 250px; - } -} - -.repository.file.list #repo-files-table td.age { - color: var(--color-text-light-1); -} - -.repository.file.list #repo-files-table td .truncate { - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - padding-top: 8px; - padding-bottom: 8px; -} - -.repository.file.list #repo-files-table td a { - padding-top: 8px; - padding-bottom: 8px; -} - -.repository.file.list #repo-files-table td .at { - margin-left: 3px; - margin-right: 3px; -} - -.repository.file.list #repo-files-table td > * { - vertical-align: middle; -} - -.repository.file.list #repo-files-table td.message .isSigned { - cursor: default; -} - -.repository.file.list #repo-files-table tr:last-of-type td:first-child { - border-bottom-left-radius: var(--border-radius); -} - -.repository.file.list #repo-files-table tr:last-of-type td:last-child { - border-bottom-right-radius: var(--border-radius); -} - -.repository.file.list #repo-files-table tr:hover { - background-color: var(--color-hover); -} - -.repository.file.list #repo-files-table tr.has-parent a { - display: inline-block; - padding-top: 8px; - padding-bottom: 8px; - width: calc(100% - 1.25rem); -} - .repository.file.list .non-diff-file-content .header .icon { font-size: 1em; } @@ -787,47 +619,6 @@ td .commit-summary { height: 30px !important; } -.singular-commit .shabox .sha.label { - margin: 0; - border: 1px solid var(--color-light-border); -} - -.singular-commit .shabox .sha.label.isSigned.isWarning { - border: 1px solid var(--color-red-badge); - background: var(--color-red-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isWarning:hover { - background: var(--color-red-badge-hover-bg) !important; -} - -.singular-commit .shabox .sha.label.isSigned.isVerified { - border: 1px solid var(--color-green-badge); - background: var(--color-green-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isVerified:hover { - background: var(--color-green-badge-hover-bg) !important; -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted { - border: 1px solid var(--color-yellow-badge); - background: var(--color-yellow-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted:hover { - background: var(--color-yellow-badge-hover-bg) !important; -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched { - border: 1px solid var(--color-orange-badge); - background: var(--color-orange-badge-bg); -} - -.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched:hover { - background: var(--color-orange-badge-hover-bg) !important; -} - .repository.view.issue .comment-list .timeline-item.event > .commit-status-link { float: right; margin-right: 8px; @@ -1162,151 +953,6 @@ td .commit-summary { background-color: var(--color-light) !important; } -.repository #commits-table td.sha .sha.label, -.repository #repo-files-table .sha.label, -.repository #repo-file-commit-box .sha.label, -.repository #rev-list .sha.label, -.repository .timeline-item.commits-list .singular-commit .sha.label { - border: 1px solid var(--color-light-border); -} - -.repository #commits-table td.sha .sha.label .detail.icon, -.repository #repo-files-table .sha.label .detail.icon, -.repository #repo-file-commit-box .sha.label .detail.icon, -.repository #rev-list .sha.label .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon { - background: var(--color-light); - margin: -6px -10px -4px 0; - padding: 5px 4px 5px 6px; - border-left: 1px solid var(--color-light-border); - border-top: 0; - border-right: 0; - border-bottom: 0; - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.repository #commits-table td.sha .sha.label .detail.icon .svg, -.repository #repo-files-table .sha.label .detail.icon .svg, -.repository #repo-file-commit-box .sha.label .detail.icon .svg, -.repository #rev-list .sha.label .detail.icon .svg, -.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon .svg { - margin: 0 0.25em 0 0; -} - -.repository #commits-table td.sha .sha.label .detail.icon > div, -.repository #repo-files-table .sha.label .detail.icon > div, -.repository #repo-file-commit-box .sha.label .detail.icon > div, -.repository #rev-list .sha.label .detail.icon > div, -.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon > div { - display: flex; - align-items: center; -} - -.repository #commits-table td.sha .sha.label.isSigned.isWarning, -.repository #repo-files-table .sha.label.isSigned.isWarning, -.repository #repo-file-commit-box .sha.label.isSigned.isWarning, -.repository #rev-list .sha.label.isSigned.isWarning, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning { - border: 1px solid var(--color-red-badge); - background: var(--color-red-badge-bg); -} - -.repository #commits-table td.sha .sha.label.isSigned.isWarning .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isWarning .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isWarning .detail.icon, -.repository #rev-list .sha.label.isSigned.isWarning .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning .detail.icon { - border-left: 1px solid var(--color-red-badge); - color: var(--color-red-badge); -} - -.repository #commits-table td.sha .sha.label.isSigned.isWarning:hover, -.repository #repo-files-table .sha.label.isSigned.isWarning:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isWarning:hover, -.repository #rev-list .sha.label.isSigned.isWarning:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning:hover { - background: var(--color-red-badge-hover-bg) !important; -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerified, -.repository #repo-files-table .sha.label.isSigned.isVerified, -.repository #repo-file-commit-box .sha.label.isSigned.isVerified, -.repository #rev-list .sha.label.isSigned.isVerified, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified { - border: 1px solid var(--color-green-badge); - background: var(--color-green-badge-bg); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isVerified .detail.icon, -.repository #rev-list .sha.label.isSigned.isVerified .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified .detail.icon { - border-left: 1px solid var(--color-green-badge); - color: var(--color-green-badge); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerified:hover, -.repository #repo-files-table .sha.label.isSigned.isVerified:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isVerified:hover, -.repository #rev-list .sha.label.isSigned.isVerified:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified:hover { - background: var(--color-green-badge-hover-bg) !important; -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted, -.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted { - border: 1px solid var(--color-yellow-badge); - background: var(--color-yellow-badge-bg); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted .detail.icon { - border-left: 1px solid var(--color-yellow-badge); - color: var(--color-yellow-badge); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted:hover { - background: var(--color-yellow-badge-hover-bg) !important; -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched, -.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched { - border: 1px solid var(--color-orange-badge); - background: var(--color-orange-badge-bg); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched .detail.icon, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched .detail.icon { - border-left: 1px solid var(--color-orange-badge); - color: var(--color-orange-badge); -} - -.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched:hover, -.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched:hover { - background: var(--color-orange-badge-hover-bg) !important; -} - .repository .data-table { width: 100%; } @@ -1615,14 +1261,6 @@ td .commit-summary { font-weight: var(--font-weight-normal); } -.repository.quickstart .guide #repo-clone-url { - border-radius: 0; - padding: 5px 10px; - font-size: 1.2em; - line-height: 1.4; - flex: 1 -} - .empty-placeholder { display: flex; flex-direction: column; @@ -1678,92 +1316,6 @@ td .commit-summary { padding-top: 0; } -.repository .ui.attached.isSigned.isWarning { - border-left: 1px solid var(--color-error-border); - border-right: 1px solid var(--color-error-border); -} - -.repository .ui.attached.isSigned.isWarning.top, -.repository .ui.attached.isSigned.isWarning.message { - border-top: 1px solid var(--color-error-border); -} - -.repository .ui.attached.isSigned.isWarning.message { - box-shadow: none; - background-color: var(--color-error-bg); - color: var(--color-error-text); -} - -.repository .ui.attached.isSigned.isWarning.message .ui.text { - color: var(--color-error-text); -} - -.repository .ui.attached.isSigned.isWarning:last-child, -.repository .ui.attached.isSigned.isWarning.bottom { - border-bottom: 1px solid var(--color-error-border); -} - -.repository .ui.attached.isSigned.isVerified { - border-left: 1px solid var(--color-success-border); - border-right: 1px solid var(--color-success-border); -} - -.repository .ui.attached.isSigned.isVerified.top, -.repository .ui.attached.isSigned.isVerified.message { - border-top: 1px solid var(--color-success-border); -} - -.repository .ui.attached.isSigned.isVerified.message { - box-shadow: none; - background-color: var(--color-success-bg); - color: var(--color-success-text); -} - -.repository .ui.attached.isSigned.isVerified.message .pull-right { - color: var(--color-text); -} - -.repository .ui.attached.isSigned.isVerified.message .ui.text { - color: var(--color-success-text); -} - -.repository .ui.attached.isSigned.isVerified:last-child, -.repository .ui.attached.isSigned.isVerified.bottom { - border-bottom: 1px solid var(--color-success-border); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted, -.repository .ui.attached.isSigned.isVerifiedUnmatched { - border-left: 1px solid var(--color-warning-border); - border-right: 1px solid var(--color-warning-border); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted.top, -.repository .ui.attached.isSigned.isVerifiedUnmatched.top, -.repository .ui.attached.isSigned.isVerifiedUntrusted.message, -.repository .ui.attached.isSigned.isVerifiedUnmatched.message { - border-top: 1px solid var(--color-warning-border); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted.message, -.repository .ui.attached.isSigned.isVerifiedUnmatched.message { - box-shadow: none; - background-color: var(--color-warning-bg); - color: var(--color-warning-text); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted.message .ui.text, -.repository .ui.attached.isSigned.isVerifiedUnmatched.message .ui.text { - color: var(--color-warning-text); -} - -.repository .ui.attached.isSigned.isVerifiedUntrusted:last-child, -.repository .ui.attached.isSigned.isVerifiedUnmatched:last-child, -.repository .ui.attached.isSigned.isVerifiedUntrusted.bottom, -.repository .ui.attached.isSigned.isVerifiedUnmatched.bottom { - border-bottom: 1px solid var(--color-warning-border); -} - .repository .ui.fluid.action.input .ui.search.action.input { flex: auto; } @@ -1782,7 +1334,7 @@ td .commit-summary { .repository .repository-summary .sub-menu .item { flex: 1; - height: 30px; + height: 33px; /* match search bar height, need to be improved in the future to use some consistent methods */ line-height: var(--line-height-default); display: flex; align-items: center; @@ -2110,26 +1662,18 @@ td .commit-summary { display: flex; align-items: center; gap: 8px; - justify-content: space-between; + flex-wrap: wrap; } .repo-button-row-left, .repo-button-row-right { display: flex; - flex: 1; align-items: center; gap: 0.5rem; } -.repo-button-row-right { - justify-content: flex-end; -} - -@media (max-width: 1200px) { - .repository:not(.wiki) .repo-button-row { - flex-direction: column; - align-items: stretch; - } +.repo-button-row-left { + flex: 1; } .repo-button-row .button { @@ -2143,16 +1687,6 @@ td .commit-summary { padding-right: 22px !important; /* normal buttons have !important paddings, so we need to override it for dropdown (Add File) icons */ } -.repo-button-row input { - height: 30px; -} - -@media (max-width: 600px) { - .repo-button-row-left { - flex-wrap: wrap; - } -} - tbody.commit-list { vertical-align: baseline; } @@ -2178,11 +1712,6 @@ tbody.commit-list { overflow-wrap: anywhere; } -/* but in the repo-files-table we cannot */ -#repo-files-table .commit-list .message-wrapper { - display: inline-block; -} - @media (max-width: 767.98px) { tr.commit-list { width: 100%; @@ -2578,25 +2107,6 @@ tbody.commit-list { } @media (max-width: 767.98px) { - .repository.file.list #repo-files-table .entry, - .repository.file.list #repo-files-table .commit-list { - align-items: center; - display: flex !important; - padding-top: 4px; - padding-bottom: 4px; - } - .repository.file.list #repo-files-table .entry td.age, - .repository.file.list #repo-files-table .commit-list td.age, - .repository.file.list #repo-files-table .entry th.age, - .repository.file.list #repo-files-table .commit-list th.age { - margin-left: auto; - } - .repository.file.list #repo-files-table .entry td.message, - .repository.file.list #repo-files-table .commit-list td.message, - .repository.file.list #repo-files-table .entry span.commit-summary, - .repository.file.list #repo-files-table .commit-list tr span.commit-summary { - display: none !important; - } .repository.view.issue .comment-list .timeline, .repository.view.issue .comment-list .timeline-item { margin-left: 0; diff --git a/web_src/css/repo/clone.css b/web_src/css/repo/clone.css new file mode 100644 index 00000000000..3f6a1323fea --- /dev/null +++ b/web_src/css/repo/clone.css @@ -0,0 +1,35 @@ +/* only used by "repo/empty.tmpl" */ +.clone-buttons-combo { + display: flex; + align-items: center; + flex: 1; +} + +.clone-buttons-combo input { + border-left: none !important; + border-radius: 0 !important; + height: 30px; +} + +/* used by the clone-panel popup */ +.clone-panel-field, +.clone-panel-list { + margin: 10px; +} + +.clone-panel-tab .item { + padding: 5px 10px; + background: none; +} + +.clone-panel-tab .item.active { + border-bottom: 3px solid var(--color-secondary); +} + +.clone-panel-tab + .divider { + margin: -1px 0 0; +} + +.clone-panel-list .item { + margin: 5px 0; +} diff --git a/web_src/css/repo/commit-sign.css b/web_src/css/repo/commit-sign.css new file mode 100644 index 00000000000..e7570304194 --- /dev/null +++ b/web_src/css/repo/commit-sign.css @@ -0,0 +1,272 @@ + +.repository .ui.attached.isSigned.isWarning { + border-left: 1px solid var(--color-error-border); + border-right: 1px solid var(--color-error-border); +} + +.repository .ui.attached.isSigned.isWarning.top, +.repository .ui.attached.isSigned.isWarning.message { + border-top: 1px solid var(--color-error-border); +} + +.repository .ui.attached.isSigned.isWarning.message { + box-shadow: none; + background-color: var(--color-error-bg); + color: var(--color-error-text); +} + +.repository .ui.attached.isSigned.isWarning.message .ui.text { + color: var(--color-error-text); +} + +.repository .ui.attached.isSigned.isWarning:last-child, +.repository .ui.attached.isSigned.isWarning.bottom { + border-bottom: 1px solid var(--color-error-border); +} + +.repository .ui.attached.isSigned.isVerified { + border-left: 1px solid var(--color-success-border); + border-right: 1px solid var(--color-success-border); +} + +.repository .ui.attached.isSigned.isVerified.top, +.repository .ui.attached.isSigned.isVerified.message { + border-top: 1px solid var(--color-success-border); +} + +.repository .ui.attached.isSigned.isVerified.message { + box-shadow: none; + background-color: var(--color-success-bg); + color: var(--color-success-text); +} + +.repository .ui.attached.isSigned.isVerified.message .pull-right { + color: var(--color-text); +} + +.repository .ui.attached.isSigned.isVerified.message .ui.text { + color: var(--color-success-text); +} + +.repository .ui.attached.isSigned.isVerified:last-child, +.repository .ui.attached.isSigned.isVerified.bottom { + border-bottom: 1px solid var(--color-success-border); +} + +.repository .ui.attached.isSigned.isVerifiedUntrusted, +.repository .ui.attached.isSigned.isVerifiedUnmatched { + border-left: 1px solid var(--color-warning-border); + border-right: 1px solid var(--color-warning-border); +} + +.repository .ui.attached.isSigned.isVerifiedUntrusted.top, +.repository .ui.attached.isSigned.isVerifiedUnmatched.top, +.repository .ui.attached.isSigned.isVerifiedUntrusted.message, +.repository .ui.attached.isSigned.isVerifiedUnmatched.message { + border-top: 1px solid var(--color-warning-border); +} + +.repository .ui.attached.isSigned.isVerifiedUntrusted.message, +.repository .ui.attached.isSigned.isVerifiedUnmatched.message { + box-shadow: none; + background-color: var(--color-warning-bg); + color: var(--color-warning-text); +} + +.repository .ui.attached.isSigned.isVerifiedUntrusted.message .ui.text, +.repository .ui.attached.isSigned.isVerifiedUnmatched.message .ui.text { + color: var(--color-warning-text); +} + +.repository .ui.attached.isSigned.isVerifiedUntrusted:last-child, +.repository .ui.attached.isSigned.isVerifiedUnmatched:last-child, +.repository .ui.attached.isSigned.isVerifiedUntrusted.bottom, +.repository .ui.attached.isSigned.isVerifiedUnmatched.bottom { + border-bottom: 1px solid var(--color-warning-border); +} + +.repository #commits-table td.sha .sha.label, +.repository #repo-files-table .sha.label, +.repository #repo-file-commit-box .sha.label, +.repository #rev-list .sha.label, +.repository .timeline-item.commits-list .singular-commit .sha.label { + border: 1px solid var(--color-light-border); +} + +.repository #commits-table td.sha .sha.label .detail.icon, +.repository #repo-files-table .sha.label .detail.icon, +.repository #repo-file-commit-box .sha.label .detail.icon, +.repository #rev-list .sha.label .detail.icon, +.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon { + background: var(--color-light); + margin: -6px -10px -4px 0; + padding: 5px 4px 5px 6px; + border-left: 1px solid var(--color-light-border); + border-top: 0; + border-right: 0; + border-bottom: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.repository #commits-table td.sha .sha.label .detail.icon .svg, +.repository #repo-files-table .sha.label .detail.icon .svg, +.repository #repo-file-commit-box .sha.label .detail.icon .svg, +.repository #rev-list .sha.label .detail.icon .svg, +.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon .svg { + margin: 0 0.25em 0 0; +} + +.repository #commits-table td.sha .sha.label .detail.icon > div, +.repository #repo-files-table .sha.label .detail.icon > div, +.repository #repo-file-commit-box .sha.label .detail.icon > div, +.repository #rev-list .sha.label .detail.icon > div, +.repository .timeline-item.commits-list .singular-commit .sha.label .detail.icon > div { + display: flex; + align-items: center; +} + +.repository #commits-table td.sha .sha.label.isSigned.isWarning, +.repository #repo-files-table .sha.label.isSigned.isWarning, +.repository #repo-file-commit-box .sha.label.isSigned.isWarning, +.repository #rev-list .sha.label.isSigned.isWarning, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning { + border: 1px solid var(--color-red-badge); + background: var(--color-red-badge-bg); +} + +.repository #commits-table td.sha .sha.label.isSigned.isWarning .detail.icon, +.repository #repo-files-table .sha.label.isSigned.isWarning .detail.icon, +.repository #repo-file-commit-box .sha.label.isSigned.isWarning .detail.icon, +.repository #rev-list .sha.label.isSigned.isWarning .detail.icon, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning .detail.icon { + border-left: 1px solid var(--color-red-badge); + color: var(--color-red-badge); +} + +.repository #commits-table td.sha .sha.label.isSigned.isWarning:hover, +.repository #repo-files-table .sha.label.isSigned.isWarning:hover, +.repository #repo-file-commit-box .sha.label.isSigned.isWarning:hover, +.repository #rev-list .sha.label.isSigned.isWarning:hover, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isWarning:hover { + background: var(--color-red-badge-hover-bg) !important; +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerified, +.repository #repo-files-table .sha.label.isSigned.isVerified, +.repository #repo-file-commit-box .sha.label.isSigned.isVerified, +.repository #rev-list .sha.label.isSigned.isVerified, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified { + border: 1px solid var(--color-green-badge); + background: var(--color-green-badge-bg); +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon, +.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon, +.repository #repo-file-commit-box .sha.label.isSigned.isVerified .detail.icon, +.repository #rev-list .sha.label.isSigned.isVerified .detail.icon, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified .detail.icon { + border-left: 1px solid var(--color-green-badge); + color: var(--color-green-badge); +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerified:hover, +.repository #repo-files-table .sha.label.isSigned.isVerified:hover, +.repository #repo-file-commit-box .sha.label.isSigned.isVerified:hover, +.repository #rev-list .sha.label.isSigned.isVerified:hover, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerified:hover { + background: var(--color-green-badge-hover-bg) !important; +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted, +.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted, +.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted, +.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted { + border: 1px solid var(--color-yellow-badge); + background: var(--color-yellow-badge-bg); +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted .detail.icon, +.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted .detail.icon, +.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted .detail.icon, +.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted .detail.icon, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted .detail.icon { + border-left: 1px solid var(--color-yellow-badge); + color: var(--color-yellow-badge); +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUntrusted:hover, +.repository #repo-files-table .sha.label.isSigned.isVerifiedUntrusted:hover, +.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUntrusted:hover, +.repository #rev-list .sha.label.isSigned.isVerifiedUntrusted:hover, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUntrusted:hover { + background: var(--color-yellow-badge-hover-bg) !important; +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched, +.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched, +.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched, +.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched { + border: 1px solid var(--color-orange-badge); + background: var(--color-orange-badge-bg); +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched .detail.icon, +.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched .detail.icon, +.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched .detail.icon, +.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched .detail.icon, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched .detail.icon { + border-left: 1px solid var(--color-orange-badge); + color: var(--color-orange-badge); +} + +.repository #commits-table td.sha .sha.label.isSigned.isVerifiedUnmatched:hover, +.repository #repo-files-table .sha.label.isSigned.isVerifiedUnmatched:hover, +.repository #repo-file-commit-box .sha.label.isSigned.isVerifiedUnmatched:hover, +.repository #rev-list .sha.label.isSigned.isVerifiedUnmatched:hover, +.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned.isVerifiedUnmatched:hover { + background: var(--color-orange-badge-hover-bg) !important; +} + +.singular-commit .shabox .sha.label { + margin: 0; + border: 1px solid var(--color-light-border); +} + +.singular-commit .shabox .sha.label.isSigned.isWarning { + border: 1px solid var(--color-red-badge); + background: var(--color-red-badge-bg); +} + +.singular-commit .shabox .sha.label.isSigned.isWarning:hover { + background: var(--color-red-badge-hover-bg) !important; +} + +.singular-commit .shabox .sha.label.isSigned.isVerified { + border: 1px solid var(--color-green-badge); + background: var(--color-green-badge-bg); +} + +.singular-commit .shabox .sha.label.isSigned.isVerified:hover { + background: var(--color-green-badge-hover-bg) !important; +} + +.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted { + border: 1px solid var(--color-yellow-badge); + background: var(--color-yellow-badge-bg); +} + +.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted:hover { + background: var(--color-yellow-badge-hover-bg) !important; +} + +.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched { + border: 1px solid var(--color-orange-badge); + background: var(--color-orange-badge-bg); +} + +.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched:hover { + background: var(--color-orange-badge-hover-bg) !important; +} diff --git a/web_src/css/repo/home-file-list.css b/web_src/css/repo/home-file-list.css new file mode 100644 index 00000000000..ecb26fa6629 --- /dev/null +++ b/web_src/css/repo/home-file-list.css @@ -0,0 +1,75 @@ +#repo-files-table { + width: 100%; + display: grid; + grid-template-columns: auto 1fr auto; + border: 1px solid var(--color-light-border); + border-radius: var(--border-radius); + margin: 10px 0; /* match the "clone-panel-popup" margin to avoid "visual double-border" */ +} + +#repo-files-table .svg.octicon-file-directory-fill, +#repo-files-table .svg.octicon-file-submodule { + color: var(--color-primary); +} + +#repo-files-table .svg.octicon-file, +#repo-files-table .svg.octicon-file-symlink-file, +#repo-files-table .svg.octicon-file-directory-symlink { + color: var(--color-secondary-dark-7); +} + +#repo-files-table .repo-file-item { + display: contents; +} + +#repo-files-table .repo-file-item:hover > .repo-file-cell { + background: var(--color-hover); +} + +#repo-files-table .repo-file-line, +#repo-files-table .repo-file-cell { + border-top: 1px solid var(--color-light-border); + padding: 6px 10px; +} + +#repo-files-table .repo-file-line:first-child { + border-top: none; +} + +#repo-files-table .repo-file-line { + grid-column: 1 / span 3; + display: flex; + align-items: center; + gap: 0.5em; + padding: 6px 10px; +} + +#repo-files-table .repo-file-last-commit { + background: var(--color-box-header); +} + +#repo-files-table .repo-file-cell.name { + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#repo-files-table .repo-file-cell.message { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--color-text-light-1); +} + +#repo-files-table .repo-file-cell.age { + text-align: right; + white-space: nowrap; + color: var(--color-text-light-1); +} + +@media (max-width: 767.98px) { + #repo-files-table .repo-file-cell.name { + max-width: 150px; + } +} diff --git a/web_src/css/repo/wiki.css b/web_src/css/repo/wiki.css index ba502d32161..ca59dadb9c8 100644 --- a/web_src/css/repo/wiki.css +++ b/web_src/css/repo/wiki.css @@ -59,9 +59,6 @@ } @media (max-width: 767.98px) { - .repository.wiki .clone-panel #repo-clone-url { - width: 160px; - } .repository.wiki .wiki-content-main.with-sidebar, .repository.wiki .wiki-content-sidebar { float: none; diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 7f647b668a6..eece2efaf86 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -429,7 +429,8 @@ export function initRepositoryActionView() { {{ run.commit.pusher.displayName }} - {{ run.commit.branch.name }} + {{ run.commit.branch.name }} + {{ run.commit.branch.name }}
    diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue index 97678b9a13b..e79fc80d8e3 100644 --- a/web_src/js/components/RepoContributors.vue +++ b/web_src/js/components/RepoContributors.vue @@ -1,5 +1,6 @@