Merge branch 'main' into feat-32257-add-comments-unchanged-lines-and-show

This commit is contained in:
Rajesh Jonnalagadda 2024-12-13 15:07:41 +05:30 committed by GitHub
commit 8d6b3c3e09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 1442 additions and 1091 deletions

View File

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

View File

@ -3,3 +3,5 @@ self-hosted-runner:
- actuated-4cpu-8gb
- actuated-4cpu-16gb
- nscloud
- namespace-profile-gitea-release-docker
- namespace-profile-gitea-release-binary

View File

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

View File

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

View File

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

10
go.mod
View File

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

19
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,23 +20,23 @@ func TestMathRender(t *testing.T) {
}{
{
"$a$",
`<p><code class="language-math is-loading">a</code></p>` + nl,
`<p><code class="language-math">a</code></p>` + nl,
},
{
"$ a $",
`<p><code class="language-math is-loading">a</code></p>` + nl,
`<p><code class="language-math">a</code></p>` + nl,
},
{
"$a$ $b$",
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
},
{
`\(a\) \(b\)`,
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
},
{
`$a$.`,
`<p><code class="language-math is-loading">a</code>.</p>` + nl,
`<p><code class="language-math">a</code>.</p>` + nl,
},
{
`.$a$`,
@ -64,27 +64,27 @@ func TestMathRender(t *testing.T) {
},
{
"$a$ ($b$) [$c$] {$d$}",
`<p><code class="language-math is-loading">a</code> (<code class="language-math is-loading">b</code>) [$c$] {$d$}</p>` + nl,
`<p><code class="language-math">a</code> (<code class="language-math">b</code>) [$c$] {$d$}</p>` + nl,
},
{
"$$a$$",
`<code class="chroma language-math display">a</code>` + nl,
`<code class="language-math display">a</code>` + nl,
},
{
"$$a$$ test",
`<p><code class="language-math display is-loading">a</code> test</p>` + nl,
`<p><code class="language-math">a</code> test</p>` + nl,
},
{
"test $$a$$",
`<p>test <code class="language-math display is-loading">a</code></p>` + nl,
`<p>test <code class="language-math">a</code></p>` + nl,
},
{
`foo $x=\$$ bar`,
`<p>foo <code class="language-math is-loading">x=\$</code> bar</p>` + nl,
`<p>foo <code class="language-math">x=\$</code> bar</p>` + nl,
},
{
`$\text{$b$}$`,
`<p><code class="language-math is-loading">\text{$b$}</code></p>` + nl,
`<p><code class="language-math">\text{$b$}</code></p>` + nl,
},
}
@ -110,7 +110,7 @@ func TestMathRenderBlockIndent(t *testing.T) {
\alpha
\]
`,
`<pre class="code-block is-loading"><code class="chroma language-math display">
`<pre class="code-block is-loading"><code class="language-math display">
\alpha
</code></pre>
`,
@ -122,7 +122,7 @@ func TestMathRenderBlockIndent(t *testing.T) {
\alpha
\]
`,
`<pre class="code-block is-loading"><code class="chroma language-math display">
`<pre class="code-block is-loading"><code class="language-math display">
\alpha
</code></pre>
`,
@ -137,7 +137,7 @@ a
d
\]
`,
`<pre class="code-block is-loading"><code class="chroma language-math display">
`<pre class="code-block is-loading"><code class="language-math display">
a
b
c
@ -154,7 +154,7 @@ c
c
\]
`,
`<pre class="code-block is-loading"><code class="chroma language-math display">
`<pre class="code-block is-loading"><code class="language-math display">
a
b
c
@ -165,7 +165,7 @@ c
"indent-0-oneline",
`$$ x $$
foo`,
`<code class="chroma language-math display"> x </code>
`<code class="language-math display"> x </code>
<p>foo</p>
`,
},
@ -173,7 +173,7 @@ foo`,
"indent-3-oneline",
` $$ x $$<SPACE>
foo`,
`<code class="chroma language-math display"> x </code>
`<code class="language-math display"> x </code>
<p>foo</p>
`,
},
@ -188,10 +188,10 @@ foo`,
> \]
`,
`<blockquote>
<pre class="code-block is-loading"><code class="chroma language-math display">
<pre class="code-block is-loading"><code class="language-math display">
a
</code></pre>
<pre class="code-block is-loading"><code class="chroma language-math display">
<pre class="code-block is-loading"><code class="language-math display">
b
</code></pre>
</blockquote>
@ -207,7 +207,7 @@ b
2. b`,
`<ol>
<li>a
<pre class="code-block is-loading"><code class="chroma language-math display">
<pre class="code-block is-loading"><code class="language-math display">
x
</code></pre>
</li>

