Merge branch 'main' into lunny/project_workflow

pull/30205/head
Lunny Xiao 2025-11-03 12:23:08 -08:00 committed by GitHub
commit c6bd967966
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 218 additions and 198 deletions

View File

@ -74,6 +74,9 @@ cpu.out
/VERSION
/.air
/.go-licenses
/Dockerfile
/Dockerfile.rootless
/.venv
# Files and folders that were previously generated
/public/assets/img/webpack

View File

@ -11,25 +11,23 @@ jobs:
files-changed:
uses: ./.github/workflows/files-changed.yml
regular:
container:
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
- name: Build regular container image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: gitea/gitea:linux-amd64
rootless:
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
- name: Build rootless container image
uses: docker/build-push-action@v5
with:
context: .
push: false
file: Dockerfile.rootless
tags: gitea/gitea:linux-amd64

View File

@ -56,7 +56,7 @@ jobs:
- name: upload binaries to s3
run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
nightly-docker-rootful:
nightly-container:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
@ -65,10 +65,6 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Get cleaned branch name
@ -76,6 +72,29 @@ jobs:
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- uses: docker/metadata-action@v5
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
tags: |
type=raw,value=${{ steps.clean_name.outputs.branch }}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- uses: docker/metadata-action@v5
id: meta_rootless
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
suffix=-rootless
tags: |
type=raw,value=${{ steps.clean_name.outputs.branch }}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@ -87,57 +106,20 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules
run: make vendor
- name: build rootful docker image
- name: build regular docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: |-
gitea/gitea:${{ steps.clean_name.outputs.branch }}
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
nightly-docker-rootless:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v5
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules
run: make vendor
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: build rootless docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: |-
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
tags: ${{ steps.meta_rootless.outputs.tags }}
annotations: ${{ steps.meta_rootless.outputs.annotations }}

View File

@ -66,7 +66,7 @@ jobs:
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
container:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
@ -88,38 +88,10 @@ jobs:
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker-rootless:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v5
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- uses: docker/metadata-action@v5
id: meta
id: meta_rootless
with:
images: |-
gitea/gitea
@ -131,6 +103,8 @@ jobs:
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@ -142,12 +116,20 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image
- name: build regular container image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: build rootless container image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta_rootless.outputs.tags }}
annotations: ${{ steps.meta_rootless.outputs.annotations }}

View File

@ -70,7 +70,7 @@ jobs:
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
container:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
@ -96,36 +96,10 @@ jobs:
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker-rootless:
runs-on: namespace-profile-gitea-release-docker
steps:
- uses: actions/checkout@v5
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- uses: docker/metadata-action@v5
id: meta
id: meta_rootless
with:
images: |-
gitea/gitea
@ -142,6 +116,8 @@ jobs:
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@ -153,12 +129,20 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image
- name: build regular container image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: build rootless container image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta_rootless.outputs.tags }}
annotations: ${{ steps.meta_rootless.outputs.annotations }}

3
.gitignore vendored
View File

@ -124,3 +124,6 @@ prime/
/AGENT.md
/CLAUDE.md
/llms.txt
# Ignore worktrees when working on multiple branches
.worktrees/

View File

