0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-16 14:47:48 +02:00

Merge branch 'main' into lunny/project_workflow

This commit is contained in:
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 /VERSION
/.air /.air
/.go-licenses /.go-licenses
/Dockerfile
/Dockerfile.rootless
/.venv
# Files and folders that were previously generated # Files and folders that were previously generated
/public/assets/img/webpack /public/assets/img/webpack

View File

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

View File

@ -56,7 +56,7 @@ jobs:
- name: upload binaries to s3 - name: upload binaries to s3
run: | run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress 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 runs-on: namespace-profile-gitea-release-docker
permissions: permissions:
packages: write # to publish to ghcr.io 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 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 # 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 - 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-qemu-action@v3
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v3
- name: Get cleaned branch name - name: Get cleaned branch name
@ -76,6 +72,29 @@ jobs:
run: | run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') 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" 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 - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@ -87,57 +106,20 @@ jobs:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules - name: build regular docker image
run: make vendor
- name: build rootful docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/riscv64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
tags: |- tags: ${{ steps.meta.outputs.tags }}
gitea/gitea:${{ steps.clean_name.outputs.branch }} annotations: ${{ steps.meta.outputs.annotations }}
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
- name: build rootless docker image - name: build rootless docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
file: Dockerfile.rootless file: Dockerfile.rootless
tags: |- tags: ${{ steps.meta_rootless.outputs.tags }}
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless annotations: ${{ steps.meta_rootless.outputs.annotations }}
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless

View File