View File

@ -12,6 +12,17 @@ import (
"github.com/yuin/goldmark/util"
)
// Block render output:
// <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
//
// Keep in mind that there is another "code block" render in "func (r *GlodmarkRender) highlightingRenderer"
// "highlightingRenderer" outputs the math block with extra "chroma" class:
// <pre class="code-block is-loading"><code class="chroma language-math display">...</code></pre>
//
// 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, "", `<pre class="code-block is-loading">`) + `<code class="chroma language-math display">`
code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
_ = r.renderInternal.FormatWithSafeAttrs(w, code)
r.writeLines(w, source, n)
} else {

View File

@ -13,6 +13,9 @@ import (
"github.com/yuin/goldmark/util"
)
// Inline render output:
// <code class="language-math">...</code>
// 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, `<code class="language-math %sis-loading">`, extraClass)
_ = r.renderInternal.FormatWithSafeAttrs(w, `<code class="language-math">`)
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
segment := c.(*ast.Text).Segment
value := util.EscapeHTML(segment.Value(source))

View File

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

View File

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

View File

@ -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 <a href="%s">.git-blame-ignore-revs</a>. Clique <a href="%s">aqui para contornar</a> e ver a vista normal de responsabilização.
blame.ignore_revs.failed=Falhou ao ignorar as revisões em <a href="%s">.git-blame-ignore-revs</a>.
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

94
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
})
}

View File

@ -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 branch>...[<head repo>:]<head branch>
// 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 // [<user>:]<branch> 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

View File

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

View File

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

View File

@ -27,10 +27,10 @@
</div>
</div>
<div class="flex-item-trailing">
{{if .RefLink}}
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}">{{.PrettyRef}}</a>
{{if .IsRefDeleted}}
<span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</span>
{{else}}
<span class="ui label run-list-ref gt-ellipsis">{{.PrettyRef}}</span>
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</a>
{{end}}
<div class="run-list-item-right">
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince .Updated}}</div>

View File

@ -1,15 +1,13 @@
<!-- there is always at least one button (by context/repo.go) -->
{{if $.CloneButtonShowHTTPS}}
<button class="ui small button" id="repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">
HTTPS
<!-- there is always at least one button (guaranteed by context/repo.go) -->
<div class="ui action small input clone-buttons-combo">
{{if $.CloneButtonShowHTTPS}}
<button class="ui small button repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
{{end}}
{{if $.CloneButtonShowSSH}}
<button class="ui small button repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
{{end}}
<input size="10" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
<button class="ui small icon button" data-clipboard-target=".repo-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
{{svg "octicon-copy" 14}}
</button>
{{end}}
{{if $.CloneButtonShowSSH}}
<button class="ui small button" id="repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">
SSH
</button>
{{end}}
<input id="repo-clone-url" size="10" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
<button class="ui small icon button" id="clipboard-btn" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{ctx.Locale.Tr "copy_url"}}">
{{svg "octicon-copy" 14}}
</button>
</div>

View File

@ -0,0 +1,44 @@
<button class="ui green button js-btn-clone-panel">
<span>{{svg "octicon-code" 16}} Code</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</button>
<div class="clone-panel-popup tippy-target">
<div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div>
<div class="clone-panel-tab">
<!-- there is always at least one button (guaranteed by context/repo.go) -->
{{if $.CloneButtonShowHTTPS}}
<button class="item repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
{{end}}
{{if $.CloneButtonShowSSH}}
<button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
{{end}}
</div>
<div class="divider"></div>
<div class="clone-panel-field">
<div class="ui input tiny action">
<input size="30" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
<div class="ui small compact icon button" data-clipboard-target=".js-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
{{svg "octicon-copy" 14}}
</div>
</div>
</div>
{{if not .PageIsWiki}}
<div class="flex-items-block clone-panel-list">
{{range .OpenWithEditorApps}}
<a class="item muted js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
{{end}}
</div>
{{if and (not $.DisableDownloadSourceArchives) $.RefName}}
<div class="divider"></div>
<div class="flex-items-block clone-panel-list">
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_zip"}}</a>
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_tar"}}</a>
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package"}} {{ctx.Locale.Tr "repo.download_bundle"}}</a>
</div>
{{end}}
{{end}}
</div>

View File

