Compare commits
10 Commits
8c9247e717
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65d93d819b | ||
|
|
288d1f526a | ||
|
|
7883f6dde9 | ||
|
|
c2dea22926 | ||
|
|
584d8ef75f | ||
|
|
9d96039027 | ||
|
|
072de7d8cd | ||
|
|
e377da989f | ||
|
|
7ad9bf4523 | ||
|
|
7292ae1ed5 |
+1
-1
@@ -84,9 +84,9 @@ docs-update-needed:
|
|||||||
topic/code-linting:
|
topic/code-linting:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- ".eslintrc.cjs"
|
|
||||||
- ".golangci.yml"
|
- ".golangci.yml"
|
||||||
- ".markdownlint.yaml"
|
- ".markdownlint.yaml"
|
||||||
- ".spectral.yaml"
|
- ".spectral.yaml"
|
||||||
- ".yamllint.yaml"
|
- ".yamllint.yaml"
|
||||||
|
- "eslint*.config.*"
|
||||||
- "stylelint.config.*"
|
- "stylelint.config.*"
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ jobs:
|
|||||||
- "uv.lock"
|
- "uv.lock"
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
|
- ".github/workflows/pull-docker-dryrun.yml"
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
- "Dockerfile.rootless"
|
- "Dockerfile.rootless"
|
||||||
- "docker/**"
|
- "docker/**"
|
||||||
|
|||||||
@@ -14,24 +14,25 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
container:
|
container:
|
||||||
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
|
if: needs.files-changed.outputs.docker == 'true'
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
- uses: docker/setup-qemu-action@v3
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: docker/setup-buildx-action@v3
|
||||||
- name: Build regular container image
|
- name: Build regular container image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
push: false
|
push: false
|
||||||
tags: gitea/gitea:linux-amd64
|
|
||||||
- name: Build rootless container image
|
- name: Build rootless container image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/riscv64
|
||||||
file: Dockerfile.rootless
|
file: Dockerfile.rootless
|
||||||
tags: gitea/gitea:linux-amd64
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
# Instructions for agents
|
# Instructions for agents
|
||||||
|
|
||||||
- Use `make help` to find available development targets
|
- Use `make help` to find available development targets
|
||||||
- Before committing go code changes, run `make fmt`
|
- Use the latest Golang stable release when working on Go code
|
||||||
|
- Use the latest Node.js LTS release when working on TypeScript code
|
||||||
|
- Before committing `.go` changes, run `make fmt` to format, and run `make lint-go` to lint
|
||||||
|
- Before committing `.ts` changes, run `make lint-js` to lint
|
||||||
- Before committing `go.mod` changes, run `make tidy`
|
- Before committing `go.mod` changes, run `make tidy`
|
||||||
- Before committing new `.go` files, add the current year into the copyright header
|
- Before committing new `.go` files, add the current year into the copyright header
|
||||||
- Before committing files, removed any trailing whitespace
|
- Before committing any files, remove all trailing whitespace from source code lines
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# 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.23 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY=direct
|
ARG GOPROXY=direct
|
||||||
|
|
||||||
@@ -39,7 +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 AS gitea
|
FROM docker.io/library/alpine:3.23 AS gitea
|
||||||
|
|
||||||
EXPOSE 22 3000
|
EXPOSE 22 3000
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# 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.23 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY=direct
|
ARG GOPROXY=direct
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ COPY docker/rootless /tmp/local
|
|||||||
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 AS gitea-rootless
|
FROM docker.io/library/alpine:3.23 AS gitea-rootless
|
||||||
|
|
||||||
EXPOSE 2222 3000
|
EXPOSE 2222 3000
|
||||||
|
|
||||||
|
|||||||
@@ -314,16 +314,14 @@ lint-backend: lint-go lint-go-gitea-vet lint-editorconfig ## lint backend files
|
|||||||
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
|
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
|
||||||
|
|
||||||
.PHONY: lint-js
|
.PHONY: lint-js
|
||||||
lint-js: node_modules ## lint js files
|
lint-js: node_modules ## lint js and ts files
|
||||||
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES)
|
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES)
|
||||||
$(NODE_VARS) pnpm exec vue-tsc
|
$(NODE_VARS) pnpm exec vue-tsc
|
||||||
$(NODE_VARS) pnpm exec knip --no-progress --cache
|
|
||||||
|
|
||||||
.PHONY: lint-js-fix
|
.PHONY: lint-js-fix
|
||||||
lint-js-fix: node_modules ## lint js files and fix issues
|
lint-js-fix: node_modules ## lint js and ts files and fix issues
|
||||||
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES) --fix
|
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES) --fix
|
||||||
$(NODE_VARS) pnpm exec vue-tsc
|
$(NODE_VARS) pnpm exec vue-tsc
|
||||||
$(NODE_VARS) pnpm exec knip --no-progress --cache --fix
|
|
||||||
|
|
||||||
.PHONY: lint-css
|
.PHONY: lint-css
|
||||||
lint-css: node_modules ## lint css files
|
lint-css: node_modules ## lint css files
|
||||||
|
|||||||
+4
-5
@@ -15,6 +15,7 @@ import vue from 'eslint-plugin-vue';
|
|||||||
import vueScopedCss from 'eslint-plugin-vue-scoped-css';
|
import vueScopedCss from 'eslint-plugin-vue-scoped-css';
|
||||||
import wc from 'eslint-plugin-wc';
|
import wc from 'eslint-plugin-wc';
|
||||||
import {defineConfig, globalIgnores} from 'eslint/config';
|
import {defineConfig, globalIgnores} from 'eslint/config';
|
||||||
|
import type {ESLint} from 'eslint';
|
||||||
|
|
||||||
const jsExts = ['js', 'mjs', 'cjs'] as const;
|
const jsExts = ['js', 'mjs', 'cjs'] as const;
|
||||||
const tsExts = ['ts', 'mts', 'cts'] as const;
|
const tsExts = ['ts', 'mts', 'cts'] as const;
|
||||||
@@ -62,8 +63,7 @@ export default defineConfig([
|
|||||||
'@stylistic': stylistic,
|
'@stylistic': stylistic,
|
||||||
'@typescript-eslint': typescriptPlugin.plugin,
|
'@typescript-eslint': typescriptPlugin.plugin,
|
||||||
'array-func': arrayFunc,
|
'array-func': arrayFunc,
|
||||||
// @ts-expect-error -- https://github.com/un-ts/eslint-plugin-import-x/issues/203
|
'import-x': importPlugin as unknown as ESLint.Plugin, // https://github.com/un-ts/eslint-plugin-import-x/issues/203
|
||||||
'import-x': importPlugin,
|
|
||||||
regexp,
|
regexp,
|
||||||
sonarjs,
|
sonarjs,
|
||||||
unicorn,
|
unicorn,
|
||||||
@@ -156,7 +156,7 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/adjacent-overload-signatures': [0],
|
'@typescript-eslint/adjacent-overload-signatures': [0],
|
||||||
'@typescript-eslint/array-type': [0],
|
'@typescript-eslint/array-type': [0],
|
||||||
'@typescript-eslint/await-thenable': [2],
|
'@typescript-eslint/await-thenable': [2],
|
||||||
'@typescript-eslint/ban-ts-comment': [2, {'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}],
|
'@typescript-eslint/ban-ts-comment': [2, {'ts-expect-error': true, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}],
|
||||||
'@typescript-eslint/ban-tslint-comment': [0],
|
'@typescript-eslint/ban-tslint-comment': [0],
|
||||||
'@typescript-eslint/class-literal-property-style': [0],
|
'@typescript-eslint/class-literal-property-style': [0],
|
||||||
'@typescript-eslint/class-methods-use-this': [0],
|
'@typescript-eslint/class-methods-use-this': [0],
|
||||||
@@ -924,8 +924,7 @@ export default defineConfig([
|
|||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
vue.configs['flat/recommended'],
|
vue.configs['flat/recommended'],
|
||||||
// @ts-expect-error
|
vueScopedCss.configs['flat/recommended'] as any,
|
||||||
vueScopedCss.configs['flat/recommended'],
|
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/attributes-order': [0],
|
'vue/attributes-order': [0],
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import type {KnipConfig} from 'knip';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
entry: [
|
|
||||||
'*.ts',
|
|
||||||
'tools/**/*.ts',
|
|
||||||
'tests/e2e/**/*.ts',
|
|
||||||
],
|
|
||||||
ignoreDependencies: [
|
|
||||||
// dependencies used in Makefile or tools
|
|
||||||
'@primer/octicons',
|
|
||||||
'markdownlint-cli',
|
|
||||||
'nolyfill',
|
|
||||||
'spectral-cli-bundle',
|
|
||||||
'vue-tsc',
|
|
||||||
'webpack-cli',
|
|
||||||
],
|
|
||||||
} satisfies KnipConfig;
|
|
||||||
@@ -1034,6 +1034,20 @@ func GetCommentByID(ctx context.Context, id int64) (*Comment, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCommentWithRepoID(ctx context.Context, repoID, commentID int64) (*Comment, error) {
|
||||||
|
c, err := GetCommentByID(ctx, commentID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.LoadIssue(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c.Issue.RepoID != repoID {
|
||||||
|
return nil, ErrCommentNotExist{commentID, 0}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FindCommentsOptions describes the conditions to Find comments
|
// FindCommentsOptions describes the conditions to Find comments
|
||||||
type FindCommentsOptions struct {
|
type FindCommentsOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
comment.Review = re
|
comment.Review = re
|
||||||
|
comment.Issue = issue
|
||||||
}
|
}
|
||||||
comments[n] = comment
|
comments[n] = comment
|
||||||
n++
|
n++
|
||||||
|
|||||||
@@ -43,13 +43,15 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
|
|||||||
|
|
||||||
existing := &PackageBlob{}
|
existing := &PackageBlob{}
|
||||||
|
|
||||||
has, err := e.Where(builder.Eq{
|
hashCond := builder.Eq{
|
||||||
"size": pb.Size,
|
"size": pb.Size,
|
||||||
"hash_md5": pb.HashMD5,
|
"hash_md5": pb.HashMD5,
|
||||||
"hash_sha1": pb.HashSHA1,
|
"hash_sha1": pb.HashSHA1,
|
||||||
"hash_sha256": pb.HashSHA256,
|
"hash_sha256": pb.HashSHA256,
|
||||||
"hash_sha512": pb.HashSHA512,
|
"hash_sha512": pb.HashSHA512,
|
||||||
}).Get(existing)
|
}
|
||||||
|
|
||||||
|
has, err := e.Where(hashCond).Get(existing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,11 @@ func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool,
|
|||||||
return existing, true, nil
|
return existing, true, nil
|
||||||
}
|
}
|
||||||
if _, err = e.Insert(pb); err != nil {
|
if _, err = e.Insert(pb); err != nil {
|
||||||
|
// Handle race condition: another request may have inserted the same blob
|
||||||
|
// between our SELECT and INSERT. Retry the SELECT to get the existing blob.
|
||||||
|
if has, _ = e.Where(hashCond).Get(existing); has {
|
||||||
|
return existing, true, nil
|
||||||
|
}
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
return pb, false, nil
|
return pb, false, nil
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetOrInsertBlobConcurrent(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
testBlob := PackageBlob{
|
||||||
|
Size: 123,
|
||||||
|
HashMD5: "md5",
|
||||||
|
HashSHA1: "sha1",
|
||||||
|
HashSHA256: "sha256",
|
||||||
|
HashSHA512: "sha512",
|
||||||
|
}
|
||||||
|
|
||||||
|
const numGoroutines = 3
|
||||||
|
var wg errgroup.Group
|
||||||
|
results := make([]*PackageBlob, numGoroutines)
|
||||||
|
existed := make([]bool, numGoroutines)
|
||||||
|
for idx := range numGoroutines {
|
||||||
|
wg.Go(func() error {
|
||||||
|
blob := testBlob // Create a copy of the test blob for each goroutine
|
||||||
|
var err error
|
||||||
|
results[idx], existed[idx], err = GetOrInsertBlob(t.Context(), &blob)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
require.NoError(t, wg.Wait())
|
||||||
|
|
||||||
|
// then: all GetOrInsertBlob succeeds with the same blob ID, and only one indicates it did not exist before
|
||||||
|
existedCount := 0
|
||||||
|
assert.NotNil(t, results[0])
|
||||||
|
for i := range numGoroutines {
|
||||||
|
assert.Equal(t, results[0].ID, results[i].ID)
|
||||||
|
if existed[i] {
|
||||||
|
existedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Equal(t, numGoroutines-1, existedCount)
|
||||||
|
}
|
||||||
+24
-10
@@ -4,6 +4,7 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
@@ -23,10 +24,6 @@ func NewGhostUser() *User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsGhostUserName(name string) bool {
|
|
||||||
return strings.EqualFold(name, GhostUserName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsGhost check if user is fake user for a deleted account
|
// IsGhost check if user is fake user for a deleted account
|
||||||
func (u *User) IsGhost() bool {
|
func (u *User) IsGhost() bool {
|
||||||
if u == nil {
|
if u == nil {
|
||||||
@@ -41,10 +38,6 @@ const (
|
|||||||
ActionsUserEmail = "teabot@gitea.io"
|
ActionsUserEmail = "teabot@gitea.io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsGiteaActionsUserName(name string) bool {
|
|
||||||
return strings.EqualFold(name, ActionsUserName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewActionsUser creates and returns a fake user for running the actions.
|
// NewActionsUser creates and returns a fake user for running the actions.
|
||||||
func NewActionsUser() *User {
|
func NewActionsUser() *User {
|
||||||
return &User{
|
return &User{
|
||||||
@@ -61,15 +54,36 @@ func NewActionsUser() *User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewActionsUserWithTaskID(id int64) *User {
|
||||||
|
u := NewActionsUser()
|
||||||
|
// LoginName is for only internal usage in this case, so it can be moved to other fields in the future
|
||||||
|
u.LoginSource = -1
|
||||||
|
u.LoginName = "@" + ActionsUserName + "/" + strconv.FormatInt(id, 10)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetActionsUserTaskID(u *User) (int64, bool) {
|
||||||
|
if u == nil || u.ID != ActionsUserID {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
prefix, payload, _ := strings.Cut(u.LoginName, "/")
|
||||||
|
if prefix != "@"+ActionsUserName {
|
||||||
|
return 0, false
|
||||||
|
} else if taskID, err := strconv.ParseInt(payload, 10, 64); err == nil {
|
||||||
|
return taskID, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) IsGiteaActions() bool {
|
func (u *User) IsGiteaActions() bool {
|
||||||
return u != nil && u.ID == ActionsUserID
|
return u != nil && u.ID == ActionsUserID
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSystemUserByName(name string) *User {
|
func GetSystemUserByName(name string) *User {
|
||||||
if IsGhostUserName(name) {
|
if strings.EqualFold(name, GhostUserName) {
|
||||||
return NewGhostUser()
|
return NewGhostUser()
|
||||||
}
|
}
|
||||||
if IsGiteaActionsUserName(name) {
|
if strings.EqualFold(name, ActionsUserName) {
|
||||||
return NewActionsUser()
|
return NewActionsUser()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -16,14 +16,20 @@ func TestSystemUser(t *testing.T) {
|
|||||||
assert.Equal(t, "Ghost", u.Name)
|
assert.Equal(t, "Ghost", u.Name)
|
||||||
assert.Equal(t, "ghost", u.LowerName)
|
assert.Equal(t, "ghost", u.LowerName)
|
||||||
assert.True(t, u.IsGhost())
|
assert.True(t, u.IsGhost())
|
||||||
assert.True(t, IsGhostUserName("gHost"))
|
|
||||||
|
u = GetSystemUserByName("gHost")
|
||||||
|
require.NotNil(t, u)
|
||||||
|
assert.Equal(t, "Ghost", u.Name)
|
||||||
|
|
||||||
u, err = GetPossibleUserByID(t.Context(), -2)
|
u, err = GetPossibleUserByID(t.Context(), -2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "gitea-actions", u.Name)
|
assert.Equal(t, "gitea-actions", u.Name)
|
||||||
assert.Equal(t, "gitea-actions", u.LowerName)
|
assert.Equal(t, "gitea-actions", u.LowerName)
|
||||||
assert.True(t, u.IsGiteaActions())
|
assert.True(t, u.IsGiteaActions())
|
||||||
assert.True(t, IsGiteaActionsUserName("Gitea-actionS"))
|
|
||||||
|
u = GetSystemUserByName("Gitea-actionS")
|
||||||
|
require.NotNil(t, u)
|
||||||
|
assert.Equal(t, "Gitea Actions", u.FullName)
|
||||||
|
|
||||||
_, err = GetPossibleUserByID(t.Context(), -3)
|
_, err = GetPossibleUserByID(t.Context(), -3)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|||||||
@@ -4,10 +4,28 @@
|
|||||||
package analyze
|
package analyze
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-enry/go-enry/v2"
|
"github.com/go-enry/go-enry/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsVendor returns whether or not path is a vendor path.
|
// IsVendor returns whether the path is a vendor path.
|
||||||
func IsVendor(path string) bool {
|
// It uses go-enry's IsVendor function but overrides its detection for certain
|
||||||
return enry.IsVendor(path)
|
// special cases that shouldn't be marked as vendored in the diff view.
|
||||||
|
func IsVendor(treePath string) bool {
|
||||||
|
if !enry.IsVendor(treePath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override detection for single files
|
||||||
|
basename := path.Base(treePath)
|
||||||
|
switch basename {
|
||||||
|
case ".gitignore", ".gitattributes", ".gitmodules":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(treePath, ".github/") || strings.HasPrefix(treePath, ".gitea/") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ func TestIsVendor(t *testing.T) {
|
|||||||
path string
|
path string
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
|
// Original go-enry test cases
|
||||||
{"cache/", true},
|
{"cache/", true},
|
||||||
{"random/cache/", true},
|
{"random/cache/", true},
|
||||||
{"cache", false},
|
{"cache", false},
|
||||||
@@ -34,6 +35,14 @@ func TestIsVendor(t *testing.T) {
|
|||||||
{"a/docs/_build/", true},
|
{"a/docs/_build/", true},
|
||||||
{"a/dasdocs/_build-vsdoc.js", true},
|
{"a/dasdocs/_build-vsdoc.js", true},
|
||||||
{"a/dasdocs/_build-vsdoc.j", false},
|
{"a/dasdocs/_build-vsdoc.j", false},
|
||||||
|
|
||||||
|
// Override: Git/GitHub/Gitea-related paths should NOT be detected as vendored
|
||||||
|
{".gitignore", false},
|
||||||
|
{".gitattributes", false},
|
||||||
|
{".gitmodules", false},
|
||||||
|
{"src/.gitignore", false},
|
||||||
|
{".github/workflows/ci.yml", false},
|
||||||
|
{".gitea/workflows/ci.yml", false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.path, func(t *testing.T) {
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ type PullReviewComment struct {
|
|||||||
HTMLPullURL string `json:"pull_request_url"`
|
HTMLPullURL string `json:"pull_request_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePullReviewOptions are options to create a pull review
|
// CreatePullReviewOptions are options to create a pull request review
|
||||||
type CreatePullReviewOptions struct {
|
type CreatePullReviewOptions struct {
|
||||||
Event ReviewStateType `json:"event"`
|
Event ReviewStateType `json:"event"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
@@ -91,19 +91,19 @@ type CreatePullReviewComment struct {
|
|||||||
NewLineNum int64 `json:"new_position"`
|
NewLineNum int64 `json:"new_position"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitPullReviewOptions are options to submit a pending pull review
|
// SubmitPullReviewOptions are options to submit a pending pull request review
|
||||||
type SubmitPullReviewOptions struct {
|
type SubmitPullReviewOptions struct {
|
||||||
Event ReviewStateType `json:"event"`
|
Event ReviewStateType `json:"event"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DismissPullReviewOptions are options to dismiss a pull review
|
// DismissPullReviewOptions are options to dismiss a pull request review
|
||||||
type DismissPullReviewOptions struct {
|
type DismissPullReviewOptions struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Priors bool `json:"priors"`
|
Priors bool `json:"priors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullReviewRequestOptions are options to add or remove pull review requests
|
// PullReviewRequestOptions are options to add or remove pull request review requests
|
||||||
type PullReviewRequestOptions struct {
|
type PullReviewRequestOptions struct {
|
||||||
Reviewers []string `json:"reviewers"`
|
Reviewers []string `json:"reviewers"`
|
||||||
TeamReviewers []string `json:"team_reviewers"`
|
TeamReviewers []string `json:"team_reviewers"`
|
||||||
|
|||||||
@@ -977,6 +977,7 @@
|
|||||||
"repo.fork.blocked_user": "Ní féidir an stór a fhorcáil toisc go bhfuil úinéir an stórais bac ort.",
|
"repo.fork.blocked_user": "Ní féidir an stór a fhorcáil toisc go bhfuil úinéir an stórais bac ort.",
|
||||||
"repo.use_template": "Úsáid an teimpléad seo",
|
"repo.use_template": "Úsáid an teimpléad seo",
|
||||||
"repo.open_with_editor": "Oscail le %s",
|
"repo.open_with_editor": "Oscail le %s",
|
||||||
|
"repo.download_directory_as": "Íoslódáil an eolaire mar %s",
|
||||||
"repo.download_zip": "Íoslódáil ZIP",
|
"repo.download_zip": "Íoslódáil ZIP",
|
||||||
"repo.download_tar": "Íoslódáil TAR.GZ",
|
"repo.download_tar": "Íoslódáil TAR.GZ",
|
||||||
"repo.download_bundle": "Íoslódáil BUNDLE",
|
"repo.download_bundle": "Íoslódáil BUNDLE",
|
||||||
@@ -1489,6 +1490,7 @@
|
|||||||
"repo.issues.filter_sort.feweststars": "An líon réaltaí is lú",
|
"repo.issues.filter_sort.feweststars": "An líon réaltaí is lú",
|
||||||
"repo.issues.filter_sort.mostforks": "An líon forcanna is mó",
|
"repo.issues.filter_sort.mostforks": "An líon forcanna is mó",
|
||||||
"repo.issues.filter_sort.fewestforks": "An líon forcanna is lú",
|
"repo.issues.filter_sort.fewestforks": "An líon forcanna is lú",
|
||||||
|
"repo.issues.quick_goto": "Téigh go dtí an cheist",
|
||||||
"repo.issues.action_open": "Oscailte",
|
"repo.issues.action_open": "Oscailte",
|
||||||
"repo.issues.action_close": "Dún",
|
"repo.issues.action_close": "Dún",
|
||||||
"repo.issues.action_label": "Lipéad",
|
"repo.issues.action_label": "Lipéad",
|
||||||
@@ -1701,6 +1703,7 @@
|
|||||||
"repo.issues.review.content.empty": "Ní mór duit trácht a fhágáil a léiríonn an t-athrú (í) iarrtha.",
|
"repo.issues.review.content.empty": "Ní mór duit trácht a fhágáil a léiríonn an t-athrú (í) iarrtha.",
|
||||||
"repo.issues.review.reject": "athruithe iarrtha %s",
|
"repo.issues.review.reject": "athruithe iarrtha %s",
|
||||||
"repo.issues.review.wait": "iarradh athbhreithniú %s",
|
"repo.issues.review.wait": "iarradh athbhreithniú %s",
|
||||||
|
"repo.issues.review.codeowners_rules": "Rialacha CÓDÚINÉIRÍ",
|
||||||
"repo.issues.review.add_review_request": "athbhreithniú iarrtha ó %s %s",
|
"repo.issues.review.add_review_request": "athbhreithniú iarrtha ó %s %s",
|
||||||
"repo.issues.review.remove_review_request": "iarratas athbhreithnithe bainte le haghaidh %s %s",
|
"repo.issues.review.remove_review_request": "iarratas athbhreithnithe bainte le haghaidh %s %s",
|
||||||
"repo.issues.review.remove_review_request_self": "dhiúltaigh athbhreithniú a dhéanamh ar %s",
|
"repo.issues.review.remove_review_request_self": "dhiúltaigh athbhreithniú a dhéanamh ar %s",
|
||||||
@@ -1793,6 +1796,7 @@
|
|||||||
"repo.pulls.remove_prefix": "Bain an réimír <strong>%s</strong>",
|
"repo.pulls.remove_prefix": "Bain an réimír <strong>%s</strong>",
|
||||||
"repo.pulls.data_broken": "Tá an t-iarratas tarraingthe seo briste mar gheall ar fhaisnéis forc a bheith in easnamh.",
|
"repo.pulls.data_broken": "Tá an t-iarratas tarraingthe seo briste mar gheall ar fhaisnéis forc a bheith in easnamh.",
|
||||||
"repo.pulls.files_conflicted": "Tá athruithe ag an iarratas tarraingthe seo atá contrártha leis an spriocbhrainse.",
|
"repo.pulls.files_conflicted": "Tá athruithe ag an iarratas tarraingthe seo atá contrártha leis an spriocbhrainse.",
|
||||||
|
"repo.pulls.files_conflicted_no_listed_files": "(Níl aon chomhaid choinbhleacha liostaithe)",
|
||||||
"repo.pulls.is_checking": "Ag seiceáil le haghaidh coinbhleachtaí cumaisc…",
|
"repo.pulls.is_checking": "Ag seiceáil le haghaidh coinbhleachtaí cumaisc…",
|
||||||
"repo.pulls.is_ancestor": "Tá an brainse seo san áireamh cheana féin sa spriocbhrainse. Níl aon rud le cumasc.",
|
"repo.pulls.is_ancestor": "Tá an brainse seo san áireamh cheana féin sa spriocbhrainse. Níl aon rud le cumasc.",
|
||||||
"repo.pulls.is_empty": "Tá na hathruithe ar an mbrainse seo ar an spriocbhrainse cheana féin. Is tiomantas folamh é seo.",
|
"repo.pulls.is_empty": "Tá na hathruithe ar an mbrainse seo ar an spriocbhrainse cheana féin. Is tiomantas folamh é seo.",
|
||||||
@@ -1847,7 +1851,8 @@
|
|||||||
"repo.pulls.status_checking": "Tá roinnt seiceála ar feitheamh",
|
"repo.pulls.status_checking": "Tá roinnt seiceála ar feitheamh",
|
||||||
"repo.pulls.status_checks_success": "D'éirigh le gach seiceáil",
|
"repo.pulls.status_checks_success": "D'éirigh le gach seiceáil",
|
||||||
"repo.pulls.status_checks_warning": "Thuairiscigh roinnt seiceálacha rabhaidh",
|
"repo.pulls.status_checks_warning": "Thuairiscigh roinnt seiceálacha rabhaidh",
|
||||||
"repo.pulls.status_checks_failure": "Theip ar roinnt seiceálacha",
|
"repo.pulls.status_checks_failure_required": "Theip ar roinnt seiceálacha riachtanacha",
|
||||||
|
"repo.pulls.status_checks_failure_optional": "Theip ar roinnt seiceálacha roghnacha",
|
||||||
"repo.pulls.status_checks_error": "Thug roinnt seiceálacha earráidí",
|
"repo.pulls.status_checks_error": "Thug roinnt seiceálacha earráidí",
|
||||||
"repo.pulls.status_checks_requested": "Riachtanach",
|
"repo.pulls.status_checks_requested": "Riachtanach",
|
||||||
"repo.pulls.status_checks_details": "Sonraí",
|
"repo.pulls.status_checks_details": "Sonraí",
|
||||||
@@ -2542,8 +2547,8 @@
|
|||||||
"repo.diff.too_many_files": "Níor taispeánadh roinnt comhad mar go bhfuil an iomarca comhad athraithe sa difríocht seo",
|
"repo.diff.too_many_files": "Níor taispeánadh roinnt comhad mar go bhfuil an iomarca comhad athraithe sa difríocht seo",
|
||||||
"repo.diff.show_more": "Taispeáin Tuilleadh",
|
"repo.diff.show_more": "Taispeáin Tuilleadh",
|
||||||
"repo.diff.load": "Difríocht Luchtaigh",
|
"repo.diff.load": "Difríocht Luchtaigh",
|
||||||
"repo.diff.generated": "a ghintear",
|
"repo.diff.generated": "Gineadh",
|
||||||
"repo.diff.vendored": "curtha ar fáil",
|
"repo.diff.vendored": "Díoltóir",
|
||||||
"repo.diff.comment.add_line_comment": "Cuir trácht líne leis",
|
"repo.diff.comment.add_line_comment": "Cuir trácht líne leis",
|
||||||
"repo.diff.comment.placeholder": "Fág trácht",
|
"repo.diff.comment.placeholder": "Fág trácht",
|
||||||
"repo.diff.comment.add_single_comment": "Cuir trácht aonair leis",
|
"repo.diff.comment.add_single_comment": "Cuir trácht aonair leis",
|
||||||
@@ -2660,7 +2665,7 @@
|
|||||||
"repo.branch.new_branch_from": "Cruthaigh brainse nua ó \"%s\"",
|
"repo.branch.new_branch_from": "Cruthaigh brainse nua ó \"%s\"",
|
||||||
"repo.branch.renamed": "Ainmníodh brainse %s go %s.",
|
"repo.branch.renamed": "Ainmníodh brainse %s go %s.",
|
||||||
"repo.branch.rename_default_or_protected_branch_error": "Ní féidir ach le riarthóirí brainsí réamhshocraithe nó cosanta a athainmniú.",
|
"repo.branch.rename_default_or_protected_branch_error": "Ní féidir ach le riarthóirí brainsí réamhshocraithe nó cosanta a athainmniú.",
|
||||||
"repo.branch.rename_protected_branch_failed": "Tá an brainse seo faoi chosaint ag rialacha cosanta domhanda.",
|
"repo.branch.rename_protected_branch_failed": "Theip ar athainmniú na brainse mar gheall ar rialacha cosanta brainse.",
|
||||||
"repo.branch.commits_divergence_from": "Difríocht tiomantais: %[1]d taobh thiar agus %[2]d chun tosaigh ar %[3]s",
|
"repo.branch.commits_divergence_from": "Difríocht tiomantais: %[1]d taobh thiar agus %[2]d chun tosaigh ar %[3]s",
|
||||||
"repo.branch.commits_no_divergence": "Mar an gcéanna le brainse %[1]s",
|
"repo.branch.commits_no_divergence": "Mar an gcéanna le brainse %[1]s",
|
||||||
"repo.tag.create_tag": "Cruthaigh clib %s",
|
"repo.tag.create_tag": "Cruthaigh clib %s",
|
||||||
@@ -3281,8 +3286,6 @@
|
|||||||
"admin.config.git_gc_args": "Argóintí GC",
|
"admin.config.git_gc_args": "Argóintí GC",
|
||||||
"admin.config.git_migrate_timeout": "Teorainn Ama Imirce",
|
"admin.config.git_migrate_timeout": "Teorainn Ama Imirce",
|
||||||
"admin.config.git_mirror_timeout": "Teorainn Ama Nuashonraithe Scátháin",
|
"admin.config.git_mirror_timeout": "Teorainn Ama Nuashonraithe Scátháin",
|
||||||
"admin.config.git_clone_timeout": "Teorainn Ama Oibríochta Clón",
|
|
||||||
"admin.config.git_pull_timeout": "Tarraing Am Oibríochta",
|
|
||||||
"admin.config.git_gc_timeout": "Teorainn Ama Oibriúcháin GC",
|
"admin.config.git_gc_timeout": "Teorainn Ama Oibriúcháin GC",
|
||||||
"admin.config.log_config": "Cumraíocht Logáil",
|
"admin.config.log_config": "Cumraíocht Logáil",
|
||||||
"admin.config.logger_name_fmt": "Logálaí: %s",
|
"admin.config.logger_name_fmt": "Logálaí: %s",
|
||||||
@@ -3724,8 +3727,8 @@
|
|||||||
"projects.exit_fullscreen": "Scoir Lánscáileáin",
|
"projects.exit_fullscreen": "Scoir Lánscáileáin",
|
||||||
"git.filemode.changed_filemode": "%[1]s → %[2]s",
|
"git.filemode.changed_filemode": "%[1]s → %[2]s",
|
||||||
"git.filemode.directory": "Eolaire",
|
"git.filemode.directory": "Eolaire",
|
||||||
"git.filemode.normal_file": "Comhad gnáth",
|
"git.filemode.normal_file": "Rialta",
|
||||||
"git.filemode.executable_file": "Comhad infheidhmithe",
|
"git.filemode.executable_file": "Inrite",
|
||||||
"git.filemode.symbolic_link": "Nasc siombalach",
|
"git.filemode.symbolic_link": "Nasc siombalach",
|
||||||
"git.filemode.submodule": "Fo-mhodúl"
|
"git.filemode.submodule": "Fo-mhodúl"
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-16
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.28.1",
|
"packageManager": "pnpm@10.28.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 22.6.0",
|
"node": ">= 22.6.0",
|
||||||
"pnpm": ">= 10.0.0"
|
"pnpm": ">= 10.0.0"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"clippie": "4.1.9",
|
"clippie": "4.1.9",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.3",
|
||||||
"dayjs": "1.11.19",
|
"dayjs": "1.11.19",
|
||||||
"dropzone": "6.0.0-beta.2",
|
"dropzone": "6.0.0-beta.2",
|
||||||
"easymde": "2.20.0",
|
"easymde": "2.20.0",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"idiomorph": "0.7.4",
|
"idiomorph": "0.7.4",
|
||||||
"jquery": "4.0.0",
|
"jquery": "4.0.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"katex": "0.16.27",
|
"katex": "0.16.28",
|
||||||
"mermaid": "11.12.2",
|
"mermaid": "11.12.2",
|
||||||
"mini-css-extract-plugin": "2.10.0",
|
"mini-css-extract-plugin": "2.10.0",
|
||||||
"monaco-editor": "0.55.1",
|
"monaco-editor": "0.55.1",
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint-community/eslint-plugin-eslint-comments": "4.6.0",
|
"@eslint-community/eslint-plugin-eslint-comments": "4.6.0",
|
||||||
"@eslint/json": "0.14.0",
|
"@eslint/json": "0.14.0",
|
||||||
"@playwright/test": "1.58.0",
|
"@playwright/test": "1.58.1",
|
||||||
"@stylistic/eslint-plugin": "5.7.1",
|
"@stylistic/eslint-plugin": "5.7.1",
|
||||||
"@stylistic/stylelint-plugin": "5.0.1",
|
"@stylistic/stylelint-plugin": "5.0.1",
|
||||||
"@types/codemirror": "5.60.17",
|
"@types/codemirror": "5.60.17",
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/toastify-js": "1.12.4",
|
"@types/toastify-js": "1.12.4",
|
||||||
"@typescript-eslint/parser": "8.53.1",
|
"@typescript-eslint/parser": "8.54.0",
|
||||||
"@vitejs/plugin-vue": "6.0.3",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
"@vitest/eslint-plugin": "1.6.6",
|
"@vitest/eslint-plugin": "1.6.6",
|
||||||
"eslint": "9.39.2",
|
"eslint": "9.39.2",
|
||||||
@@ -90,34 +90,33 @@
|
|||||||
"eslint-plugin-array-func": "5.1.0",
|
"eslint-plugin-array-func": "5.1.0",
|
||||||
"eslint-plugin-github": "6.0.0",
|
"eslint-plugin-github": "6.0.0",
|
||||||
"eslint-plugin-import-x": "4.16.1",
|
"eslint-plugin-import-x": "4.16.1",
|
||||||
"eslint-plugin-playwright": "2.5.0",
|
"eslint-plugin-playwright": "2.5.1",
|
||||||
"eslint-plugin-regexp": "3.0.0",
|
"eslint-plugin-regexp": "3.0.0",
|
||||||
"eslint-plugin-sonarjs": "3.0.5",
|
"eslint-plugin-sonarjs": "3.0.6",
|
||||||
"eslint-plugin-unicorn": "62.0.0",
|
"eslint-plugin-unicorn": "62.0.0",
|
||||||
"eslint-plugin-vue": "10.7.0",
|
"eslint-plugin-vue": "10.7.0",
|
||||||
"eslint-plugin-vue-scoped-css": "2.12.0",
|
"eslint-plugin-vue-scoped-css": "2.12.0",
|
||||||
"eslint-plugin-wc": "3.0.2",
|
"eslint-plugin-wc": "3.0.2",
|
||||||
"globals": "17.1.0",
|
"globals": "17.2.0",
|
||||||
"happy-dom": "20.3.7",
|
"happy-dom": "20.4.0",
|
||||||
"jiti": "2.6.1",
|
"jiti": "2.6.1",
|
||||||
"knip": "5.82.1",
|
|
||||||
"markdownlint-cli": "0.47.0",
|
"markdownlint-cli": "0.47.0",
|
||||||
"material-icon-theme": "5.31.0",
|
"material-icon-theme": "5.31.0",
|
||||||
"nolyfill": "1.0.44",
|
"nolyfill": "1.0.44",
|
||||||
"postcss-html": "1.8.1",
|
"postcss-html": "1.8.1",
|
||||||
"spectral-cli-bundle": "1.0.3",
|
"spectral-cli-bundle": "1.0.3",
|
||||||
"stylelint": "17.0.0",
|
"stylelint": "17.1.0",
|
||||||
"stylelint-config-recommended": "18.0.0",
|
"stylelint-config-recommended": "18.0.0",
|
||||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
|
||||||
"stylelint-declaration-strict-value": "1.10.11",
|
"stylelint-declaration-strict-value": "1.10.11",
|
||||||
"stylelint-value-no-unknown-custom-properties": "6.1.1",
|
"stylelint-value-no-unknown-custom-properties": "6.1.1",
|
||||||
"svgo": "4.0.0",
|
"svgo": "4.0.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.53.1",
|
"typescript-eslint": "8.54.0",
|
||||||
"updates": "17.0.8",
|
"updates": "17.0.9",
|
||||||
"vite-string-plugin": "1.5.0",
|
"vite-string-plugin": "2.0.0",
|
||||||
"vitest": "4.0.18",
|
"vitest": "4.0.18",
|
||||||
"vue-tsc": "3.2.3"
|
"vue-tsc": "3.2.4"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"defaults"
|
"defaults"
|
||||||
|
|||||||
Generated
+346
-624
File diff suppressed because it is too large
Load Diff
@@ -26,9 +26,18 @@ import (
|
|||||||
|
|
||||||
// saveAsPackageBlob creates a package blob from an upload
|
// saveAsPackageBlob creates a package blob from an upload
|
||||||
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
||||||
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam // PackageBlob is never used
|
// There will be concurrent uploading for the same blob, so it needs a global lock per blob hash
|
||||||
|
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam //returned PackageBlob is never used
|
||||||
pb := packages_service.NewPackageBlob(hsr)
|
pb := packages_service.NewPackageBlob(hsr)
|
||||||
|
err := globallock.LockAndDo(ctx, "container-blob:"+pb.HashSHA256, func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
pb, err = saveAsPackageBlobInternal(ctx, hsr, pci, pb)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return pb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveAsPackageBlobInternal(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo, pb *packages_model.PackageBlob) (*packages_model.PackageBlob, error) {
|
||||||
exists := false
|
exists := false
|
||||||
|
|
||||||
contentStore := packages_module.NewContentStore()
|
contentStore := packages_module.NewContentStore()
|
||||||
@@ -67,7 +76,7 @@ func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader
|
|||||||
return createFileForBlob(ctx, uploadVersion, pb)
|
return createFileForBlob(ctx, uploadVersion, pb)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !exists {
|
if !exists && pb != nil { // pb can be nil if GetOrInsertBlob failed
|
||||||
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
||||||
log.Error("Error deleting package blob from content store: %v", err)
|
log.Error("Error deleting package blob from content store: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,8 +188,7 @@ func repoAssignment() func(ctx *context.APIContext) {
|
|||||||
repo.Owner = owner
|
repo.Owner = owner
|
||||||
ctx.Repo.Repository = repo
|
ctx.Repo.Repository = repo
|
||||||
|
|
||||||
if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
|
if taskID, ok := user_model.GetActionsUserTaskID(ctx.Doer); ok {
|
||||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
|
||||||
ctx.Repo.Permission, err = access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
|
ctx.Repo.Permission, err = access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
@@ -349,11 +348,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
|||||||
// Contexter middleware already checks token for user sign in process.
|
// Contexter middleware already checks token for user sign in process.
|
||||||
func reqToken() func(ctx *context.APIContext) {
|
func reqToken() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
// If actions token is present
|
// if a real user is signed in, or the user is from a Actions task, we are good
|
||||||
if true == ctx.Data["IsActionsToken"] {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1353,6 +1348,8 @@ func Routes() *web.Router {
|
|||||||
m.Combo("").Get(repo.ListPullRequests).
|
m.Combo("").Get(repo.ListPullRequests).
|
||||||
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
|
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
|
||||||
m.Get("/pinned", repo.ListPinnedPullRequests)
|
m.Get("/pinned", repo.ListPinnedPullRequests)
|
||||||
|
m.Post("/comments/{id}/resolve", reqToken(), mustNotBeArchived, repo.ResolvePullReviewComment)
|
||||||
|
m.Post("/comments/{id}/unresolve", reqToken(), mustNotBeArchived, repo.UnresolvePullReviewComment)
|
||||||
m.Group("/{index}", func() {
|
m.Group("/{index}", func() {
|
||||||
m.Combo("").Get(repo.GetPullRequest).
|
m.Combo("").Get(repo.GetPullRequest).
|
||||||
Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
|
Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ func GetIssueComment(ctx *context.APIContext) {
|
|||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrCommentNotExist(err) {
|
if issues_model.IsErrCommentNotExist(err) {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
@@ -455,15 +455,6 @@ func GetIssueComment(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = comment.LoadIssue(ctx); err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||||
ctx.APIErrorNotFound()
|
ctx.APIErrorNotFound()
|
||||||
return
|
return
|
||||||
@@ -579,7 +570,7 @@ func EditIssueCommentDeprecated(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
|
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
|
||||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrCommentNotExist(err) {
|
if issues_model.IsErrCommentNotExist(err) {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
@@ -589,16 +580,6 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||||
ctx.Status(http.StatusForbidden)
|
ctx.Status(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -698,7 +679,7 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deleteIssueComment(ctx *context.APIContext) {
|
func deleteIssueComment(ctx *context.APIContext) {
|
||||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrCommentNotExist(err) {
|
if issues_model.IsErrCommentNotExist(err) {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
@@ -708,16 +689,6 @@ func deleteIssueComment(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||||
ctx.Status(http.StatusForbidden)
|
ctx.Status(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ func Migrate(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := migrations.MigrateOptions{
|
opts := migrations.MigrateOptions{
|
||||||
|
OriginalURL: form.CloneAddr,
|
||||||
CloneAddr: remoteAddr,
|
CloneAddr: remoteAddr,
|
||||||
RepoName: form.RepoName,
|
RepoName: form.RepoName,
|
||||||
Description: form.Description,
|
Description: form.Description,
|
||||||
|
|||||||
@@ -208,6 +208,126 @@ func GetPullReviewComments(ctx *context.APIContext) {
|
|||||||
ctx.JSON(http.StatusOK, apiComments)
|
ctx.JSON(http.StatusOK, apiComments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolvePullReviewComment resolves a review comment in a pull request
|
||||||
|
func ResolvePullReviewComment(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/pulls/comments/{id}/resolve repository repoResolvePullReviewComment
|
||||||
|
// ---
|
||||||
|
// summary: Resolve a pull request review comment
|
||||||
|
// 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: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the review comment
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
updatePullReviewCommentResolve(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnresolvePullReviewComment unresolves a review comment in a pull request
|
||||||
|
func UnresolvePullReviewComment(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/pulls/comments/{id}/unresolve repository repoUnresolvePullReviewComment
|
||||||
|
// ---
|
||||||
|
// summary: Unresolve a pull request review comment
|
||||||
|
// 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: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the review comment
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
updatePullReviewCommentResolve(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePullReviewCommentResolve(ctx *context.APIContext, isResolve bool) {
|
||||||
|
comment := getPullReviewCommentToResolve(ctx)
|
||||||
|
if comment == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
canMarkConv, err := issues_model.CanMarkConversation(ctx, comment.Issue, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !canMarkConv {
|
||||||
|
ctx.APIError(http.StatusForbidden, "user should have permission to resolve comment")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = issues_model.MarkConversation(ctx, comment, ctx.Doer, isResolve); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPullReviewCommentToResolve(ctx *context.APIContext) *issues_model.Comment {
|
||||||
|
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||||
|
if err != nil {
|
||||||
|
if issues_model.IsErrCommentNotExist(err) {
|
||||||
|
ctx.APIErrorNotFound("GetCommentByID", err)
|
||||||
|
} else {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !comment.Issue.IsPull {
|
||||||
|
ctx.APIError(http.StatusBadRequest, "comment does not belong to a pull request")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if comment.Type != issues_model.CommentTypeCode {
|
||||||
|
ctx.APIError(http.StatusBadRequest, "comment is not a review comment")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return comment
|
||||||
|
}
|
||||||
|
|
||||||
// DeletePullReview delete a specific review from a pull request
|
// DeletePullReview delete a specific review from a pull request
|
||||||
func DeletePullReview(ctx *context.APIContext) {
|
func DeletePullReview(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoDeletePullReview
|
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoDeletePullReview
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"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/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
@@ -166,7 +167,7 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && ctx.Data["IsActionsToken"] != true {
|
if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && !ctx.Doer.IsGiteaActions() {
|
||||||
_, err = auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
_, err = auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
|
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
|
||||||
@@ -197,8 +198,7 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
|
|||||||
accessMode = perm.AccessModeRead
|
accessMode = perm.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Data["IsActionsToken"] == true {
|
if taskID, ok := user_model.GetActionsUserTaskID(ctx.Doer); ok {
|
||||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
|
||||||
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
|
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetActionsUserRepoPermission", err)
|
ctx.ServerError("GetActionsUserRepoPermission", err)
|
||||||
|
|||||||
@@ -117,12 +117,8 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
|||||||
task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken)
|
task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken)
|
||||||
if err == nil && task != nil {
|
if err == nil && task != nil {
|
||||||
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
||||||
|
|
||||||
store.GetData()["LoginMethod"] = ActionTokenMethodName
|
store.GetData()["LoginMethod"] = ActionTokenMethodName
|
||||||
store.GetData()["IsActionsToken"] = true
|
return user_model.NewActionsUserWithTaskID(task.ID), nil
|
||||||
store.GetData()["ActionsTaskID"] = task.ID
|
|
||||||
|
|
||||||
return user_model.NewActionsUser(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !setting.Service.EnableBasicAuth {
|
if !setting.Service.EnableBasicAuth {
|
||||||
|
|||||||
+19
-40
@@ -6,6 +6,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -17,14 +18,12 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/actions"
|
"code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/oauth2_provider"
|
"code.gitea.io/gitea/services/oauth2_provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
var _ Method = &OAuth2{}
|
||||||
var (
|
|
||||||
_ Method = &OAuth2{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetOAuthAccessTokenScopeAndUserID returns access token scope and user id
|
// GetOAuthAccessTokenScopeAndUserID returns access token scope and user id
|
||||||
func GetOAuthAccessTokenScopeAndUserID(ctx context.Context, accessToken string) (auth_model.AccessTokenScope, int64) {
|
func GetOAuthAccessTokenScopeAndUserID(ctx context.Context, accessToken string) (auth_model.AccessTokenScope, int64) {
|
||||||
@@ -106,18 +105,16 @@ func parseToken(req *http.Request) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// userIDFromToken returns the user id corresponding to the OAuth token.
|
// userFromToken returns the user corresponding to the OAuth token.
|
||||||
// It will set 'IsApiToken' to true if the token is an API token and
|
// It will set 'IsApiToken' to true if the token is an API token and
|
||||||
// set 'ApiTokenScope' to the scope of the access token
|
// set 'ApiTokenScope' to the scope of the access token (TODO: this behavior should be fixed, don't set ctx.Data)
|
||||||
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 {
|
func (o *OAuth2) userFromToken(ctx context.Context, tokenSHA string, store DataStore) (*user_model.User, error) {
|
||||||
// Let's see if token is valid.
|
// Let's see if token is valid.
|
||||||
if strings.Contains(tokenSHA, ".") {
|
if strings.Contains(tokenSHA, ".") {
|
||||||
// First attempt to decode an actions JWT, returning the actions user
|
// First attempt to decode an actions JWT, returning the actions user
|
||||||
if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil {
|
if taskID, err := actions.TokenToTaskID(tokenSHA); err == nil {
|
||||||
if CheckTaskIsRunning(ctx, taskID) {
|
if CheckTaskIsRunning(ctx, taskID) {
|
||||||
store.GetData()["IsActionsToken"] = true
|
return user_model.NewActionsUserWithTaskID(taskID), nil
|
||||||
store.GetData()["ActionsTaskID"] = taskID
|
|
||||||
return user_model.ActionsUserID
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,33 +124,27 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
|
|||||||
store.GetData()["IsApiToken"] = true
|
store.GetData()["IsApiToken"] = true
|
||||||
store.GetData()["ApiTokenScope"] = accessTokenScope
|
store.GetData()["ApiTokenScope"] = accessTokenScope
|
||||||
}
|
}
|
||||||
return uid
|
return user_model.GetUserByID(ctx, uid)
|
||||||
}
|
}
|
||||||
t, err := auth_model.GetAccessTokenBySHA(ctx, tokenSHA)
|
t, err := auth_model.GetAccessTokenBySHA(ctx, tokenSHA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if auth_model.IsErrAccessTokenNotExist(err) {
|
if auth_model.IsErrAccessTokenNotExist(err) {
|
||||||
// check task token
|
// check task token
|
||||||
task, err := actions_model.GetRunningTaskByToken(ctx, tokenSHA)
|
if task, err := actions_model.GetRunningTaskByToken(ctx, tokenSHA); err == nil {
|
||||||
if err == nil && task != nil {
|
|
||||||
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
||||||
|
return user_model.NewActionsUserWithTaskID(task.ID), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
store.GetData()["IsActionsToken"] = true
|
|
||||||
store.GetData()["ActionsTaskID"] = task.ID
|
|
||||||
|
|
||||||
return user_model.ActionsUserID
|
|
||||||
}
|
|
||||||
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
|
||||||
log.Error("GetAccessTokenBySHA: %v", err)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
t.UpdatedUnix = timeutil.TimeStampNow()
|
t.UpdatedUnix = timeutil.TimeStampNow()
|
||||||
if err = auth_model.UpdateAccessToken(ctx, t); err != nil {
|
if err = auth_model.UpdateAccessToken(ctx, t); err != nil {
|
||||||
log.Error("UpdateAccessToken: %v", err)
|
log.Error("UpdateAccessToken: %v", err)
|
||||||
}
|
}
|
||||||
store.GetData()["IsApiToken"] = true
|
store.GetData()["IsApiToken"] = true
|
||||||
store.GetData()["ApiTokenScope"] = t.Scope
|
store.GetData()["ApiTokenScope"] = t.Scope
|
||||||
return t.UID
|
return user_model.GetUserByID(ctx, t.UID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify extracts the user ID from the OAuth token in the query parameters
|
// Verify extracts the user ID from the OAuth token in the query parameters
|
||||||
@@ -173,21 +164,9 @@ func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStor
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
id := o.userIDFromToken(req.Context(), token, store)
|
user, err := o.userFromToken(req.Context(), token, store)
|
||||||
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||||
if id <= 0 && id != -2 { // -2 means actions, so we need to allow it.
|
log.Error("userFromToken: %v", err) // the callers might ignore the error, so log it here
|
||||||
return nil, user_model.ErrUserNotExist{}
|
|
||||||
}
|
}
|
||||||
log.Trace("OAuth2 Authorization: Found token for user[%d]", id)
|
return user, err
|
||||||
|
|
||||||
user, err := user_model.GetPossibleUserByID(req.Context(), id)
|
|
||||||
if err != nil {
|
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
|
||||||
log.Error("GetUserByName: %v", err)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("OAuth2 Authorization: Logged in user %-v", user)
|
|
||||||
return user, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,23 +12,26 @@ import (
|
|||||||
"code.gitea.io/gitea/services/actions"
|
"code.gitea.io/gitea/services/actions"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUserIDFromToken(t *testing.T) {
|
func TestUserIDFromToken(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
t.Run("Actions JWT", func(t *testing.T) {
|
t.Run("Actions JWT", func(t *testing.T) {
|
||||||
const RunningTaskID = 47
|
const RunningTaskID int64 = 47
|
||||||
token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
|
token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
ds := make(reqctx.ContextData)
|
ds := make(reqctx.ContextData)
|
||||||
|
|
||||||
o := OAuth2{}
|
o := OAuth2{}
|
||||||
uid := o.userIDFromToken(t.Context(), token, ds)
|
u, err := o.userFromToken(t.Context(), token, ds)
|
||||||
assert.Equal(t, user_model.ActionsUserID, uid)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, true, ds["IsActionsToken"])
|
assert.Equal(t, user_model.ActionsUserID, u.ID)
|
||||||
assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID))
|
taskID, ok := user_model.GetActionsUserTaskID(u)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, RunningTaskID, taskID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,12 +92,21 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d
|
|||||||
for _, lines := range review.CodeComments {
|
for _, lines := range review.CodeComments {
|
||||||
for _, comments := range lines {
|
for _, comments := range lines {
|
||||||
for _, comment := range comments {
|
for _, comment := range comments {
|
||||||
|
apiComments = append(apiComments, ToPullReviewComment(ctx, comment, doer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apiComments, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPullReviewComment convert a single code review comment to api format
|
||||||
|
func ToPullReviewComment(ctx context.Context, comment *issues_model.Comment, doer *user_model.User) *api.PullReviewComment {
|
||||||
apiComment := &api.PullReviewComment{
|
apiComment := &api.PullReviewComment{
|
||||||
ID: comment.ID,
|
ID: comment.ID,
|
||||||
Body: comment.Content,
|
Body: comment.Content,
|
||||||
Poster: ToUser(ctx, comment.Poster, doer),
|
Poster: ToUser(ctx, comment.Poster, doer),
|
||||||
Resolver: ToUser(ctx, comment.ResolveDoer, doer),
|
Resolver: ToUser(ctx, comment.ResolveDoer, doer),
|
||||||
ReviewID: review.ID,
|
ReviewID: comment.ReviewID,
|
||||||
Created: comment.CreatedUnix.AsTime(),
|
Created: comment.CreatedUnix.AsTime(),
|
||||||
Updated: comment.UpdatedUnix.AsTime(),
|
Updated: comment.UpdatedUnix.AsTime(),
|
||||||
Path: comment.TreePath,
|
Path: comment.TreePath,
|
||||||
@@ -105,7 +114,7 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d
|
|||||||
OrigCommitID: comment.OldRef,
|
OrigCommitID: comment.OldRef,
|
||||||
DiffHunk: patch2diff(comment.Patch),
|
DiffHunk: patch2diff(comment.Patch),
|
||||||
HTMLURL: comment.HTMLURL(ctx),
|
HTMLURL: comment.HTMLURL(ctx),
|
||||||
HTMLPullURL: review.Issue.HTMLURL(ctx),
|
HTMLPullURL: comment.Issue.HTMLURL(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
if comment.Line < 0 {
|
if comment.Line < 0 {
|
||||||
@@ -113,11 +122,8 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d
|
|||||||
} else {
|
} else {
|
||||||
apiComment.LineNum = comment.UnsignedLine()
|
apiComment.LineNum = comment.UnsignedLine()
|
||||||
}
|
}
|
||||||
apiComments = append(apiComments, apiComment)
|
|
||||||
}
|
return apiComment
|
||||||
}
|
|
||||||
}
|
|
||||||
return apiComments, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch2diff(patch string) string {
|
func patch2diff(patch string) string {
|
||||||
|
|||||||
@@ -541,8 +541,7 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho
|
|||||||
accessMode = perm_model.AccessModeWrite
|
accessMode = perm_model.AccessModeWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Data["IsActionsToken"] == true {
|
if taskID, ok := user_model.GetActionsUserTaskID(ctx.Doer); ok {
|
||||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
|
||||||
perm, err := access_model.GetActionsUserRepoPermission(ctx, repository, ctx.Doer, taskID)
|
perm, err := access_model.GetActionsUserRepoPermission(ctx, repository, ctx.Doer, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to GetActionsUserRepoPermission for task[%d] Error: %v", taskID, err)
|
log.Error("Unable to GetActionsUserRepoPermission for task[%d] Error: %v", taskID, err)
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ func MigrateRepository(ctx context.Context, doer *user_model.User, ownerName str
|
|||||||
if err1 := uploader.Rollback(); err1 != nil {
|
if err1 := uploader.Rollback(); err1 != nil {
|
||||||
log.Error("rollback failed: %v", err1)
|
log.Error("rollback failed: %v", err1)
|
||||||
}
|
}
|
||||||
if err2 := system_model.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil {
|
if err2 := system_model.CreateRepositoryNotice(fmt.Sprintf("Migrate repository (%s/%s) from %s failed: %v", ownerName, opts.RepoName, opts.OriginalURL, err)); err2 != nil {
|
||||||
log.Error("create respotiry notice failed: ", err2)
|
log.Error("create repository notice failed: ", err2)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ func pruneBrokenReferences(ctx context.Context, m *repo_model.Mirror, gitRepo gi
|
|||||||
stdoutMessage := util.SanitizeCredentialURLs(stdout)
|
stdoutMessage := util.SanitizeCredentialURLs(stdout)
|
||||||
|
|
||||||
log.Error("Failed to prune mirror repository %s references:\nStdout: %s\nStderr: %s\nErr: %v", gitRepo.RelativePath(), stdoutMessage, stderrMessage, pruneErr)
|
log.Error("Failed to prune mirror repository %s references:\nStdout: %s\nStderr: %s\nErr: %v", gitRepo.RelativePath(), stdoutMessage, stderrMessage, pruneErr)
|
||||||
desc := fmt.Sprintf("Failed to prune mirror repository %s references: %s", gitRepo.RelativePath(), stderrMessage)
|
desc := fmt.Sprintf("Failed to prune mirror repository (%s) references: %s", m.Repo.FullName(), stderrMessage)
|
||||||
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
|
|||||||
// If there is still an error (or there always was an error)
|
// If there is still an error (or there always was an error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("SyncMirrors [repo: %-v]: failed to update mirror repository:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
|
log.Error("SyncMirrors [repo: %-v]: failed to update mirror repository:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
|
||||||
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", m.Repo.RelativePath(), stderrMessage)
|
desc := fmt.Sprintf("Failed to update mirror repository (%s): %s", m.Repo.FullName(), stderrMessage)
|
||||||
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -355,7 +355,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
|
|||||||
// If there is still an error (or there always was an error)
|
// If there is still an error (or there always was an error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("SyncMirrors [repo: %-v Wiki]: failed to update mirror repository wiki:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
|
log.Error("SyncMirrors [repo: %-v Wiki]: failed to update mirror repository wiki:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
|
||||||
desc := fmt.Sprintf("Failed to update mirror repository wiki '%s': %s", m.Repo.WikiStorageRepo().RelativePath(), stderrMessage)
|
desc := fmt.Sprintf("Failed to update mirror repository wiki (%s): %s", m.Repo.FullName(), stderrMessage)
|
||||||
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -595,7 +595,7 @@ func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, re
|
|||||||
// Update the is empty and default_branch columns
|
// Update the is empty and default_branch columns
|
||||||
if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
|
if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
|
||||||
log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err)
|
log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err)
|
||||||
desc := fmt.Sprintf("Failed to update default branch of repository '%s': %v", m.Repo.RelativePath(), err)
|
desc := fmt.Sprintf("Failed to update default branch of repository (%s): %v", m.Repo.FullName(), err)
|
||||||
if err = system_model.CreateRepositoryNotice(desc); err != nil {
|
if err = system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ func NewBlobUploader(ctx context.Context, id string) (*BlobUploader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &BlobUploader{
|
return &BlobUploader{
|
||||||
model,
|
PackageBlobUpload: model,
|
||||||
hash,
|
MultiHasher: hash,
|
||||||
f,
|
file: f,
|
||||||
false,
|
reading: false,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func AdoptRepository(ctx context.Context, doer, owner *user_model.User, opts Cre
|
|||||||
// WARNING: Don't override all later err with local variables
|
// WARNING: Don't override all later err with local variables
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we can not use the ctx because it maybe canceled or timeout
|
// we can not use `ctx` because it may be canceled or timed out
|
||||||
if errDel := deleteFailedAdoptRepository(repo.ID); errDel != nil {
|
if errDel := deleteFailedAdoptRepository(repo.ID); errDel != nil {
|
||||||
log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel)
|
log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Du
|
|||||||
stdout, _, err = gitrepo.RunCmdString(ctx, repo, command)
|
stdout, _, err = gitrepo.RunCmdString(ctx, repo, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
||||||
desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RelativePath(), stdout, err)
|
desc := fmt.Sprintf("Repository garbage collection failed (%s). Stdout: %s\nError: %v", repo.FullName(), stdout, err)
|
||||||
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,7 @@ func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Du
|
|||||||
// Now update the size of the repository
|
// Now update the size of the repository
|
||||||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
||||||
log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
||||||
desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RelativePath(), stdout, err)
|
desc := fmt.Sprintf("Updating size as part of garbage collection failed (%s). Stdout: %s\nError: %v", repo.FullName(), stdout, err)
|
||||||
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error
|
|||||||
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
||||||
if err := DeleteRepositoryDirectly(ctx, repo.ID); err != nil {
|
if err := DeleteRepositoryDirectly(ctx, repo.ID); err != nil {
|
||||||
log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err)
|
log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err)
|
||||||
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository (%s) [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ func ReinitMissingRepositories(ctx context.Context) error {
|
|||||||
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
|
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
|
||||||
if err := gitrepo.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil {
|
if err := gitrepo.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil {
|
||||||
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RelativePath(), err)
|
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RelativePath(), err)
|
||||||
if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
|
if err2 := system_model.CreateRepositoryNotice("InitRepository (%s) [%d]: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err2)
|
log.Error("CreateRepositoryNotice: %v", err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,8 +265,8 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
|
|||||||
// WARNING: Don't override all later err with local variables
|
// WARNING: Don't override all later err with local variables
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we can not use the ctx because it maybe canceled or timeout
|
// we can not use `ctx` because it may be canceled or timed out
|
||||||
cleanupRepository(repo.ID)
|
cleanupRepository(repo)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -461,11 +461,11 @@ func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *r
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupRepository(repoID int64) {
|
func cleanupRepository(repo *repo_model.Repository) {
|
||||||
if errDelete := DeleteRepositoryDirectly(graceful.GetManager().ShutdownContext(), repoID); errDelete != nil {
|
ctx := graceful.GetManager().ShutdownContext()
|
||||||
|
if errDelete := DeleteRepositoryDirectly(ctx, repo.ID); errDelete != nil {
|
||||||
log.Error("cleanupRepository failed: %v", errDelete)
|
log.Error("cleanupRepository failed: %v", errDelete)
|
||||||
// add system notice
|
if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository (%s)", repo.FullName(), errDelete); err != nil {
|
||||||
if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository: %v", errDelete); err != nil {
|
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams
|
|||||||
|
|
||||||
// Remove repository files.
|
// Remove repository files.
|
||||||
if err := gitrepo.DeleteRepository(ctx, repo); err != nil {
|
if err := gitrepo.DeleteRepository(ctx, repo); err != nil {
|
||||||
desc := fmt.Sprintf("Delete repository files [%s]: %v", repo.FullName(), err)
|
desc := fmt.Sprintf("Delete repository files (%s): %v", repo.FullName(), err)
|
||||||
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams
|
|||||||
|
|
||||||
// Remove wiki files if it exists.
|
// Remove wiki files if it exists.
|
||||||
if err := gitrepo.DeleteRepository(ctx, repo.WikiStorageRepo()); err != nil {
|
if err := gitrepo.DeleteRepository(ctx, repo.WikiStorageRepo()); err != nil {
|
||||||
desc := fmt.Sprintf("Delete wiki repository files [%s]: %v", repo.FullName(), err)
|
desc := fmt.Sprintf("Delete wiki repository files (%s): %v", repo.FullName(), err)
|
||||||
// Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled
|
// Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled
|
||||||
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
|||||||
// WARNING: Don't override all later err with local variables
|
// WARNING: Don't override all later err with local variables
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we can not use the ctx because it maybe canceled or timeout
|
// we can not use `ctx` because it may be canceled or timed out
|
||||||
cleanupRepository(repo.ID)
|
cleanupRepository(repo)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
|
|||||||
// last - clean up the repository if something goes wrong
|
// last - clean up the repository if something goes wrong
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we can not use the ctx because it maybe canceled or timeout
|
// we can not use `ctx` because it may be canceled or timed out
|
||||||
cleanupRepository(generateRepo.ID)
|
cleanupRepository(generateRepo)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatte
|
|||||||
text = fmt.Sprintf("Commit Status changed: %s - %s", refLink, p.Description)
|
text = fmt.Sprintf("Commit Status changed: %s - %s", refLink, p.Description)
|
||||||
color = greenColor
|
color = greenColor
|
||||||
if withSender {
|
if withSender {
|
||||||
if user_model.IsGiteaActionsUserName(p.Sender.UserName) {
|
if user_model.GetSystemUserByName(p.Sender.UserName) != nil {
|
||||||
text += " by " + p.Sender.FullName
|
text += " by " + p.Sender.FullName
|
||||||
} else {
|
} else {
|
||||||
text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)
|
text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.DeleteRepository(ctx, repo.WikiStorageRepo()); err != nil {
|
if err := gitrepo.DeleteRepository(ctx, repo.WikiStorageRepo()); err != nil {
|
||||||
desc := fmt.Sprintf("Delete wiki repository files [%s]: %v", repo.FullName(), err)
|
desc := fmt.Sprintf("Delete wiki repository files (%s): %v", repo.FullName(), err)
|
||||||
// Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled
|
// Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled
|
||||||
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
|
|||||||
+5
-3
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-check
|
||||||
// TODO: Move to .ts after https://github.com/stylelint/stylelint/issues/8893 is fixed
|
// TODO: Move to .ts after https://github.com/stylelint/stylelint/issues/8893 is fixed
|
||||||
import {fileURLToPath} from 'node:url';
|
import {fileURLToPath} from 'node:url';
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ const cssVarFiles = [
|
|||||||
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
|
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** @type {import('stylelint').Config} */
|
||||||
export default {
|
export default {
|
||||||
extends: 'stylelint-config-recommended',
|
extends: 'stylelint-config-recommended',
|
||||||
reportUnscopedDisables: true,
|
reportUnscopedDisables: true,
|
||||||
@@ -57,14 +59,14 @@ export default {
|
|||||||
'@stylistic/block-opening-brace-space-before': 'always',
|
'@stylistic/block-opening-brace-space-before': 'always',
|
||||||
'@stylistic/color-hex-case': 'lower',
|
'@stylistic/color-hex-case': 'lower',
|
||||||
'@stylistic/declaration-bang-space-after': 'never',
|
'@stylistic/declaration-bang-space-after': 'never',
|
||||||
'@stylistic/declaration-bang-space-before': null,
|
'@stylistic/declaration-bang-space-before': 'always',
|
||||||
'@stylistic/declaration-block-semicolon-newline-after': null,
|
'@stylistic/declaration-block-semicolon-newline-after': null,
|
||||||
'@stylistic/declaration-block-semicolon-newline-before': null,
|
'@stylistic/declaration-block-semicolon-newline-before': null,
|
||||||
'@stylistic/declaration-block-semicolon-space-after': null,
|
'@stylistic/declaration-block-semicolon-space-after': null,
|
||||||
'@stylistic/declaration-block-semicolon-space-before': 'never',
|
'@stylistic/declaration-block-semicolon-space-before': 'never',
|
||||||
'@stylistic/declaration-block-trailing-semicolon': null,
|
'@stylistic/declaration-block-trailing-semicolon': null,
|
||||||
'@stylistic/declaration-colon-newline-after': null,
|
'@stylistic/declaration-colon-newline-after': null,
|
||||||
'@stylistic/declaration-colon-space-after': null,
|
'@stylistic/declaration-colon-space-after': 'always',
|
||||||
'@stylistic/declaration-colon-space-before': 'never',
|
'@stylistic/declaration-colon-space-before': 'never',
|
||||||
'@stylistic/function-comma-newline-after': null,
|
'@stylistic/function-comma-newline-after': null,
|
||||||
'@stylistic/function-comma-newline-before': null,
|
'@stylistic/function-comma-newline-before': null,
|
||||||
@@ -101,7 +103,7 @@ export default {
|
|||||||
'@stylistic/selector-attribute-operator-space-before': null,
|
'@stylistic/selector-attribute-operator-space-before': null,
|
||||||
'@stylistic/selector-combinator-space-after': null,
|
'@stylistic/selector-combinator-space-after': null,
|
||||||
'@stylistic/selector-combinator-space-before': null,
|
'@stylistic/selector-combinator-space-before': null,
|
||||||
'@stylistic/selector-descendant-combinator-no-non-space': null,
|
'@stylistic/selector-descendant-combinator-no-non-space': true,
|
||||||
'@stylistic/selector-list-comma-newline-after': null,
|
'@stylistic/selector-list-comma-newline-after': null,
|
||||||
'@stylistic/selector-list-comma-newline-before': null,
|
'@stylistic/selector-list-comma-newline-before': null,
|
||||||
'@stylistic/selector-list-comma-space-after': 'always-single-line',
|
'@stylistic/selector-list-comma-space-after': 'always-single-line',
|
||||||
|
|||||||
Generated
+104
-4
@@ -13707,6 +13707,106 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/pulls/comments/{id}/resolve": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Resolve a pull request review comment",
|
||||||
|
"operationId": "repoResolvePullReviewComment",
|
||||||
|
"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": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "id of the review comment",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/pulls/comments/{id}/unresolve": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Unresolve a pull request review comment",
|
||||||
|
"operationId": "repoUnresolvePullReviewComment",
|
||||||
|
"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": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "id of the review comment",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/pulls/pinned": {
|
"/repos/{owner}/{repo}/pulls/pinned": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -23536,7 +23636,7 @@
|
|||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
"CreatePullReviewOptions": {
|
"CreatePullReviewOptions": {
|
||||||
"description": "CreatePullReviewOptions are options to create a pull review",
|
"description": "CreatePullReviewOptions are options to create a pull request review",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"body": {
|
"body": {
|
||||||
@@ -24133,7 +24233,7 @@
|
|||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
"DismissPullReviewOptions": {
|
"DismissPullReviewOptions": {
|
||||||
"description": "DismissPullReviewOptions are options to dismiss a pull review",
|
"description": "DismissPullReviewOptions are options to dismiss a pull request review",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"message": {
|
"message": {
|
||||||
@@ -27645,7 +27745,7 @@
|
|||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
"PullReviewRequestOptions": {
|
"PullReviewRequestOptions": {
|
||||||
"description": "PullReviewRequestOptions are options to add or remove pull review requests",
|
"description": "PullReviewRequestOptions are options to add or remove pull request review requests",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"reviewers": {
|
"reviewers": {
|
||||||
@@ -28389,7 +28489,7 @@
|
|||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
"SubmitPullReviewOptions": {
|
"SubmitPullReviewOptions": {
|
||||||
"description": "SubmitPullReviewOptions are options to submit a pending pull review",
|
"description": "SubmitPullReviewOptions are options to submit a pending pull request review",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"body": {
|
"body": {
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -362,6 +364,79 @@ func TestAPIPullReviewRequest(t *testing.T) {
|
|||||||
MakeRequest(t, req, http.StatusNoContent)
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIPullReviewCommentResolveEndpoints(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
ctx := t.Context()
|
||||||
|
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||||
|
require.NoError(t, pullIssue.LoadAttributes(ctx))
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
|
||||||
|
|
||||||
|
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: pullIssue.PosterID})
|
||||||
|
require.NoError(t, pullIssue.LoadPullRequest(ctx))
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
latestCommitID, err := gitRepo.GetRefCommitID(pullIssue.PullRequest.GetGitHeadRefName())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
codeComment, err := pull_service.CreateCodeComment(ctx, doer, gitRepo, pullIssue, 1, "resolve comment", "README.md", false, 0, latestCommitID, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, codeComment)
|
||||||
|
|
||||||
|
session := loginUser(t, doer.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
resolveURL := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/resolve", repo.OwnerName, repo.Name, codeComment.ID)
|
||||||
|
unresolveURL := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/unresolve", repo.OwnerName, repo.Name, codeComment.ID)
|
||||||
|
|
||||||
|
req := NewRequest(t, http.MethodPost, resolveURL).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Verify comment is resolved
|
||||||
|
updatedComment, err := issues_model.GetCommentByID(ctx, codeComment.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotZero(t, updatedComment.ResolveDoerID)
|
||||||
|
assert.Equal(t, doer.ID, updatedComment.ResolveDoerID)
|
||||||
|
|
||||||
|
// Resolving again should be idempotent
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
req = NewRequest(t, http.MethodPost, unresolveURL).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Verify comment is unresolved
|
||||||
|
updatedComment, err = issues_model.GetCommentByID(ctx, codeComment.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Zero(t, updatedComment.ResolveDoerID)
|
||||||
|
|
||||||
|
// Unresolving again should be idempotent
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Non-existing comment ID
|
||||||
|
req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/999999/resolve", repo.OwnerName, repo.Name)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
// Non-code-comment
|
||||||
|
plainComment, err := issue_service.CreateIssueComment(ctx, doer, repo, pullIssue, "not a review comment", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/resolve", repo.OwnerName, repo.Name, plainComment.ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
|
||||||
|
// Test permission check: use a user without write access for target repo to test 403 response
|
||||||
|
unauthorizedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||||
|
require.NotEqual(t, pullIssue.PosterID, unauthorizedUser.ID)
|
||||||
|
|
||||||
|
unauthorizedSession := loginUser(t, unauthorizedUser.Name)
|
||||||
|
unauthorizedToken := getTokenForLoggedInUser(t, unauthorizedSession, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
req = NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d", repo.OwnerName, repo.Name, plainComment.ID)).AddTokenAuth(unauthorizedToken)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
req = NewRequest(t, http.MethodPost, resolveURL).AddTokenAuth(unauthorizedToken)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIPullReviewStayDismissed(t *testing.T) {
|
func TestAPIPullReviewStayDismissed(t *testing.T) {
|
||||||
// This test against issue https://github.com/go-gitea/gitea/issues/28542
|
// This test against issue https://github.com/go-gitea/gitea/issues/28542
|
||||||
// where old reviews surface after a review request got dismissed.
|
// where old reviews surface after a review request got dismissed.
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext", "webworker"],
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|||||||
Vendored
+87
@@ -2,18 +2,105 @@ declare module '@techknowlogick/license-checker-webpack-plugin' {
|
|||||||
const plugin: any;
|
const plugin: any;
|
||||||
export = plugin;
|
export = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'eslint-plugin-no-use-extend-native' {
|
declare module 'eslint-plugin-no-use-extend-native' {
|
||||||
import type {Eslint} from 'eslint';
|
import type {Eslint} from 'eslint';
|
||||||
const plugin: Eslint.Plugin;
|
const plugin: Eslint.Plugin;
|
||||||
export = plugin;
|
export = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'eslint-plugin-array-func' {
|
declare module 'eslint-plugin-array-func' {
|
||||||
import type {Eslint} from 'eslint';
|
import type {Eslint} from 'eslint';
|
||||||
const plugin: Eslint.Plugin;
|
const plugin: Eslint.Plugin;
|
||||||
export = plugin;
|
export = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'eslint-plugin-github' {
|
declare module 'eslint-plugin-github' {
|
||||||
import type {Eslint} from 'eslint';
|
import type {Eslint} from 'eslint';
|
||||||
const plugin: Eslint.Plugin;
|
const plugin: Eslint.Plugin;
|
||||||
export = plugin;
|
export = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '*.svg' {
|
||||||
|
const value: string;
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.css' {
|
||||||
|
const value: string;
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type {DefineComponent} from 'vue';
|
||||||
|
const component: DefineComponent<unknown, unknown, any>;
|
||||||
|
export default component;
|
||||||
|
// Here we declare all exports from vue files so `tsc` or `tsgo` can work for
|
||||||
|
// non-vue files. To lint .vue files, `vue-tsc` must be used.
|
||||||
|
export function initDashboardRepoList(): void;
|
||||||
|
export function initRepositoryActionView(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'htmx.org/dist/htmx.esm.js' {
|
||||||
|
const value = await import('htmx.org');
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
|
||||||
|
const value = await import('swagger-ui-dist');
|
||||||
|
export default value.SwaggerUIBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'asciinema-player' {
|
||||||
|
interface AsciinemaPlayer {
|
||||||
|
create(src: string, element: HTMLElement, options?: Record<string, unknown>): void;
|
||||||
|
}
|
||||||
|
const exports: AsciinemaPlayer;
|
||||||
|
export = exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@citation-js/core' {
|
||||||
|
export class Cite {
|
||||||
|
constructor(data: string);
|
||||||
|
format(format: string, options?: Record<string, any>): string;
|
||||||
|
}
|
||||||
|
export const plugins: {
|
||||||
|
config: {
|
||||||
|
get(name: string): any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@citation-js/plugin-software-formats' {}
|
||||||
|
declare module '@citation-js/plugin-bibtex' {}
|
||||||
|
declare module '@citation-js/plugin-csl' {}
|
||||||
|
|
||||||
|
declare module 'vue-bar-graph' {
|
||||||
|
import type {DefineComponent} from 'vue';
|
||||||
|
|
||||||
|
interface BarGraphPoint {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VueBarGraph: DefineComponent<{
|
||||||
|
points?: Array<BarGraphPoint>;
|
||||||
|
barColor?: string;
|
||||||
|
textColor?: string;
|
||||||
|
textAltColor?: string;
|
||||||
|
height?: number;
|
||||||
|
labelHeight?: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@mcaptcha/vanilla-glue' {
|
||||||
|
export let INPUT_NAME: string;
|
||||||
|
export default class Widget {
|
||||||
|
constructor(options: {
|
||||||
|
siteKey: {
|
||||||
|
instanceUrl: URL;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ export default {
|
|||||||
'@mcaptcha/vanilla-glue', // breaking changes in rc versions need to be handled
|
'@mcaptcha/vanilla-glue', // breaking changes in rc versions need to be handled
|
||||||
'cropperjs', // need to migrate to v2 but v2 is not compatible with v1
|
'cropperjs', // need to migrate to v2 but v2 is not compatible with v1
|
||||||
'tailwindcss', // need to migrate
|
'tailwindcss', // need to migrate
|
||||||
|
'@eslint/json', // needs eslint 10
|
||||||
],
|
],
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
|||||||
@@ -80,13 +80,12 @@ function initGlobalErrorHandler() {
|
|||||||
// we added an event handler for window error at the very beginning of <script> of page head the
|
// we added an event handler for window error at the very beginning of <script> of page head the
|
||||||
// handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before
|
// handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before
|
||||||
// this init then in this init, we can collect all error events and show them.
|
// this init then in this init, we can collect all error events and show them.
|
||||||
for (const e of window._globalHandlerErrors || []) {
|
for (const e of (window._globalHandlerErrors as Iterable<ErrorEvent & PromiseRejectionEvent>) || []) {
|
||||||
processWindowErrorEvent(e);
|
processWindowErrorEvent(e);
|
||||||
}
|
}
|
||||||
// then, change _globalHandlerErrors to an object with push method, to process further error
|
// then, change _globalHandlerErrors to an object with push method, to process further error
|
||||||
// events directly
|
// events directly
|
||||||
// @ts-expect-error -- this should be refactored to not use a fake array
|
window._globalHandlerErrors = {_inited: true, push: (e: ErrorEvent & PromiseRejectionEvent) => processWindowErrorEvent(e)} as any;
|
||||||
window._globalHandlerErrors = {_inited: true, push: (e: ErrorEvent & PromiseRejectionEvent) => processWindowErrorEvent(e)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initGlobalErrorHandler();
|
initGlobalErrorHandler();
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {localUserSettings} from '../modules/user-settings.ts';
|
|||||||
// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
|
// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
|
||||||
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
|
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
|
||||||
|
|
||||||
|
type StepContainerElement = HTMLElement & {_stepLogsActiveContainer?: HTMLElement}
|
||||||
|
|
||||||
type LogLine = {
|
type LogLine = {
|
||||||
index: number;
|
index: number;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
@@ -221,19 +223,18 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// get the job step logs container ('.job-step-logs')
|
// get the job step logs container ('.job-step-logs')
|
||||||
getJobStepLogsContainer(stepIndex: number): HTMLElement {
|
getJobStepLogsContainer(stepIndex: number): StepContainerElement {
|
||||||
return (this.$refs.logs as any)[stepIndex];
|
return (this.$refs.logs as any)[stepIndex];
|
||||||
},
|
},
|
||||||
|
|
||||||
// get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
|
// get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
|
||||||
getActiveLogsContainer(stepIndex: number): HTMLElement {
|
getActiveLogsContainer(stepIndex: number): StepContainerElement {
|
||||||
const el = this.getJobStepLogsContainer(stepIndex);
|
const el = this.getJobStepLogsContainer(stepIndex);
|
||||||
// @ts-expect-error - _stepLogsActiveContainer is a custom property
|
|
||||||
return el._stepLogsActiveContainer ?? el;
|
return el._stepLogsActiveContainer ?? el;
|
||||||
},
|
},
|
||||||
// begin a log group
|
// begin a log group
|
||||||
beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
|
beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
|
||||||
const el = (this.$refs.logs as any)[stepIndex];
|
const el = (this.$refs.logs as any)[stepIndex] as StepContainerElement;
|
||||||
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
|
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
|
||||||
this.createLogLine(stepIndex, startTime, {
|
this.createLogLine(stepIndex, startTime, {
|
||||||
index: line.index,
|
index: line.index,
|
||||||
@@ -395,7 +396,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// auto-scroll to the last log line of the last step
|
// auto-scroll to the last log line of the last step
|
||||||
let autoScrollJobStepElement: HTMLElement | undefined;
|
let autoScrollJobStepElement: StepContainerElement | undefined;
|
||||||
for (let stepIndex = 0; stepIndex < this.currentJob.steps.length; stepIndex++) {
|
for (let stepIndex = 0; stepIndex < this.currentJob.steps.length; stepIndex++) {
|
||||||
if (!autoScrollStepIndexes.get(stepIndex)) continue;
|
if (!autoScrollStepIndexes.get(stepIndex)) continue;
|
||||||
autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex);
|
autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// @ts-expect-error - module exports no types
|
|
||||||
import {VueBarGraph} from 'vue-bar-graph';
|
import {VueBarGraph} from 'vue-bar-graph';
|
||||||
import {computed, onMounted, shallowRef, useTemplateRef, type ShallowRef} from 'vue';
|
import {computed, onMounted, shallowRef, useTemplateRef, type ShallowRef} from 'vue';
|
||||||
|
|
||||||
|
|||||||
@@ -155,9 +155,8 @@ export default defineComponent({
|
|||||||
return -1;
|
return -1;
|
||||||
},
|
},
|
||||||
getActiveItem() {
|
getActiveItem() {
|
||||||
const el = this.$refs[`listItem${this.activeItemIndex}`];
|
const el = this.$refs[`listItem${this.activeItemIndex}`] as Array<HTMLDivElement>;
|
||||||
// @ts-expect-error - el is unknown type
|
return el?.length ? el[0] : null;
|
||||||
return (el && el.length) ? el[0] : null;
|
|
||||||
},
|
},
|
||||||
keydown(e: KeyboardEvent) {
|
keydown(e: KeyboardEvent) {
|
||||||
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||||
@@ -174,7 +173,7 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.activeItemIndex = nextIndex;
|
this.activeItemIndex = nextIndex;
|
||||||
this.getActiveItem().scrollIntoView({block: 'nearest'});
|
this.getActiveItem()!.scrollIntoView({block: 'nearest'});
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.getActiveItem()?.click();
|
this.getActiveItem()?.click();
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ const customEventListener: Plugin = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type LineOptions = ChartOptions<'line'> & {
|
||||||
|
plugins?: {
|
||||||
|
customEventListener?: {
|
||||||
|
chartType: string;
|
||||||
|
instance: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Chart.defaults.color = chartJsColors.text;
|
Chart.defaults.color = chartJsColors.text;
|
||||||
Chart.defaults.borderColor = chartJsColors.border;
|
Chart.defaults.borderColor = chartJsColors.border;
|
||||||
|
|
||||||
@@ -251,7 +260,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getOptions(type: string): ChartOptions<'line'> {
|
getOptions(type: string): LineOptions {
|
||||||
return {
|
return {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
@@ -264,7 +273,6 @@ export default defineComponent({
|
|||||||
position: 'top',
|
position: 'top',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
// @ts-expect-error: bug in chart.js types
|
|
||||||
customEventListener: {
|
customEventListener: {
|
||||||
chartType: type,
|
chartType: type,
|
||||||
instance: this,
|
instance: this,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
TimeScale,
|
TimeScale,
|
||||||
type ChartOptions,
|
type ChartOptions,
|
||||||
type ChartData,
|
type ChartData,
|
||||||
|
type ChartDataset,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import {GET} from '../modules/fetch.ts';
|
import {GET} from '../modules/fetch.ts';
|
||||||
import {Bar} from 'vue-chartjs';
|
import {Bar} from 'vue-chartjs';
|
||||||
@@ -83,13 +84,12 @@ function toGraphData(data: DayData[]): ChartData<'bar'> {
|
|||||||
return {
|
return {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
// @ts-expect-error -- bar chart expects one-dimensional data, but apparently x/y still works
|
|
||||||
data: data.map((i) => ({x: i.week, y: i.commits})),
|
data: data.map((i) => ({x: i.week, y: i.commits})),
|
||||||
label: 'Commits',
|
label: 'Commits',
|
||||||
backgroundColor: chartJsColors['commits'],
|
backgroundColor: chartJsColors['commits'],
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
},
|
} as unknown as ChartDataset<'bar'>,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export async function initCaptcha() {
|
|||||||
// * the INPUT_NAME is a "const", it should not be changed.
|
// * the INPUT_NAME is a "const", it should not be changed.
|
||||||
// * the "mCaptcha.default" is actually the "Widget".
|
// * the "mCaptcha.default" is actually the "Widget".
|
||||||
|
|
||||||
// @ts-expect-error TS2540: Cannot assign to 'INPUT_NAME' because it is a read-only property.
|
|
||||||
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
||||||
const instanceURL = captchaEl.getAttribute('data-instance-url')!;
|
const instanceURL = captchaEl.getAttribute('data-instance-url')!;
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,9 @@ const {pageData} = window.config;
|
|||||||
|
|
||||||
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
|
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
|
||||||
const [{Cite, plugins}] = await Promise.all([
|
const [{Cite, plugins}] = await Promise.all([
|
||||||
// @ts-expect-error: module exports no types
|
|
||||||
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
|
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
|
||||||
// @ts-expect-error: module exports no types
|
|
||||||
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
|
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
|
||||||
// @ts-expect-error: module exports no types
|
|
||||||
import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'),
|
import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'),
|
||||||
// @ts-expect-error: module exports no types
|
|
||||||
import(/* webpackChunkName: "citation-js-csl" */'@citation-js/plugin-csl'),
|
import(/* webpackChunkName: "citation-js-csl" */'@citation-js/plugin-csl'),
|
||||||
]);
|
]);
|
||||||
const {citationFileContent} = pageData;
|
const {citationFileContent} = pageData;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const baseOptions: MonacoOpts = {
|
|||||||
renderLineHighlight: 'all',
|
renderLineHighlight: 'all',
|
||||||
renderLineHighlightOnlyWhenFocus: true,
|
renderLineHighlightOnlyWhenFocus: true,
|
||||||
rulers: [],
|
rulers: [],
|
||||||
scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6},
|
scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6, alwaysConsumeMouseWheel: false},
|
||||||
scrollBeyondLastLine: false,
|
scrollBeyondLastLine: false,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
wrappingIndent: 'none',
|
wrappingIndent: 'none',
|
||||||
|
|||||||
@@ -72,10 +72,9 @@ class Source {
|
|||||||
const sourcesByUrl = new Map<string, Source | null>();
|
const sourcesByUrl = new Map<string, Source | null>();
|
||||||
const sourcesByPort = new Map<MessagePort, Source | null>();
|
const sourcesByPort = new Map<MessagePort, Source | null>();
|
||||||
|
|
||||||
// @ts-expect-error: typescript bug?
|
(self as unknown as SharedWorkerGlobalScope).addEventListener('connect', (e: MessageEvent) => {
|
||||||
self.addEventListener('connect', (e: MessageEvent) => {
|
|
||||||
for (const port of e.ports) {
|
for (const port of e.ports) {
|
||||||
port.addEventListener('message', (event) => {
|
port.addEventListener('message', (event: MessageEvent) => {
|
||||||
if (!self.EventSource) {
|
if (!self.EventSource) {
|
||||||
// some browsers (like PaleMoon, Firefox<53) don't support EventSource in SharedWorkerGlobalScope.
|
// some browsers (like PaleMoon, Firefox<53) don't support EventSource in SharedWorkerGlobalScope.
|
||||||
// this event handler needs EventSource when doing "new Source(url)", so just post a message back to the caller,
|
// this event handler needs EventSource when doing "new Source(url)", so just post a message back to the caller,
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ function initRepoDiffConversationForm() {
|
|||||||
const idx = newConversationHolder.getAttribute('data-idx');
|
const idx = newConversationHolder.getAttribute('data-idx');
|
||||||
|
|
||||||
form.closest('.conversation-holder')!.replaceWith(newConversationHolder);
|
form.closest('.conversation-holder')!.replaceWith(newConversationHolder);
|
||||||
// @ts-expect-error -- prevent further usage of the form because it should have been replaced
|
(form as any) = null; // prevent further usage of the form because it should have been replaced
|
||||||
form = null;
|
|
||||||
|
|
||||||
if (trLineType) {
|
if (trLineType) {
|
||||||
// if there is a line-type for the "tr", it means the form is on the diff page
|
// if there is a line-type for the "tr", it means the form is on the diff page
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ async function pinMoveEnd(e: SortableEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initIssuePinSort() {
|
async function initIssuePinSort() {
|
||||||
const pinDiv = document.querySelector('#issue-pins');
|
const pinDiv = document.querySelector<HTMLElement>('#issue-pins');
|
||||||
|
|
||||||
if (pinDiv === null) return;
|
if (pinDiv === null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<voi
|
|||||||
|
|
||||||
async function initRepoProjectSortable(): Promise<void> {
|
async function initRepoProjectSortable(): Promise<void> {
|
||||||
// the HTML layout is: #project-board.board > .project-column .cards > .issue-card
|
// the HTML layout is: #project-board.board > .project-column .cards > .issue-card
|
||||||
const mainBoard = document.querySelector('#project-board')!;
|
const mainBoard = document.querySelector<HTMLElement>('#project-board')!;
|
||||||
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
||||||
createSortable(mainBoard, {
|
createSortable(mainBoard, {
|
||||||
group: 'project-column',
|
group: 'project-column',
|
||||||
@@ -67,7 +67,7 @@ async function initRepoProjectSortable(): Promise<void> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const boardColumn of boardColumns) {
|
for (const boardColumn of boardColumns) {
|
||||||
const boardCardList = boardColumn.querySelector('.cards')!;
|
const boardCardList = boardColumn.querySelector<HTMLElement>('.cards')!;
|
||||||
createSortable(boardCardList, {
|
createSortable(boardCardList, {
|
||||||
group: 'shared',
|
group: 'shared',
|
||||||
onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
|
onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
|
||||||
|
|||||||
@@ -56,12 +56,11 @@ describe('Repository Branch Settings', () => {
|
|||||||
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
||||||
|
|
||||||
// Mock createSortable to capture and execute the onEnd callback
|
// Mock createSortable to capture and execute the onEnd callback
|
||||||
vi.mocked(createSortable).mockImplementation(async (_el: Element, options: SortableOptions | undefined) => {
|
vi.mocked(createSortable).mockImplementation(async (_el: HTMLElement, options: SortableOptions | undefined) => {
|
||||||
if (options?.onEnd) {
|
if (options?.onEnd) {
|
||||||
options.onEnd(new Event('SortableEvent') as SortableEvent);
|
options.onEnd(new Event('SortableEvent') as SortableEvent);
|
||||||
}
|
}
|
||||||
// @ts-expect-error: mock is incomplete
|
return {destroy: vi.fn()} as unknown as Sortable;
|
||||||
return {destroy: vi.fn()} as Sortable;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
initRepoSettingsBranchesDrag();
|
initRepoSettingsBranchesDrag();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {showErrorToast} from '../modules/toast.ts';
|
|||||||
import {queryElemChildren} from '../utils/dom.ts';
|
import {queryElemChildren} from '../utils/dom.ts';
|
||||||
|
|
||||||
export function initRepoSettingsBranchesDrag() {
|
export function initRepoSettingsBranchesDrag() {
|
||||||
const protectedBranchesList = document.querySelector('#protected-branches-list');
|
const protectedBranchesList = document.querySelector<HTMLElement>('#protected-branches-list');
|
||||||
if (!protectedBranchesList) return;
|
if (!protectedBranchesList) return;
|
||||||
|
|
||||||
createSortable(protectedBranchesList, {
|
createSortable(protectedBranchesList, {
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import {emojiKeys, emojiHTML, emojiString} from './emoji.ts';
|
import {emojiKeys, emojiHTML, emojiString} from './emoji.ts';
|
||||||
import {html, htmlRaw} from '../utils/html.ts';
|
import {html, htmlRaw} from '../utils/html.ts';
|
||||||
|
import type {TributeCollection} from 'tributejs';
|
||||||
type TributeItem = Record<string, any>;
|
|
||||||
|
|
||||||
export async function attachTribute(element: HTMLElement) {
|
export async function attachTribute(element: HTMLElement) {
|
||||||
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
|
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
|
||||||
|
|
||||||
const collections = [
|
const emojiCollection: TributeCollection<string> = { // emojis
|
||||||
{ // emojis
|
|
||||||
trigger: ':',
|
trigger: ':',
|
||||||
requireLeadingSpace: true,
|
requireLeadingSpace: true,
|
||||||
values: (query: string, cb: (matches: Array<string>) => void) => {
|
values: (query: string, cb: (matches: Array<string>) => void) => {
|
||||||
@@ -20,18 +18,20 @@ export async function attachTribute(element: HTMLElement) {
|
|||||||
}
|
}
|
||||||
cb(matches);
|
cb(matches);
|
||||||
},
|
},
|
||||||
lookup: (item: TributeItem) => item,
|
lookup: (item) => item,
|
||||||
selectTemplate: (item: TributeItem) => {
|
selectTemplate: (item) => {
|
||||||
if (item === undefined) return null;
|
if (item === undefined) return '';
|
||||||
return emojiString(item.original);
|
return emojiString(item.original) ?? '';
|
||||||
},
|
},
|
||||||
menuItemTemplate: (item: TributeItem) => {
|
menuItemTemplate: (item) => {
|
||||||
return html`<div class="tribute-item">${htmlRaw(emojiHTML(item.original))}<span>${item.original}</span></div>`;
|
return html`<div class="tribute-item">${htmlRaw(emojiHTML(item.original))}<span>${item.original}</span></div>`;
|
||||||
},
|
},
|
||||||
}, { // mentions
|
};
|
||||||
|
|
||||||
|
const mentionCollection: TributeCollection<Record<string, any>> = {
|
||||||
values: window.config.mentionValues,
|
values: window.config.mentionValues,
|
||||||
requireLeadingSpace: true,
|
requireLeadingSpace: true,
|
||||||
menuItemTemplate: (item: TributeItem) => {
|
menuItemTemplate: (item) => {
|
||||||
const fullNameHtml = item.original.fullname && item.original.fullname !== '' ? html`<span class="fullname">${item.original.fullname}</span>` : '';
|
const fullNameHtml = item.original.fullname && item.original.fullname !== '' ? html`<span class="fullname">${item.original.fullname}</span>` : '';
|
||||||
return html`
|
return html`
|
||||||
<div class="tribute-item">
|
<div class="tribute-item">
|
||||||
@@ -41,11 +41,12 @@ export async function attachTribute(element: HTMLElement) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
];
|
|
||||||
|
|
||||||
// @ts-expect-error TS2351: This expression is not constructable (strange, why)
|
const tribute = new Tribute({
|
||||||
const tribute = new Tribute({collection: collections, noMatchTemplate: ''});
|
collection: [emojiCollection as TributeCollection<any>, mentionCollection],
|
||||||
|
noMatchTemplate: () => '',
|
||||||
|
});
|
||||||
tribute.attach(element);
|
tribute.attach(element);
|
||||||
return tribute;
|
return tribute;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-30
@@ -1,33 +1,3 @@
|
|||||||
declare module '*.svg' {
|
|
||||||
const value: string;
|
|
||||||
export default value;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.css' {
|
|
||||||
const value: string;
|
|
||||||
export default value;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.vue' {
|
|
||||||
import type {DefineComponent} from 'vue';
|
|
||||||
const component: DefineComponent<unknown, unknown, any>;
|
|
||||||
export default component;
|
|
||||||
// Here we declare all exports from vue files so `tsc` or `tsgo` can work for
|
|
||||||
// non-vue files. To lint .vue files, `vue-tsc` must be used.
|
|
||||||
export function initDashboardRepoList(): void;
|
|
||||||
export function initRepositoryActionView(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'htmx.org/dist/htmx.esm.js' {
|
|
||||||
const value = await import('htmx.org');
|
|
||||||
export default value;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
|
|
||||||
const value = await import('swagger-ui-dist');
|
|
||||||
export default value.SwaggerUIBundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface JQuery {
|
interface JQuery {
|
||||||
areYouSure: any, // jquery.are-you-sure
|
areYouSure: any, // jquery.are-you-sure
|
||||||
fomanticExt: any; // fomantic extension
|
fomanticExt: any; // fomantic extension
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import {queryElems} from '../utils/dom.ts';
|
|||||||
export async function initMarkupRenderAsciicast(elMarkup: HTMLElement): Promise<void> {
|
export async function initMarkupRenderAsciicast(elMarkup: HTMLElement): Promise<void> {
|
||||||
queryElems(elMarkup, '.asciinema-player-container', async (el) => {
|
queryElems(elMarkup, '.asciinema-player-container', async (el) => {
|
||||||
const [player] = await Promise.all([
|
const [player] = await Promise.all([
|
||||||
// @ts-expect-error: module exports no types
|
|
||||||
import(/* webpackChunkName: "asciinema-player" */'asciinema-player'),
|
import(/* webpackChunkName: "asciinema-player" */'asciinema-player'),
|
||||||
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
|
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
player.create(el.getAttribute('data-asciinema-player-src'), el, {
|
player.create(el.getAttribute('data-asciinema-player-src')!, el, {
|
||||||
// poster (a preview frame) to display until the playback is started.
|
// poster (a preview frame) to display until the playback is started.
|
||||||
// Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more.
|
// Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more.
|
||||||
poster: 'npt:1:0:0',
|
poster: 'npt:1:0:0',
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type {SortableOptions, SortableEvent} from 'sortablejs';
|
import type {SortableOptions, SortableEvent} from 'sortablejs';
|
||||||
import type SortableType from 'sortablejs';
|
import type SortableType from 'sortablejs';
|
||||||
|
|
||||||
export async function createSortable(el: Element, opts: {handle?: string} & SortableOptions = {}): Promise<SortableType> {
|
export async function createSortable(el: HTMLElement, opts: {handle?: string} & SortableOptions = {}): Promise<SortableType> {
|
||||||
// @ts-expect-error: wrong type derived by typescript
|
// type reassigned because typescript derives the wrong type from this import
|
||||||
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');
|
const {Sortable} = (await import(/* webpackChunkName: "sortablejs" */'sortablejs') as unknown as {Sortable: typeof SortableType});
|
||||||
|
|
||||||
return new Sortable(el, {
|
return new Sortable(el, {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ try {
|
|||||||
new Intl.NumberFormat('en', {style: 'unit', unit: 'minute'}).format(1);
|
new Intl.NumberFormat('en', {style: 'unit', unit: 'minute'}).format(1);
|
||||||
} catch {
|
} catch {
|
||||||
const intlNumberFormat = Intl.NumberFormat;
|
const intlNumberFormat = Intl.NumberFormat;
|
||||||
// @ts-expect-error - polyfill is incomplete
|
|
||||||
Intl.NumberFormat = function(locales: string | string[], options: Intl.NumberFormatOptions) {
|
Intl.NumberFormat = function(locales: string | string[], options: Intl.NumberFormatOptions) {
|
||||||
if (options.style === 'unit') {
|
if (options.style === 'unit') {
|
||||||
return {
|
return {
|
||||||
format(value: number | bigint | string) {
|
format(value: number | bigint | string) {
|
||||||
return ` ${value} ${options.unit}`;
|
return ` ${value} ${options.unit}`;
|
||||||
},
|
},
|
||||||
};
|
} as Intl.NumberFormat;
|
||||||
}
|
}
|
||||||
return intlNumberFormat(locales, options);
|
return intlNumberFormat(locales, options);
|
||||||
};
|
} as unknown as typeof Intl.NumberFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function weakRefClass() {
|
export function weakRefClass() {
|
||||||
|
|||||||
Reference in New Issue
Block a user