@ -1,8 +1,8 @@
# syntax=docker/dockerfile:1
# Build stage
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct}
ARG GOPROXY=direct
ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify"
@ -14,22 +14,24 @@ RUN apk --no-cache add \
build-base \
git \
nodejs \
npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
pnpm
# Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
# Use COPY but not "mount" because some directories like "node_modules" contain platform-depended contents and these directories need to be ignored.
# ".git" directory will be mounted later separately for getting version data.
# TODO: in the future, maybe we can pre-build the frontend assets on one platform and share them for different platforms, the benefit is that it won't be affected by webpack plugin compatibility problems, then the working directory can be fully mounted and the COPY is not needed.
COPY --exclude=.git/ . .
# Checkout version if set
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
&& make clean-all build
# Build gitea, .git mount is required for version data
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target="/root/.cache/go-build" \
--mount=type=cache,target=/root/.local/share/pnpm/store \
--mount=type=bind,source=".git/",target=".git/" \
make
# Copy local files
COPY docker/root /tmp/local
# Set permissions
# Set permissions for builds that made under windows which strips the executable bit from file
RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/usr/local/bin/* \
/tmp/local/etc/s6/gitea/* \
@ -37,8 +39,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea
FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io"
FROM docker.io/library/alpine:3.22 AS gitea
EXPOSE 22 3000
@ -53,8 +54,7 @@ RUN apk --no-cache add \
s6 \
sqlite \
su-exec \
gnupg \
&& rm -rf /var/cache/apk/*
gnupg
RUN addgroup \
-S -g 1000 \
@ -68,6 +68,9 @@ RUN addgroup \
git && \
echo "git:*" | chpasswd -e
COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
ENV USER=git
ENV GITEA_CUSTOM=/data/gitea
@ -75,6 +78,3 @@ VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/usr/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea

View File

@ -1,40 +1,39 @@
# syntax=docker/dockerfile:1
# Build stage
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct}
ARG GOPROXY=direct
ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS="bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS
#Build deps
# Build deps
RUN apk --no-cache add \
build-base \
git \
nodejs \
npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
pnpm
# Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea
# See the comments in Dockerfile
COPY --exclude=.git/ . .
# Checkout version if set
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
&& make clean-all build
# Build gitea, .git mount is required for version data
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target="/root/.cache/go-build" \
--mount=type=cache,target=/root/.local/share/pnpm/store \
--mount=type=bind,source=".git/",target=".git/" \
make
# Copy local files
COPY docker/rootless /tmp/local
# Set permissions
# Set permissions for builds that made under windows which strips the executable bit from file
RUN chmod 755 /tmp/local/usr/local/bin/* \
/go/src/code.gitea.io/gitea/gitea
FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io"
FROM docker.io/library/alpine:3.22 AS gitea-rootless
EXPOSE 2222 3000
@ -46,8 +45,7 @@ RUN apk --no-cache add \
git \
curl \
gnupg \
openssh-keygen \
&& rm -rf /var/cache/apk/*
openssh-keygen
RUN addgroup \
-S -g 1000 \

View File

@ -18,7 +18,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/json"
@ -207,7 +207,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
username := repoPathFields[0]
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
if !repo.IsValidSSHAccessRepoName(reponame) {
if !repo_model.IsValidSSHAccessRepoName(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
}
@ -253,10 +253,12 @@ func runServ(ctx context.Context, c *cli.Command) error {
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
}
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(results.OwnerName + "/" + results.RepoName + ".git")
// because the original repoPath maybe redirected, we need to use the returned actual repository information
if results.IsWiki {
repoPath = repo_model.RelativeWikiPath(results.OwnerName, results.RepoName)
} else {
repoPath = repo_model.RelativePath(results.OwnerName, results.RepoName)
}
// LFS SSH protocol
if verb == git.CmdVerbLfsTransfer {

View File

@ -3914,6 +3914,14 @@ variables.update.success=変数を更新しました。
logs.always_auto_scroll=常にログを自動スクロール
logs.always_expand_running=常に実行中のログを展開
general=一般
general.enable_actions=Actionsの有効化
general.collaborative_owners_management=協力オーナーの管理
general.collaborative_owners_management_help=協力オーナーとは、このリポジトリのActionやワークフローに、自身のプライベートリポジトリからアクセスできるユーザーまたは組織のことです。
general.add_collaborative_owner=協力オーナーを追加
general.collaborative_owner_not_exist=協力オーナーが存在しません。
general.remove_collaborative_owner=協力オーナーの削除
general.remove_collaborative_owner_desc=協力オーナーを削除すると、そのオーナーのリポジトリはこのリポジトリのActionにアクセスできなくなります。 続行しますか?
[projects]
deleted.display_name=削除されたプロジェクト

View File

@ -128,7 +128,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {
return fmt.Errorf("failed to issue MAIL command: %w", err)
}
} else {
if err = client.Mail(from); err != nil {
if err = client.Mail(fmt.Sprintf("<%s>", from)); err != nil {
return fmt.Errorf("failed to issue MAIL command: %w", err)
}
}

View File

@ -37,7 +37,7 @@ export default {
'./{build,models,modules,routers,services}/**/*.go',
'./templates/**/*.tmpl',
'./web_src/js/**/*.{ts,js,vue}',
].filter(Boolean),
].filter(Boolean as unknown as <T>(x: T | boolean) => x is T),
blocklist: [
// classes that don't work without CSS variables from "@tailwind base" which we don't use
'transform', 'shadow', 'ring', 'blur', 'grayscale', 'invert', '!invert', 'filter', '!filter',

View File

@ -16,7 +16,7 @@
{{else if eq .status "blocked"}}
{{svg "octicon-blocked" $size (printf "text yellow %s" $className)}}
{{else if eq .status "running"}}
{{svg "gitea-running" $size (printf "text yellow circular-spin %s" $className)}}
{{svg "gitea-running" $size (printf "text yellow rotate-clockwise %s" $className)}}
{{else}}{{/*failure, unknown*/}}
{{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}}
{{end}}

View File

@ -102,7 +102,7 @@
{{template "repo/issue/view_content/update_branch_by_merge" $}}
{{else if .Issue.PullRequest.IsChecking}}
<div class="item">
{{svg "octicon-sync" 16 "circular-spin"}}
{{svg "gitea-running" 16 "rotate-clockwise"}}
{{ctx.Locale.Tr "repo.pulls.is_checking"}}
</div>
{{else if .Issue.PullRequest.IsAncestor}}

View File

@ -33,15 +33,15 @@ export async function login_user(browser: Browser, workerInfo: WorkerInfo, user:
}
export async function load_logged_in_context(browser: Browser, workerInfo: WorkerInfo, user: string) {
let context;
try {
context = await browser.newContext({storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`});
return await browser.newContext({storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`});
} catch (err) {
if (err.code === 'ENOENT') {
throw new Error(`Could not find state for '${user}'. Did you call login_user(browser, workerInfo, '${user}') in test.beforeAll()?`);
} else {
throw err;
}
}
return context;
}
export async function save_visual(page: Page) {

View File

@ -11,7 +11,9 @@ import (
"strings"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests"
@ -71,3 +73,46 @@ func Test_RepoWikiPages(t *testing.T) {
assert.Equal(t, expectedPagePaths[i], pagePath)
})
}
func Test_WikiClone(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
username := "user2"
reponame := "repo1"
wikiPath := username + "/" + reponame + ".wiki.git"
keyname := "my-testing-key"
baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
u.Path = wikiPath
t.Run("Clone HTTP", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
dstLocalPath := t.TempDir()
assert.NoError(t, git.Clone(t.Context(), u.String(), dstLocalPath, git.CloneRepoOptions{}))
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
assert.NoError(t, err)
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
})
t.Run("Clone SSH", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
dstLocalPath := t.TempDir()
sshURL := createSSHUrl(wikiPath, u)
withKeyFile(t, keyname, func(keyFile string) {
var keyID int64
t.Run("CreateUserKey", doAPICreateUserKey(baseAPITestContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
keyID = key.ID
}))
assert.NotZero(t, keyID)
// Setup clone folder
assert.NoError(t, git.Clone(t.Context(), sshURL.String(), dstLocalPath, git.CloneRepoOptions{}))
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
assert.NoError(t, err)
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
})
})
})
}