@ -1,50 +0,0 @@
<script>
// synchronously set clone button states and urls here to avoid flickering
// on page load. initRepoCloneLink calls this when proto changes.
// this applies the protocol-dependant clone url to all elements with the
// `js-clone-url` and `js-clone-url-vsc` classes.
// TODO: This localStorage setting should be moved to backend user config
// so it's available during rendering, then this inline script can be removed.
(window.updateCloneStates = function() {
const httpsBtn = document.getElementById('repo-clone-https');
const sshBtn = document.getElementById('repo-clone-ssh');
const value = localStorage.getItem('repo-clone-protocol') || 'https';
const isSSH = value === 'ssh' && sshBtn || value !== 'ssh' && !httpsBtn;
if (httpsBtn) {
httpsBtn.textContent = window.origin.split(':')[0].toUpperCase();
httpsBtn.classList.toggle('primary', !isSSH);
httpsBtn.classList.toggle('basic', isSSH);
}
if (sshBtn) {
sshBtn.classList.toggle('primary', isSSH);
sshBtn.classList.toggle('basic', !isSSH);
}
const btn = isSSH ? sshBtn : httpsBtn;
if (!btn) return;
// NOTE: Keep this function in sync with the one in the js folder
function toOriginUrl(urlStr) {
try {
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const {origin, protocol, hostname, port} = window.location;
const url = new URL(urlStr, origin);
url.protocol = protocol;
url.hostname = hostname;
url.port = port || (protocol === 'https:' ? '443' : '80');
return url.toString();
}
} catch {}
return urlStr;
}
const link = toOriginUrl(btn.getAttribute('data-link'));
for (const el of document.getElementsByClassName('js-clone-url')) {
el[el.nodeName === 'INPUT' ? 'value' : 'textContent'] = link;
}
for (const el of document.getElementsByClassName('js-clone-url-editor')) {
el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
}
})();
</script>

View File

@ -1,6 +1,7 @@
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
<div id="repo-contributors-chart"
data-repo-link="{{.RepoLink}}"
data-repo-default-branch-name="{{.Repository.DefaultBranch}}"
data-locale-filter-label="{{ctx.Locale.Tr "repo.contributors.contribution_type.filter_label"}}"
data-locale-contribution-type-commits="{{ctx.Locale.Tr "repo.contributors.contribution_type.commits"}}"
data-locale-contribution-type-additions="{{ctx.Locale.Tr "repo.contributors.contribution_type.additions"}}"

View File

@ -167,8 +167,8 @@
<button class="btn diff-header-popup-btn tw-p-1">{{svg "octicon-kebab-horizontal" 18}}</button>
<div class="tippy-target">
{{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}}
<button class="unescape-button item" data-file-content-elem-id="diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
<button class="escape-button tw-hidden item" data-file-content-elem-id="diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
<button class="unescape-button item" data-unicode-content-selector="#diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
<button class="escape-button tw-hidden item" data-unicode-content-selector="#diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
{{end}}
{{if and (not $file.IsSubmodule) (not $.PageIsWiki)}}
{{if $file.IsDeleted}}

View File

@ -37,9 +37,7 @@
</a>
{{end}}
{{end}}
<div class="clone-panel ui action small input tw-flex-1">
{{template "repo/clone_buttons" .}}
</div>
{{template "repo/clone_buttons" .}}
</div>
</div>
@ -73,7 +71,6 @@ git push -u origin {{.Repository.DefaultBranch}}</code></pre>
{{ctx.Locale.Tr "repo.empty_message"}}
</div>
{{end}}
{{template "repo/clone_script" .}}
</div>
</div>
</div>

View File

@ -102,27 +102,10 @@
{{end}}
</div>
{{/* 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 */}}
<div class="repo-button-row-right {{if not $isTreePathRoot}}tw-flex-grow-0{{end}}">
<div class="repo-button-row-right">
<!-- Only show clone panel in repository home page -->
{{if $isTreePathRoot}}
<div class="clone-panel ui action tiny input">
{{template "repo/clone_buttons" .}}
<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
{{if not $.DisableDownloadSourceArchives}}
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
{{end}}
{{range .OpenWithEditorApps}}
<a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
{{end}}
</div>
</button>
{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
</div>
{{template "repo/clone_panel" .}}
{{end}}
{{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
@ -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}}
</div>

View File

@ -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)}}
<input id="ref_selector" name="ref" type="hidden" value="{{.Reference}}">
<div class="ui dropdown select-branch branch-selector-dropdown ellipsis-items-nowrap {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}}"
@ -29,7 +43,7 @@
{{range .Branches}}
<div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector" title="{{.}}">{{.}}</div>
{{else}}
<div class="item">{{ctx.Locale.Tr "no_results_found"}}</div>
<div class="item disabled">{{ctx.Locale.Tr "no_results_found"}}</div>
{{end}}
</div>
<div id="tag-list" class="scrolling menu reference-list-menu tw-hidden">
@ -39,7 +53,7 @@
{{range .Tags}}
<div class="item" data-id="refs/tags/{{.}}" data-name="tags/{{.}}" data-id-selector="#ref_selector">{{.}}</div>
{{else}}
<div class="item">{{ctx.Locale.Tr "no_results_found"}}</div>
<div class="item disabled">{{ctx.Locale.Tr "no_results_found"}}</div>
{{end}}
</div>
</div>