@ -66,7 +66,7 @@ jobs:
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/* gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful: container:
runs-on: namespace-profile-gitea-release-docker runs-on: namespace-profile-gitea-release-docker
permissions: permissions:
packages: write # to publish to ghcr.io packages: write # to publish to ghcr.io
@ -88,38 +88,10 @@ jobs:
# 1.2.3-rc0 # 1.2.3-rc0
tags: | tags: |
type=semver,pattern={{version}} type=semver,pattern={{version}}
- name: Login to Docker Hub annotations: |
uses: docker/login-action@v3 org.opencontainers.image.authors="maintainers@gitea.io"
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
- uses: docker/metadata-action@v5 - uses: docker/metadata-action@v5
id: meta id: meta_rootless
with: with:
images: |- images: |-
gitea/gitea gitea/gitea
@ -131,6 +103,8 @@ jobs:
# 1.2.3-rc0 # 1.2.3-rc0
tags: | tags: |
type=semver,pattern={{version}} type=semver,pattern={{version}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@ -142,12 +116,20 @@ jobs:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} 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 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/riscv64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
file: Dockerfile.rootless file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta_rootless.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} 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/* gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful: container:
runs-on: namespace-profile-gitea-release-docker runs-on: namespace-profile-gitea-release-docker
permissions: permissions:
packages: write # to publish to ghcr.io packages: write # to publish to ghcr.io
@ -96,36 +96,10 @@ jobs:
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
- name: Login to Docker Hub annotations: |
uses: docker/login-action@v3 org.opencontainers.image.authors="maintainers@gitea.io"
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
- uses: docker/metadata-action@v5 - uses: docker/metadata-action@v5
id: meta id: meta_rootless
with: with:
images: |- images: |-
gitea/gitea gitea/gitea
@ -142,6 +116,8 @@ jobs:
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@ -153,12 +129,20 @@ jobs:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} 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 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/riscv64 platforms: linux/amd64,linux/arm64,linux/riscv64
push: true push: true
file: Dockerfile.rootless file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta_rootless.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} annotations: ${{ steps.meta_rootless.outputs.annotations }}

3
.gitignore vendored
View File

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

View File

@ -1,8 +1,8 @@
# syntax=docker/dockerfile:1
# Build stage # Build stage
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
ARG GOPROXY ARG GOPROXY=direct
ENV GOPROXY=${GOPROXY:-direct}
ARG GITEA_VERSION ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
@ -14,22 +14,24 @@ RUN apk --no-cache add \
build-base \ build-base \
git \ git \
nodejs \ nodejs \
npm \ pnpm
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
# Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${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 # Build gitea, .git mount is required for version data
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ RUN --mount=type=cache,target=/go/pkg/mod \
&& make clean-all build --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 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 \ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/usr/local/bin/* \ /tmp/local/usr/local/bin/* \
/tmp/local/etc/s6/gitea/* \ /tmp/local/etc/s6/gitea/* \
@ -37,8 +39,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/etc/s6/.s6-svscan/* \ /tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea /go/src/code.gitea.io/gitea/gitea
FROM docker.io/library/alpine:3.22 FROM docker.io/library/alpine:3.22 AS gitea
LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000 EXPOSE 22 3000
@ -53,8 +54,7 @@ RUN apk --no-cache add \
s6 \ s6 \
sqlite \ sqlite \
su-exec \ su-exec \
gnupg \ gnupg
&& rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
-S -g 1000 \ -S -g 1000 \
@ -68,6 +68,9 @@ RUN addgroup \
git && \ git && \
echo "git:*" | chpasswd -e 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 USER=git
ENV GITEA_CUSTOM=/data/gitea ENV GITEA_CUSTOM=/data/gitea
@ -75,6 +78,3 @@ VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"] ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/usr/bin/s6-svscan", "/etc/s6"] 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 # Build stage
FROM docker.io/library/golang:1.25-alpine3.22 AS build-env FROM docker.io/library/golang:1.25-alpine3.22 AS build-env
ARG GOPROXY ARG GOPROXY=direct
ENV GOPROXY=${GOPROXY:-direct}
ARG GITEA_VERSION ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS="bindata timetzdata $TAGS" ENV TAGS="bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS ARG CGO_EXTRA_CFLAGS
#Build deps # Build deps
RUN apk --no-cache add \ RUN apk --no-cache add \
build-base \ build-base \
git \ git \
nodejs \ nodejs \
npm \ pnpm
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
# Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${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 # Build gitea, .git mount is required for version data
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ RUN --mount=type=cache,target=/go/pkg/mod \
&& make clean-all build --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 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/* \ RUN chmod 755 /tmp/local/usr/local/bin/* \
/go/src/code.gitea.io/gitea/gitea /go/src/code.gitea.io/gitea/gitea
FROM docker.io/library/alpine:3.22 FROM docker.io/library/alpine:3.22 AS gitea-rootless
LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000 EXPOSE 2222 3000
@ -46,8 +45,7 @@ RUN apk --no-cache add \
git \ git \
curl \ curl \
gnupg \ gnupg \
openssh-keygen \ openssh-keygen
&& rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
-S -g 1000 \ -S -g 1000 \

View File

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

View File

@ -3914,6 +3914,14 @@ variables.update.success=変数を更新しました。
logs.always_auto_scroll=常にログを自動スクロール logs.always_auto_scroll=常にログを自動スクロール
logs.always_expand_running=常に実行中のログを展開 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] [projects]
deleted.display_name=削除されたプロジェクト 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) return fmt.Errorf("failed to issue MAIL command: %w", err)
} }
} else { } 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) return fmt.Errorf("failed to issue MAIL command: %w", err)
} }
} }

View File

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

View File

@ -16,7 +16,7 @@
{{else if eq .status "blocked"}} {{else if eq .status "blocked"}}
{{svg "octicon-blocked" $size (printf "text yellow %s" $className)}} {{svg "octicon-blocked" $size (printf "text yellow %s" $className)}}
{{else if eq .status "running"}} {{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*/}} {{else}}{{/*failure, unknown*/}}
{{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}} {{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}}
{{end}} {{end}}

View File