View File

@ -47,6 +47,10 @@ function processAssetsSvgFiles(pattern: string, opts: Opts = {}) {
return glob(pattern).map((path) => processAssetsSvgFile(path, opts));
}
function lowercaseKeys(obj: Record<string, any>) {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]));
}
async function processMaterialFileIcons() {
const paths = glob('node_modules/material-icon-theme/icons/*.svg');
const svgSymbols: Record<string, string> = {};
@ -76,18 +80,30 @@ async function processMaterialFileIcons() {
// * https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
// * https://github.com/microsoft/vscode/tree/1.98.0/extensions
delete iconRules.iconDefinitions;
for (const [k, v] of Object.entries(iconRules.fileNames)) iconRules.fileNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.folderNames)) iconRules.folderNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v;
if (iconRules.fileNames) {
iconRules.fileNames = lowercaseKeys(iconRules.fileNames);
}
if (iconRules.folderNames) {
iconRules.folderNames = lowercaseKeys(iconRules.folderNames);
}
if (iconRules.fileExtensions) {
iconRules.fileExtensions = lowercaseKeys(iconRules.fileExtensions);
}
// Use VSCode's "Language ID" mapping from its extensions
for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) {
for (const [langId, names] of Object.entries(langIdExtMap)) {
for (const name of names) {
const nameLower = name.toLowerCase();
if (nameLower[0] === '.') {
iconRules.fileExtensions[nameLower.substring(1)] ??= langId;
if (iconRules.fileExtensions) {
iconRules.fileExtensions[nameLower.substring(1)] ??= langId;
}
} else {
iconRules.fileNames[nameLower] ??= langId;
if (iconRules.fileNames) {
iconRules.fileNames[nameLower] ??= langId;
}
}
}
}

View File

@ -40,6 +40,7 @@
"strictBindCallApply": true,
"strictBuiltinIteratorReturn": true,
"strictFunctionTypes": true,
"strictNullChecks": false,
"stripInternal": true,
"verbatimModuleSyntax": true,
"types": [

View File

@ -116,14 +116,12 @@ code.language-math.is-loading::after {
animation-timing-function: ease-in-out;
}
/* FIXME: `octicon-sync` is counterclockwise, so this animation is also counterclockwise, it looks somewhat strange.
Ideally in the future we should use a better image for clockwise animation. */
.circular-spin {
animation: circular-spin-keyframes 1s linear infinite;
.rotate-clockwise {
animation: rotate-clockwise-keyframes 1s linear infinite;
}
@keyframes circular-spin-keyframes {
@keyframes rotate-clockwise-keyframes {
100% {
transform: rotate(-360deg);
transform: rotate(360deg);
}
}

View File

@ -24,7 +24,7 @@ withDefaults(defineProps<{
<SvgIcon name="octicon-stop" class="text grey" :size="size" :class="className" v-else-if="status === 'cancelled'"/>
<SvgIcon name="octicon-circle" class="text grey" :size="size" :class="className" v-else-if="status === 'waiting'"/>
<SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class="className" v-else-if="status === 'blocked'"/>
<SvgIcon name="gitea-running" class="text yellow" :size="size" :class="'circular-spin ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="gitea-running" class="text yellow" :size="size" :class="'rotate-clockwise ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/><!-- failure, unknown -->
</span>
</template>

View File

@ -608,7 +608,7 @@ export default defineComponent({
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
currentJobStepsStates[i].cursor === null means the log is loaded for the first time
-->
<SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
<SvgIcon v-else :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" :class="['tw-mr-2', !isExpandable(jobStep.status) && 'tw-invisible']"/>
<ActionRunStatus :status="jobStep.status" class="tw-mr-2"/>

View File

@ -150,7 +150,7 @@ const options: ChartOptions<'line'> = {
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading">
<SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">

View File

@ -381,7 +381,7 @@ export default defineComponent({
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading">
<SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">

View File

@ -128,7 +128,7 @@ const options: ChartOptions<'bar'> = {
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading">
<SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">

View File

@ -62,7 +62,7 @@ const onItemClick = (e: MouseEvent) => {
@click.stop="onItemClick"
>
<div v-if="item.entryMode === 'tree'" class="item-toggle">
<SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/>
<SvgIcon v-if="isLoading" name="gitea-running" class="rotate-clockwise"/>
<SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop.prevent="doLoadChildren"/>
</div>
<div class="item-content">

View File

@ -261,7 +261,7 @@ export function initRepoPullRequestReview() {
if (commentDiv) {
// get the name of the parent id
const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
if (groupID && groupID.startsWith('code-comments-')) {
if (groupID?.startsWith('code-comments-')) {
const id = groupID.slice(14);
const ancestorDiffBox = commentDiv.closest<HTMLElement>('.diff-file-box');

View File

@ -101,7 +101,7 @@ function initRepoSettingsBranches() {
// show the `Matched` mark for the status checks that match the pattern
const markMatchedStatusChecks = () => {
const patterns = (document.querySelector<HTMLTextAreaElement>('#status_check_contexts').value || '').split(/[\r\n]+/);
const validPatterns = patterns.map((item) => item.trim()).filter(Boolean);
const validPatterns = patterns.map((item) => item.trim()).filter(Boolean as unknown as <T>(x: T | boolean) => x is T);
const marks = document.querySelectorAll('.status-check-matched-mark');
for (const el of marks) {

View File

@ -181,7 +181,7 @@ export function svg(name: SvgName, size = 16, classNames?: string | string[]): s
svgNode.setAttribute('width', String(size));
svgNode.setAttribute('height', String(size));
}
if (className) svgNode.classList.add(...className.split(/\s+/).filter(Boolean));
if (className) svgNode.classList.add(...className.split(/\s+/).filter(Boolean as unknown as <T>(x: T | boolean) => x is T));
return serializeXml(svgNode);
}

View File

@ -30,7 +30,7 @@ const isProduction = env.NODE_ENV !== 'development';
// false - all disabled
let sourceMaps;
if ('ENABLE_SOURCEMAP' in env) {
sourceMaps = ['true', 'false'].includes(env.ENABLE_SOURCEMAP) ? env.ENABLE_SOURCEMAP : 'reduced';
sourceMaps = ['true', 'false'].includes(env.ENABLE_SOURCEMAP || '') ? env.ENABLE_SOURCEMAP : 'reduced';
} else {
sourceMaps = isProduction ? 'reduced' : 'true';
}
@ -95,7 +95,7 @@ export default {
path: fileURLToPath(new URL('public/assets', import.meta.url)),
filename: () => 'js/[name].js',
chunkFilename: ({chunk}) => {
const language = (/monaco.*languages?_.+?_(.+?)_/.exec(String(chunk.id)) || [])[1];
const language = (/monaco.*languages?_.+?_(.+?)_/.exec(String(chunk?.id)) || [])[1];
return `js/${language ? `monaco-language-${language.toLowerCase()}` : `[name]`}.[contenthash:8].js`;
},
},
@ -270,7 +270,7 @@ export default {
excludeAssets: [
/^js\/monaco-language-.+\.js$/,
!isProduction && /^licenses.txt$/,
].filter(Boolean),
].filter(Boolean as unknown as <T>(x: T | boolean) => x is T),
groupAssetsByChunk: false,
groupAssetsByEmitStatus: false,
groupAssetsByInfo: false,