View File

@ -47,7 +47,8 @@
</div>
<div class="issue-content-right ui segment">
{{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}}
<div class="divider"></div>

View File

@ -1,5 +1,5 @@
<div class="issue-content-right ui segment">
{{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}}

View File

@ -1,5 +1,5 @@
{{if not .LatestCommit}}
<div class="ui active tiny slow centered inline"></div>
{{else}}
{{if .LatestCommitUser}}
{{ctx.AvatarUtils.Avatar .LatestCommitUser 24 "tw-mr-1"}}

View File

@ -1,73 +1,57 @@
<table id="repo-files-table" class="ui single line fixed table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
<thead>
<tr class="commit-list">
<th class="tw-overflow-hidden" colspan="2">
<div class="tw-flex">
<div class="latest-commit">
{{template "repo/latest_commit" .}}
</div>
</div>
</th>
<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}{{end}}</th>
</tr>
</thead>
<tbody>
{{if .HasParentPath}}
<tr class="has-parent">
<td colspan="3">{{svg "octicon-reply"}}<a class="muted" href="{{.BranchLink}}{{if .ParentPath}}{{PathEscapeSegments .ParentPath}}{{end}}">..</a></td>
</tr>
{{end}}
{{range $item := .Files}}
{{/* use grid layout, still use the old ID because there are many other CSS styles depending on this ID */}}
<div id="repo-files-table" {{if .HasFilesWithoutLatestCommit}}hx-indicator="#repo-files-table .repo-file-cell.message" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
<div class="repo-file-line repo-file-last-commit">
<div class="latest-commit">{{template "repo/latest_commit" .}}</div>
<div>{{if and .LatestCommit .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}</div>
</div>
{{if .HasParentPath}}
<div class="repo-file-line">
{{svg "octicon-reply"}} <a class="muted" href="{{.BranchLink}}{{if .ParentPath}}{{PathEscapeSegments .ParentPath}}{{end}}">..</a>
</div>
{{end}}
{{range $item := .Files}}
<div class="repo-file-item">
{{$entry := $item.Entry}}
{{$commit := $item.Commit}}
{{$subModuleFile := $item.SubModuleFile}}
<tr data-entryname="{{$entry.Name}}" data-ready="{{if $commit}}true{{else}}false{{end}}" class="{{if not $commit}}not{{end}}ready entry">
<td class="name four wide">
<span class="truncate">
{{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}}
<a class="muted" href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a href="{{$refURL}}/commit/{{PathEscape $subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a>
<div class="repo-file-cell name {{if not $commit}}notready{{end}}">
{{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}}
<a class="muted" href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a href="{{$refURL}}/commit/{{PathEscape $subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a>
{{else}}
{{$entry.Name}}<span class="at">@</span>{{ShortSha $subModuleFile.RefID}}
{{end}}
{{else}}
{{if $entry.IsDir}}
{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
{{svg "octicon-file-directory-fill"}}
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}">
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
{{if eq $subJumpablePathFieldLast 0}}
{{$subJumpablePathName}}
{{else}}
{{$entry.Name}}<span class="at">@</span>{{ShortSha $subModuleFile.RefID}}
{{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}}
<span class="text light-2">{{StringUtils.Join $subJumpablePathPrefixes "/"}}</span>/{{index $subJumpablePathFields $subJumpablePathFieldLast}}
{{end}}
{{else}}
{{if $entry.IsDir}}
{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
{{svg "octicon-file-directory-fill"}}
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}">
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
{{if eq $subJumpablePathFieldLast 0}}
{{$subJumpablePathName}}
{{else}}
{{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}}
<span class="text light-2">{{StringUtils.Join $subJumpablePathPrefixes "/"}}</span>/{{index $subJumpablePathFields $subJumpablePathFieldLast}}
{{end}}
</a>
{{else}}
{{svg (printf "octicon-%s" (EntryIcon $entry))}}
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
{{end}}
{{end}}
</span>
</td>
<td class="message nine wide">
<span class="truncate">
{{if $commit}}
{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}}
{{else}}
<div class="ui active tiny slow centered inline"></div>
{{end}}
</span>
</td>
<td class="text right age three wide">{{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}}
{{template "repo/view_file" .}}
{{end}}
</a>
{{else}}
{{svg (printf "octicon-%s" (EntryIcon $entry))}}
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
{{end}}
{{end}}
</div>
<div class="repo-file-cell message loading-icon-2px">
{{if $commit}}
{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}}
{{else}}
{{/* will be loaded again by LastCommitLoaderURL */}}
{{end}}
</div>
<div class="repo-file-cell age">{{if $commit}}{{DateUtils.TimeSince $commit.Committer.When}}{{end}}</div>
</div>
{{end}}
</div>