@ -102,7 +102,7 @@
{{template "repo/issue/view_content/update_branch_by_merge" $}} {{template "repo/issue/view_content/update_branch_by_merge" $}}
{{else if .Issue.PullRequest.IsChecking}} {{else if .Issue.PullRequest.IsChecking}}
<div class="item"> <div class="item">
{{svg "octicon-sync" 16 "circular-spin"}} {{svg "gitea-running" 16 "rotate-clockwise"}}
{{ctx.Locale.Tr "repo.pulls.is_checking"}} {{ctx.Locale.Tr "repo.pulls.is_checking"}}
</div> </div>
{{else if .Issue.PullRequest.IsAncestor}} {{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) { export async function load_logged_in_context(browser: Browser, workerInfo: WorkerInfo, user: string) {
let context;
try { 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) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
throw new Error(`Could not find state for '${user}'. Did you call login_user(browser, workerInfo, '${user}') in test.beforeAll()?`); 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) { export async function save_visual(page: Page) {

View File

@ -11,7 +11,9 @@ import (
"strings" "strings"
"testing" "testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -71,3 +73,46 @@ func Test_RepoWikiPages(t *testing.T) {
assert.Equal(t, expectedPagePaths[i], pagePath) 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)); 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() { async function processMaterialFileIcons() {
const paths = glob('node_modules/material-icon-theme/icons/*.svg'); const paths = glob('node_modules/material-icon-theme/icons/*.svg');
const svgSymbols: Record<string, string> = {}; const svgSymbols: Record<string, string> = {};
@ -76,18 +80,30 @@ async function processMaterialFileIcons() {
// * https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers // * https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
// * https://github.com/microsoft/vscode/tree/1.98.0/extensions // * https://github.com/microsoft/vscode/tree/1.98.0/extensions
delete iconRules.iconDefinitions; 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; if (iconRules.fileNames) {
for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v; 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 // Use VSCode's "Language ID" mapping from its extensions
for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) { for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) {
for (const [langId, names] of Object.entries(langIdExtMap)) { for (const [langId, names] of Object.entries(langIdExtMap)) {
for (const name of names) { for (const name of names) {
const nameLower = name.toLowerCase(); const nameLower = name.toLowerCase();
if (nameLower[0] === '.') { if (nameLower[0] === '.') {
iconRules.fileExtensions[nameLower.substring(1)] ??= langId; if (iconRules.fileExtensions) {
iconRules.fileExtensions[nameLower.substring(1)] ??= langId;
}
} else { } else {
iconRules.fileNames[nameLower] ??= langId; if (iconRules.fileNames) {
iconRules.fileNames[nameLower] ??= langId;
}
} }
} }
} }

View File

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

View File

@ -116,14 +116,12 @@ code.language-math.is-loading::after {
animation-timing-function: ease-in-out; animation-timing-function: ease-in-out;
} }
/* FIXME: `octicon-sync` is counterclockwise, so this animation is also counterclockwise, it looks somewhat strange. .rotate-clockwise {
Ideally in the future we should use a better image for clockwise animation. */ animation: rotate-clockwise-keyframes 1s linear infinite;
.circular-spin {
animation: circular-spin-keyframes 1s linear infinite;
} }
@keyframes circular-spin-keyframes { @keyframes rotate-clockwise-keyframes {
100% { 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-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-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="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 --> <SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/><!-- failure, unknown -->
</span> </span>
</template> </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 <!-- 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 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']"/> <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"/> <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 class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto"> <div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading"> <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 }} {{ locale.loadingInfo }}
</div> </div>
<div v-else class="text red"> <div v-else class="text red">

View File

@ -381,7 +381,7 @@ export default defineComponent({
<div class="tw-flex ui segment main-graph"> <div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto"> <div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading"> <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 }} {{ locale.loadingInfo }}
</div> </div>
<div v-else class="text red"> <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 class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto"> <div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading"> <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 }} {{ locale.loadingInfo }}
</div> </div>
<div v-else class="text red"> <div v-else class="text red">

View File

@ -62,7 +62,7 @@ const onItemClick = (e: MouseEvent) => {
@click.stop="onItemClick" @click.stop="onItemClick"
> >
<div v-if="item.entryMode === 'tree'" class="item-toggle"> <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"/> <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop.prevent="doLoadChildren"/>
</div> </div>
<div class="item-content"> <div class="item-content">

View File

@ -261,7 +261,7 @@ export function initRepoPullRequestReview() {
if (commentDiv) { if (commentDiv) {
// get the name of the parent id // get the name of the parent id
const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('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 id = groupID.slice(14);
const ancestorDiffBox = commentDiv.closest<HTMLElement>('.diff-file-box'); 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 // show the `Matched` mark for the status checks that match the pattern
const markMatchedStatusChecks = () => { const markMatchedStatusChecks = () => {
const patterns = (document.querySelector<HTMLTextAreaElement>('#status_check_contexts').value || '').split(/[\r\n]+/); 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'); const marks = document.querySelectorAll('.status-check-matched-mark');
for (const el of marks) { 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('width', String(size));
svgNode.setAttribute('height', 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); return serializeXml(svgNode);
} }

View File

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