View File

@ -15,10 +15,7 @@
</div>
</div>
<div class="ui eight wide column text right">
<div class="clone-panel ui action small input">
{{template "repo/clone_buttons" .}}
{{template "repo/clone_script" .}}
</div>
{{template "repo/clone_panel" .}}
</div>
</div>
<h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2>

View File

@ -4,7 +4,7 @@
{{$title := .title}}
<div class="ui container">
<div class="repo-button-row">
<div class="tw-flex tw-items-center">
<div class="flex-text-block tw-flex-1">
<div class="ui floating filter dropdown" data-no-results="{{ctx.Locale.Tr "no_results_found"}}">
<div class="ui basic small button">
<span class="text">
@ -28,10 +28,7 @@
</div>
</div>
</div>
<div class="clone-panel ui action small input">
{{template "repo/clone_buttons" .}}
{{template "repo/clone_script" .}}
</div>
{{template "repo/clone_panel" .}}
</div>
<div class="ui dividing header">
<div class="flex-text-block tw-flex-wrap tw-justify-end">
@ -45,15 +42,15 @@
</div>
</div>
</div>
<div class="flex-text-block tw-flex-wrap tw-justify-end">
<div class="repo-button-row">
{{if .EscapeStatus.Escaped}}
<a class="ui small button unescape-button tw-m-0 tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui small button escape-button tw-m-0">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
<a class="ui small button unescape-button tw-hidden" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui small button escape-button" data-unicode-content-selector=".wiki-content-parts">{{ctx.Locale.Tr "repo.escape_control_characters"}}</a>
{{end}}
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{ctx.Locale.Tr "repo.wiki.edit_page_button"}}</a>
<a class="ui small primary button" href="{{.RepoLink}}/wiki?action=_new">{{ctx.Locale.Tr "repo.wiki.new_page_button"}}</a>
<a class="ui small red button tw-m-0 delete-button" href="" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete" data-id="{{.PageURL}}">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
<a class="ui small red button delete-button" href="" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete" data-id="{{.PageURL}}">{{ctx.Locale.Tr "repo.wiki.delete_page_button"}}</a>
{{end}}
</div>
</div>

View File

@ -99,7 +99,7 @@
<span class="gt-ellipsis">{{.Project.Title}}</span>
</a>
{{end}}
{{if .Ref}}
{{if .Ref}}{{/* TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl" */}}
<a class="ref flex-text-inline tw-max-w-[300px]" {{if $.RepoLink}}href="{{index $.IssueRefURLs .ID}}"{{else}}href="{{.Repo.Link}}{{index $.IssueRefURLs .ID}}"{{end}}>
{{svg "octicon-git-branch" 14}}
<span class="gt-ellipsis">{{index $.IssueRefEndNames .ID}}</span>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -429,7 +429,8 @@ export function initRepositoryActionView() {
<a class="muted" :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a>
</template>
<span class="ui label tw-max-w-full" v-if="run.commit.shortSHA">
<a class="gt-ellipsis" :href="run.commit.branch.link">{{ run.commit.branch.name }}</a>
<span v-if="run.commit.branch.isDeleted" class="gt-ellipsis tw-line-through" :data-tooltip-content="run.commit.branch.name">{{ run.commit.branch.name }}</span>
<a v-else class="gt-ellipsis" :href="run.commit.branch.link" :data-tooltip-content="run.commit.branch.name">{{ run.commit.branch.name }}</a>
</span>
</div>
</div>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import {SvgIcon} from '../svg.ts';
import dayjs from 'dayjs';
import {
Chart,
Title,
@ -26,6 +27,7 @@ import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import type {Entries} from 'type-fest';
import {pathEscapeSegments} from '../utils/url.ts';
const customEventListener: Plugin = {
id: 'customEventListener',
@ -65,6 +67,10 @@ export default {
type: String,
required: true,
},
repoDefaultBranchName: {
type: String,
required: true,
},
},
data: () => ({
isLoading: false,
@ -100,6 +106,15 @@ export default {
.slice(0, 100);
},
getContributorSearchQuery(contributorEmail: string) {
const min = dayjs(this.xAxisMin).format('YYYY-MM-DD');
const max = dayjs(this.xAxisMax).format('YYYY-MM-DD');
const params = new URLSearchParams({
'q': `after:${min}, before:${max}, author:${contributorEmail}`,
});
return `${this.repoLink}/commits/branch/${pathEscapeSegments(this.repoDefaultBranchName)}/search?${params.toString()}`;
},
async fetchGraphData() {
this.isLoading = true;
try {
@ -167,7 +182,7 @@ export default {
// for details.
user.max_contribution_type += 1;
filteredData[key] = {...user, weeks: filteredWeeks};
filteredData[key] = {...user, weeks: filteredWeeks, email: key};
}
return filteredData;
@ -215,7 +230,7 @@ export default {
};
},
updateOtherCharts({chart}: {chart: Chart}, reset?: boolean = false) {
updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) {
const minVal = chart.options.scales.x.min;
const maxVal = chart.options.scales.x.max;
if (reset) {
@ -381,7 +396,7 @@ export default {
<div class="ui top attached header tw-flex tw-flex-1">
<b class="ui right">#{{ index + 1 }}</b>
<a :href="contributor.home_link">
<img class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link">
<img class="ui avatar tw-align-middle" height="40" width="40" :src="contributor.avatar_link" alt="">
</a>
<div class="tw-ml-2">
<a v-if="contributor.home_link !== ''" :href="contributor.home_link"><h4>{{ contributor.name }}</h4></a>
@ -389,7 +404,11 @@ export default {
{{ contributor.name }}
</h4>
<p class="tw-text-12 tw-flex tw-gap-1">
<strong v-if="contributor.total_commits">{{ contributor.total_commits.toLocaleString() }} {{ locale.contributionType.commits }}</strong>
<strong v-if="contributor.total_commits">
<a class="silenced" :href="getContributorSearchQuery(contributor.email)">
{{ contributor.total_commits.toLocaleString() }} {{ locale.contributionType.commits }}
</a>
</strong>
<strong v-if="contributor.total_additions" class="text green">{{ contributor.total_additions.toLocaleString() }}++ </strong>
<strong v-if="contributor.total_deletions" class="text red">
{{ contributor.total_deletions.toLocaleString() }}--</strong>

View File

@ -178,6 +178,7 @@ export function initTextareaEvents(textarea, dropzoneEl) {
});
textarea.addEventListener('drop', (e) => {
if (!e.dataTransfer.files.length) return;
if (!dropzoneEl) return;
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, e.dataTransfer.files, e);
});
dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => {

View File

@ -8,6 +8,7 @@ export async function initRepoContributors() {
try {
const View = createApp(RepoContributors, {
repoLink: el.getAttribute('data-repo-link'),
repoDefaultBranchName: el.getAttribute('data-repo-default-branch-name'),
locale: {
filterLabel: el.getAttribute('data-locale-filter-label'),
contributionType: {

View File

@ -1,10 +1,11 @@
import $ from 'jquery';
import {hideElem, queryElems, showElem} from '../utils/dom.ts';
import {queryElems} from '../utils/dom.ts';
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {sleep} from '../utils.ts';
import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';
import {createApp} from 'vue';
import {toOriginUrl} from '../utils/url.ts';
import {createTippy} from '../modules/tippy.ts';
async function onDownloadArchive(e) {
e.preventDefault();
@ -41,54 +42,69 @@ export function initRepoActivityTopAuthorsChart() {
}
}
export function initRepoCloneLink() {
const $repoCloneSsh = $('#repo-clone-ssh');
const $repoCloneHttps = $('#repo-clone-https');
const $inputLink = $('#repo-clone-url');
function initCloneSchemeUrlSelection(parent: Element) {
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
if ((!$repoCloneSsh.length && !$repoCloneHttps.length) || !$inputLink.length) {
return;
}
const tabSsh = parent.querySelector('.repo-clone-ssh');
const tabHttps = parent.querySelector('.repo-clone-https');
const updateClonePanelUi = function() {
const scheme = localStorage.getItem('repo-clone-protocol') || 'https';
const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps;
if (tabHttps) {
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
tabHttps.classList.toggle('active', !isSSH);
}
if (tabSsh) {
tabSsh.classList.toggle('active', isSSH);
}
$repoCloneSsh.on('click', () => {
localStorage.setItem('repo-clone-protocol', 'ssh');
window.updateCloneStates();
});
$repoCloneHttps.on('click', () => {
localStorage.setItem('repo-clone-protocol', 'https');
window.updateCloneStates();
});
const tab = isSSH ? tabSsh : tabHttps;
if (!tab) return;
const link = toOriginUrl(tab.getAttribute('data-link'));
$inputLink.on('focus', () => {
$inputLink.trigger('select');
});
}
export function initRepoCommonBranchOrTagDropdown(selector: string) {
$(selector).each(function () {
const $dropdown = $(this);
$dropdown.find('.reference.column').on('click', function () {
hideElem($dropdown.find('.scrolling.reference-list-menu'));
showElem($($(this).data('target')));
return false;
});
});
}
export function initRepoCommonFilterSearchDropdown(selector: string) {
const $dropdown = $(selector);
if (!$dropdown.length) return;
$dropdown.dropdown({
fullTextSearch: 'exact',
selectOnKeydown: false,
onChange(_text, _value, $choice) {
if ($choice[0].getAttribute('data-url')) {
window.location.href = $choice[0].getAttribute('data-url');
for (const el of document.querySelectorAll('.js-clone-url')) {
if (el.nodeName === 'INPUT') {
(el as HTMLInputElement).value = link;
} else {
el.textContent = link;
}
},
message: {noResults: $dropdown[0].getAttribute('data-no-results')},
}
for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) {
el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
}
};
updateClonePanelUi();
// tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server
tabSsh?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'ssh');
updateClonePanelUi();
});
tabHttps?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'https');
updateClonePanelUi();
});
elCloneUrlInput.addEventListener('focus', () => {
elCloneUrlInput.select();
});
}
function initClonePanelButton(btn: HTMLButtonElement) {
const elPanel = btn.nextElementSibling;
// "init" must be before the "createTippy" otherwise the "tippy-target" will be removed from the document
initCloneSchemeUrlSelection(elPanel);
createTippy(btn, {
content: elPanel,
trigger: 'click',
placement: 'bottom-end',
interactive: true,
hideOnClick: true,
});
}
export function initRepoCloneButtons() {
queryElems(document, '.js-btn-clone-panel', initClonePanelButton);
queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection);
}
export async function updateIssuesMeta(url, action, issue_ids, id) {

View File

@ -4,6 +4,7 @@ import {queryElems, toggleElem} from '../utils/dom.ts';
import {initIssueSidebarComboList} from './repo-issue-sidebar-combolist.ts';
function initBranchSelector() {
// TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
if (!elSelectBranch) return;

View File

@ -8,9 +8,7 @@ import {
} from './repo-issue.ts';
import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
import {
initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
} from './repo-common.ts';
import {initRepoCloneButtons} from './repo-common.ts';
import {initCitationFileCopyContent} from './citation.ts';
import {initCompLabelEdit} from './comp/LabelEdit.ts';
import {initRepoDiffConversationNav} from './repo-diff.ts';
@ -36,6 +34,33 @@ export function initBranchSelectorTabs() {
});
}
function initRepoCommonBranchOrTagDropdown(selector: string) {
$(selector).each(function () {
const $dropdown = $(this);
$dropdown.find('.reference.column').on('click', function () {
hideElem($dropdown.find('.scrolling.reference-list-menu'));
showElem($($(this).data('target')));
return false;
});
});
}
function initRepoCommonFilterSearchDropdown(selector: string) {
const $dropdown = $(selector);
if (!$dropdown.length) return;
$dropdown.dropdown({
fullTextSearch: 'exact',
selectOnKeydown: false,
onChange(_text, _value, $choice) {
if ($choice[0].getAttribute('data-url')) {
window.location.href = $choice[0].getAttribute('data-url');
}
},
message: {noResults: $dropdown[0].getAttribute('data-no-results')},
});
}
export function initRepository() {
if (!$('.page-content.repository').length) return;
@ -54,7 +79,7 @@ export function initRepository() {
initRepoCommonFilterSearchDropdown('.choose.branch .dropdown');
}
initRepoCloneLink();
initRepoCloneButtons();
initCitationFileCopyContent();
initRepoSettings();

View File

@ -1,27 +1,28 @@
import {addDelegatedEventListener, hideElem, queryElemSiblings, showElem, toggleElem} from '../utils/dom.ts';
export function initUnicodeEscapeButton() {
// buttons might appear on these pages: file view (code, rendered markdown), diff (commit, pr conversation, pr diff), blame, wiki
addDelegatedEventListener(document, 'click', '.escape-button, .unescape-button, .toggle-escape-button', (btn, e) => {
e.preventDefault();
const fileContentElemId = btn.getAttribute('data-file-content-elem-id');
const fileContent = fileContentElemId ?
document.querySelector(`#${fileContentElemId}`) :
const unicodeContentSelector = btn.getAttribute('data-unicode-content-selector');
const container = unicodeContentSelector ?
document.querySelector(unicodeContentSelector) :
btn.closest('.file-content, .non-diff-file-content');
const fileView = fileContent?.querySelectorAll('.file-code, .file-view');
const fileView = container.querySelector('.file-code, .file-view') ?? container;
if (btn.matches('.escape-button')) {
for (const el of fileView) el.classList.add('unicode-escaped');
fileView.classList.add('unicode-escaped');
hideElem(btn);
showElem(queryElemSiblings(btn, '.unescape-button'));
} else if (btn.matches('.unescape-button')) {
for (const el of fileView) el.classList.remove('unicode-escaped');
fileView.classList.remove('unicode-escaped');
hideElem(btn);
showElem(queryElemSiblings(btn, '.escape-button'));
} else if (btn.matches('.toggle-escape-button')) {
const isEscaped = fileView[0]?.classList.contains('unicode-escaped');
for (const el of fileView) el.classList.toggle('unicode-escaped', !isEscaped);
toggleElem(fileContent.querySelectorAll('.unescape-button'), !isEscaped);
toggleElem(fileContent.querySelectorAll('.escape-button'), isEscaped);
const isEscaped = fileView.classList.contains('unicode-escaped');
fileView.classList.toggle('unicode-escaped', !isEscaped);
toggleElem(container.querySelectorAll('.unescape-button'), !isEscaped);
toggleElem(container.querySelectorAll('.escape-button'), isEscaped);
}
});
}

View File

@ -1,9 +1,8 @@
import {displayError} from './common.ts';
function targetElement(el: Element) {
// The target element is either the current element if it has the
// `is-loading` class or the pre that contains it
return el.classList.contains('is-loading') ? el : el.closest('pre');
// The target element is either the parent "code block with loading indicator", or itself
return el.closest('.code-block.is-loading') ?? el;
}
export async function renderMath(): Promise<void> {

View File

@ -1,4 +1,4 @@
import {pathEscapeSegments, isUrl} from './url.ts';
import {pathEscapeSegments, isUrl, toOriginUrl} from './url.ts';
test('pathEscapeSegments', () => {
expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c');
@ -11,3 +11,19 @@ test('isUrl', () => {
expect(isUrl('https://example.com/index.html')).toEqual(true);
expect(isUrl('/index.html')).toEqual(false);
});
test('toOriginUrl', () => {
const oldLocation = String(window.location);
for (const origin of ['https://example.com', 'https://example.com:3000']) {
window.location.assign(`${origin}/`);
expect(toOriginUrl('/')).toEqual(`${origin}/`);
expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`);
}
window.location.assign(oldLocation);
});

View File

@ -13,3 +13,19 @@ export function isUrl(url: string): boolean {
return false;
}
}
// Convert an absolute or relative URL to an absolute URL with the current origin. It only
// processes absolute HTTP/HTTPS URLs or relative URLs like '/xxx' or '//host/xxx'.
export function toOriginUrl(urlStr: string) {
try {
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const {origin, protocol, hostname, port} = window.location;
const url = new URL(urlStr, origin);
url.protocol = protocol;
url.hostname = hostname;
url.port = port || (protocol === 'https:' ? '443' : '80');
return url.toString();
}
} catch {}
return urlStr;
}

View File

@ -1,17 +0,0 @@
import {toOriginUrl} from './origin-url.ts';
test('toOriginUrl', () => {
const oldLocation = String(window.location);
for (const origin of ['https://example.com', 'https://example.com:3000']) {
window.location.assign(`${origin}/`);
expect(toOriginUrl('/')).toEqual(`${origin}/`);
expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`);
expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`);
expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`);
}
window.location.assign(oldLocation);
});

View File

@ -1,19 +1,4 @@
// Convert an absolute or relative URL to an absolute URL with the current origin. It only
// processes absolute HTTP/HTTPS URLs or relative URLs like '/xxx' or '//host/xxx'.
// NOTE: Keep this function in sync with clone_script.tmpl
export function toOriginUrl(urlStr: string) {
try {
if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) {
const {origin, protocol, hostname, port} = window.location;
const url = new URL(urlStr, origin);
url.protocol = protocol;
url.hostname = hostname;
url.port = port || (protocol === 'https:' ? '443' : '80');
return url.toString();
}
} catch {}
return urlStr;
}
import {toOriginUrl} from '../utils/url.ts';
window.customElements.define('origin-url', class extends HTMLElement {
connectedCallback() {