Compare commits

...

59 Commits

Author SHA1 Message Date
xxq250 86dfe703f8 Merge pull request '20250225版本' (#9) from develop into master 2025-02-25 11:05:18 +08:00
yystopf 85e8af6e03 feat: .devops/test.yml 2024-12-05 10:00:31 +08:00
yystopf 6dc20c68a1 更改: 合并compare接口 2024-11-18 17:09:30 +08:00
yystopf 0bfffa0ad8 更改:compare接口移除diff内容 2024-11-14 16:09:07 +08:00
yystopf 9a25439e7a fixed 2024-11-14 14:58:16 +08:00
xxq250 e4f9b9f90c raw下载原文件需要登录 2024-11-11 11:24:15 +08:00
yystopf 7cc49acd7e 更改: 返回旧版参数 2024-11-11 10:50:51 +08:00
yystopf 5a5c6d062f 新增: commit\compare文件变动接口拆解 2024-11-07 13:53:44 +08:00
yystopf 123bc33bfa 新增: 根据filepath获取合并请求文件更改内容 2024-11-05 16:11:31 +08:00
yystopf cfd6d422ea 更改:编译前置参数 2024-10-28 09:48:05 +08:00
yystopf ec579df59c 新增:nacos服务注册及配置化 2024-09-29 16:45:00 +08:00
yystopf e9762f8cb3 新增:nacos服务注册及配置化 2024-09-29 16:33:55 +08:00
yystopf 69dc5fc1a5 add: 新增只查询wikiname的接口 2024-09-25 16:31:21 +08:00
xxq250 fd66ed0d8d Merge remote-tracking branch 'origin/develop' into develop 2024-09-13 10:20:53 +08:00
xxq250 1a800569c4 增加麒麟如意开源协议 2024-09-13 10:20:46 +08:00
yystopf 1ffca888e6 新增:模板配置文件 2024-09-03 14:42:13 +08:00
xxq250 5be5aa79ab windows Git Credential Manager安装后clone时浏览器弹窗屏蔽 2024-07-24 08:36:10 +08:00
yystopf 97405de7d5 新增:删除接口(更改了关联删除hook_task逻辑) 2024-07-22 15:40:58 +08:00
yystopf f58cdb13a0 新增:手动触发流水线任务接口 2024-07-10 17:45:19 +08:00
yystopf b04f4bb2d8 更改:rerun逻辑新增一条记录 2024-05-30 16:00:19 +08:00
yystopf 16c1bb3f82 Merge branch 'develop' of https://gitlink.org.cn/Gitlink/gitea_hat into develop 2024-05-28 09:02:07 +08:00
yystopf 8964eff31b 新增:重新运行及查看日志接口 2024-05-28 09:02:01 +08:00
xxq250 01725989ac 版本号定义 2024-05-27 11:40:01 +08:00
yystopf 8ad0cd1cf0 Merge branch 'feature' into develop 2024-04-17 08:48:48 +08:00
yystopf ef52417694 新增:webhook type 类型 2024-04-17 08:48:28 +08:00
yystopf f6aa75e5c6 Merge branch 'feature' into develop 2024-03-22 17:36:12 +08:00
yystopf c88506a8f7 更改:同步分支条件 2024-03-22 17:35:58 +08:00
yystopf 4b8ba4576e Merge branch 'feature' into develop 2024-03-22 17:15:53 +08:00
yystopf 54c458b31c 新增:同步仓库分支代码恢复 2024-03-22 17:15:39 +08:00
yystopf f6d3f4d21a Merge branch 'feature' into develop 2024-03-22 13:43:24 +08:00
yystopf 0f9a0a12cd 新增:最近提交列表message搜索 2024-03-20 09:09:20 +08:00
yystopf 9d83ff8685 修复:分支数为0不用读cache 2024-03-18 15:57:48 +08:00
yystopf c04df49a78 Merge branch 'develop' 2024-03-05 14:41:24 +08:00
yystopf a0bdd64714 Merge branch 'feature' into develop 2024-03-05 09:52:39 +08:00
yystopf 0333de46ce 新增:流水线接口代码 2024-03-04 11:21:57 +08:00
yystopf 520306d632 新增:最近提交列表接口 2024-01-30 14:48:54 +08:00
yystopf 8f0e78026d 新增:分支查询删除分支与普通分支 2024-01-05 13:55:20 +08:00
yystopf cf8779216a 新增:分支列表显示已删除分支
新增:恢复分支接口

新增:删除者信息
2024-01-03 15:13:49 +08:00
yystopf d752b6126d 修复:filecommit使用内置函数移除原来的函数 2023-12-13 11:32:46 +08:00
yystopf ee82334a67 新增:dockerfile 2023-12-12 10:16:43 +08:00
yystopf 9e0272b872 修复:params无法正常获取数据 2023-12-11 08:52:15 +08:00
yystopf cc050e9c19 新增:仓库标签详情接口 2023-12-11 08:49:56 +08:00
yystopf c4829333a8 更改:只有email不敏感 2023-12-11 08:44:33 +08:00
yystopf e5257f0f8c 更改:只有email不敏感 2023-12-08 10:10:03 +08:00
yystopf c03d275d4f Merge branch 'docker_compose' into feature 2023-12-08 09:57:54 +08:00
yystopf eca533c81a 新增:仓库标签详情接口 2023-12-08 09:51:33 +08:00
yystopf 86bd9b67b6 修复:贡献者email大小写不敏感 2023-12-08 09:51:10 +08:00
songjc 56ce54cb33 refactor: delete .devops/脚本执行器.yml 2023-11-10 09:54:25 +08:00
songjc 56e8423365 feat: .devops/脚本执行器.yml 2023-11-10 09:54:05 +08:00
xxq250 60664dab9e refactor: .devops/gitlink正式环境.yml 2023-07-18 17:05:16 +08:00
xxq250 ad83be94bc Update README.md 2023-07-18 16:25:31 +08:00
xxq250 c50e8fb43e Update README.md 2023-07-18 16:21:17 +08:00
xxq250 f608d1e5fc Merge pull request '20230218版本' (#6) from develop into master 2023-07-18 16:19:19 +08:00
xxq250 2e99c5cd31 Update README.md 2023-07-18 16:14:40 +08:00
yystopf be7299a09d refactor: .devops/gitlink正式环境.yml 2023-04-17 10:08:13 +08:00
yystopf 0828f86077 refactor: .devops/gitlink正式环境.yml 2023-04-11 09:26:24 +08:00
yystopf cf1341d559 feat: .devops/gitlink正式环境.yml 2023-04-08 13:15:03 +08:00
yystopf a350f16c89 Merge pull request 'v1.18.5版本更新' (#5) from develop into master 2023-03-30 14:47:59 +08:00
yystopf a03b918c78 Merge pull request '1.19.0-dev版本' (#4) from develop into master 2023-03-14 16:38:42 +08:00
41 changed files with 5302 additions and 69 deletions

View File

@ -0,0 +1,68 @@
version: 2
name: gitlink正式环境
description: ""
global:
concurrent: 1
workflow:
- ref: start
name: 开始
task: start
- ref: git_clone_0
name: git clone
on-failure: ignore
task: git_clone@1.2.6
input:
remote_url: '"https://gitlink.org.cn/Gitlink/gitea_hat.git"'
ref: '"refs/heads/master"'
commit_id: '""'
depth: 1
needs:
- start
- ref: end
name: 结束
task: end
needs:
- gitlink_ssh_cmd_0
- ref: gitlink_scp_resource_0
name: scp复制文件支持跳板机
on-failure: ignore
task: yystopf/gitlink_scp_resource@0.0.7
input:
ssh_private_key: ((ssh.siyao))
remote_host: '"10.9.117.109"'
remote_port: '"22"'
remote_user: '"root"'
remote_file: '"/root/gitea"'
local_file: golang_build_node_0.bin_dir
gateway_host: '"123.59.135.93"'
gateway_port: '"51123"'
gateway_user: '"pdl"'
temp_file: '"/home/pdl/gitea"'
needs:
- golang_build_node_0
- ref: golang_build_node_0
name: golang_build_node
on-failure: ignore
task: yystopf/golang_build_node@0.0.2
input:
workspace: git_clone_0.git_path
out_bin_name: '"gitea"'
goos: '"linux"'
goarch: '"amd64"'
needs:
- git_clone_0
- ref: gitlink_ssh_cmd_0
name: ssh执行命令支持跳板机
task: yystopf/gitlink_ssh_cmd@0.0.6
input:
ssh_private_key: ((ssh.siyao))
remote_host: '"10.9.117.109"'
remote_port: '"22"'
remote_user: '"root"'
gateway_host: '"123.59.135.93"'
gateway_port: '"51123"'
gateway_user: '"pdl"'
ssh_cmd: '"sh update.sh"'
needs:
- gitlink_scp_resource_0

40
.devops/test.yml Normal file
View File

@ -0,0 +1,40 @@
version: 2
name: test
description: ""
global:
concurrent: 1
trigger:
webhook: gitlink@1.0.0
event:
- ref: push
ruleset-operator: AND
workflow:
- ref: start
name: 开始
task: start
- ref: git_clone_0
name: git clone
task: git_clone@1.2.9
input:
remote_url: '""'
ref: '"refs/heads/master"'
commit_id: '""'
depth: 1
needs:
- start
- ref: golong_build_122_0
name: golang build 1.22
task: dagege/golong_build_122@1.0.4
input:
workspace: git_clone_0.git_path
out_bin_name: catpaw
GOOS: '"linux"'
GOARCH: '"amd64"'
needs:
- git_clone_0
- ref: end
name: 结束
task: end
needs:
- golong_build_122_0

View File

@ -42,6 +42,10 @@
## 编译
```
sh build.sh
```
类Linux平台在项目根目录运行
```shell
# 编译Linux 64位可执行程序

174
app.ini.example Normal file
View File

@ -0,0 +1,174 @@
APP_NAME = Gitea: Git with a cup of tea
RUN_USER = git
RUN_MODE = prod
WORK_PATH = /usr/local/bin
[nacos]
SERVER = 127.0.0.1
PORT = 8848
WEIGHT = 10
[oauth2]
JWT_SECRET = pyD-ZA6zwykBhVCWdF5FsdRGDtM6kg16JLhcCu8uLPM
[security]
INTERNAL_TOKEN = eyDDbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NzQyMzU3OTN9.w7RDQaNanZHdC2XIrr9kAntPIjhfkXXOiBnNTbEdqQ8
INSTALL_LOCK = true
SECRET_KEY = K4DDaucPzKQnTf7WQxKFIoFNtIlsWVacoN5Ss3VDypQBUes7Ncy96UU735sfGOar
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
PASSWORD_HASH_ALGO = pbkdf2
[database]
PATH = /data/gitea/gitea.db
DB_TYPE = mysql
HOST = 127.0.0.1:3306
NAME = gitea_hat
USER = root
PASSWD = `123456`
MYSQL_CHARSET = utf8
SSL_MODE = disable
CONN_MAX_LIFE_TIME=180
MAX_IDLE_CONNS=5
MAX_OPEN_CONNS=200
LOG_SQL =false
[repository]
ROOT = /data/gitea-repositories
DEFAULT_BRANCH = master
DEFAULT_FORK_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages,repo.actions
DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages,repo.actions
[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
[repository.upload]
TEMP_PATH = /data/gitea/uploads
[repository.pull-request]
DEFAULT_MERGE_STYLE = merge
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[lfs]
PATH = /data/git/lfs
[server]
APP_DATA_PATH = /data/gitea
DOMAIN = 172.20.32.201
SSH_DOMAIN = 172.20.32.201
HTTP_PORT = 3000
ROOT_URL = http://172.20.32.201:10082/
DISABLE_SSH = false
SSH_PORT = 2022
SSH_LISTEN_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = n2kib4qdArULO57JW0jD2Ygm3z1ehzI8Y4zVfbxouyY
OFFLINE_MODE = false
[mailer]
ENABLED = false
[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = true
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA = false
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.example.org
SHOW_REGISTRATION_BUTTON = false
[webhook]
ALLOWED_HOST_LIST = *
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER = file
[picture]
DISABLE_GRAVATAR = true
ENABLE_FEDERATED_AVATAR = false
[attachment]
PATH = /data/gitea/attachments
[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true
[session]
PROVIDER = file
[log]
MODE = file
LEVEL = Trace
ROOT_PATH = /data/gitea/log
REDIRECT_MACARON_LOG = true
ENABLE_ACCESS_LOG = true
[api]
DEFAULT_PAGING_NUM = 200
MAX_RESPONSE_ITEMS = 200
[git]
PATH =
DISABLE_DIFF_HIGHLIGHT = false
MAX_GIT_DIFF_LINES = 1000
MAX_GIT_DIFF_LINE_CHARACTERS = 5000
MAX_GIT_DIFF_FILES = 100
GC_ARGS =
EnableAutoGitWireProtocol = true
COMMITS_RANGE_SIZE = 50
; Operation timeout in seconds
[git.timeout]
DEFAULT = 1800
MIGRATE = 21600
MIRROR = 1800
CLONE = 1800
PULL = 1800
GC = 60
[cache]
ENABLED = true
ADAPTER = memory
INTERVAL = 60
HOST =
ITEM_TTL = 16h
[cache.last_commit]
ENABLED = true
ITEM_TTL = 24h
COMMITS_COUNT = 1000
[migrations]
ALLOW_LOCALNETWORKS = true
#[cron.update_mirrors]
#SCHEDULE = @every 24h
[cron.delete_repo_archives]
ENABLED = true
RUN_AT_START = true
SCHEDULE = @every 72h
OLDER_THAN = 8h
[cron.update_checker]
ENABLED = false
[storage.repo-archive]
PATH = /data/repo-archive
[mirror]
ENABLED = true
[ssh.minimum_key_sizes]
RSA = 2048

View File

@ -12,4 +12,4 @@ go run build/generate-bindata.go public public vendor/code.gitea.io/gitea/module
go run build/generate-bindata.go templates templates vendor/code.gitea.io/gitea/modules/templates/bindata.go
echo "go build -tags 'bindata' -o gitea_hat main.go..."
go build -tags 'bindata' -o gitea_hat main.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags 'bindata' -o gitea_hat main.go

View File

@ -27,6 +27,9 @@ import (
"code.gitea.io/gitea/routers/install"
hat_routers "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers"
"github.com/felixge/fgprof"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/urfave/cli/v2"
)
@ -210,8 +213,7 @@ func serveInstalled(ctx *cli.Context) error {
}
// Set up Chi routes
webRoutes := routers.NormalRoutes()
hat_routers.InitHatRouters(graceful.GetManager().HammerContext(), webRoutes)
webRoutes := hat_routers.NormalRoutes()
err := listen(webRoutes, true)
<-graceful.GetManager().Done()
@ -236,6 +238,55 @@ func runWeb(ctx *cli.Context) error {
}
}()
rootCfg := setting.CfgProvider
nacosServer := rootCfg.Section("nacos").Key("SERVER").String()
nacosPort := rootCfg.Section("nacos").Key("PORT").MustInt64()
nacosWeight := rootCfg.Section("nacos").Key("WEIGHT").MustInt64()
if nacosWeight == 0 {
nacosWeight = 10
}
if nacosServer != "" && nacosPort != 0 {
//create ServerConfig
sc := []constant.ServerConfig{
*constant.NewServerConfig(nacosServer, uint64(nacosPort), constant.WithContextPath("/nacos")),
}
//create ClientConfig
cc := *constant.NewClientConfig(
constant.WithNamespaceId(""),
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
constant.WithCacheDir("/tmp/nacos/cache"),
constant.WithLogLevel("debug"),
)
// create naming client
client, err := clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sc,
},
)
if err != nil {
panic(err)
}
//Register
_, err = client.RegisterInstance(vo.RegisterInstanceParam{
Ip: nacosServer,
Port: uint64(nacosPort),
ServiceName: "gitea_hat",
Weight: float64(nacosWeight),
Enable: true,
Healthy: true,
Ephemeral: true,
})
log.Info("Nacos: Register Gitea Web Finished")
}
managerCtx, cancel := context.WithCancel(context.Background())
graceful.InitManager(managerCtx)
defer cancel()

29
go.mod
View File

@ -7,15 +7,21 @@ toolchain go1.21.4
require (
code.gitea.io/gitea v1.21.0
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384
github.com/NYTimes/gziphandler v1.1.1
github.com/caddyserver/certmagic v0.19.2
github.com/felixge/fgprof v0.9.3
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1
github.com/gobwas/glob v0.2.3
github.com/json-iterator/go v1.1.12
github.com/klauspost/cpuid/v2 v2.2.6
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7
github.com/nektos/act v0.2.52
github.com/prometheus/client_golang v1.17.0
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/urfave/cli/v2 v2.25.7
golang.org/x/net v0.18.0
golang.org/x/net v0.23.0
golang.org/x/text v0.14.0
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.4
@ -48,7 +54,6 @@ require (
dario.cat/mergo v1.0.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
gitea.com/go-chi/cache v0.2.0 // indirect
gitea.com/go-chi/captcha v0.0.0-20230415143339-2c0754df4384 // indirect
gitea.com/go-chi/session v0.0.0-20230613035928-39541325faa3 // indirect
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 // indirect
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 // indirect
@ -58,11 +63,16 @@ require (
github.com/ClickHouse/clickhouse-go/v2 v2.15.0 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/RoaringBitmap/roaring v1.6.0 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/alecthomas/chroma/v2 v2.11.1 // indirect
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
github.com/alibabacloud-go/tea v1.1.17 // indirect
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 // indirect
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymerick/douceur v0.2.0 // indirect
@ -87,6 +97,7 @@ require (
github.com/blevesearch/zapx/v15 v15.3.13 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/buildkite/terminal-to-html/v3 v3.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chi-middleware/proxy v1.1.1 // indirect
@ -115,7 +126,6 @@ require (
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-co-op/gocron v1.36.0 // indirect
github.com/go-enry/go-enry/v2 v2.8.6 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
@ -137,6 +147,7 @@ require (
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-github/v53 v53.2.0 // indirect
@ -156,6 +167,7 @@ require (
github.com/huandu/xstrings v1.4.0 // indirect
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
@ -186,7 +198,6 @@ require (
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/msteinert/pam v1.2.0 // indirect
github.com/nektos/act v0.2.52 // indirect
github.com/niklasfasching/go-org v1.7.0 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/nxadm/tail v1.4.8 // indirect
@ -199,7 +210,6 @@ require (
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
@ -241,19 +251,20 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.15.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/protobuf v1.31.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

61
go.sum
View File

@ -76,6 +76,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0=
github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw=
@ -109,6 +111,19 @@ github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
github.com/alibabacloud-go/tea v1.1.17 h1:05R5DnaJXe9sCNIe8KUgWHC/z6w/VZIwczgUwzRnul8=
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea-utils v1.4.4 h1:lxCDvNCdTo9FaXKKq45+4vGETQUKNOW/qKTcX9Sk53o=
github.com/alibabacloud-go/tea-utils v1.4.4/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 h1:ie/8RxBOfKZWcrbYSJi2Z8uX8TcOlSMwPlEJh83OeOw=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 h1:rWkH6D2XlXb/Y+tNAQROxBzp3a0p92ni+pXcaHBe/WI=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2/go.mod h1:GDtq+Kw+v0fO+j5BrrWiUHbBq7L+hfpzpPfXKOZMFE0=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 h1:olLiPI2iM8Hqq6vKnSxpM3awCrm9/BeOgHpzQkOYnI4=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7/go.mod h1:oDg1j4kFxnhgftaiLJABkGeSvuEvSF5Lo6UmRAMruX4=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
@ -185,6 +200,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg=
github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/buildkite/terminal-to-html/v3 v3.9.1 h1:8SOCKFK9ntpYvPE3yUAXHiZYdQI4xf9o9S3wOX7x12A=
github.com/buildkite/terminal-to-html/v3 v3.9.1/go.mod h1:Nsx19oOIo6MZM/cEPookXi/nrQQmnSJFLZL1KS05t+A=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@ -373,6 +390,7 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
@ -398,6 +416,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -565,10 +585,17 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhillyerd/enmime v1.0.1 h1:y6RyqIgBOI2hIinOXIzmeB+ITRVls0zTJIm5GwgXnjE=
github.com/jhillyerd/enmime v1.0.1/go.mod h1:LMMbm6oTlzWHghPavqHtOrP/NosVv3l42CUrZjn03/Q=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -688,6 +715,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
@ -700,6 +729,8 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7 h1:wCC1f3/VzIR1WD30YKeJGZAOchYCK/35mLC8qWt6Q6o=
github.com/nacos-group/nacos-sdk-go/v2 v2.2.7/go.mod h1:VYlyDPlQchPC31PmfBustu81vsOkdpCuO5k0dRdQcFc=
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
@ -895,6 +926,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
@ -953,6 +985,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
@ -969,8 +1002,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -1003,6 +1036,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
@ -1040,6 +1074,7 @@ golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@ -1050,8 +1085,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1119,7 +1154,9 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1140,8 +1177,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1151,8 +1188,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1227,6 +1264,7 @@ golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
@ -1325,8 +1363,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -1341,8 +1379,11 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkp
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=

View File

@ -22,7 +22,7 @@ import (
)
var (
Version = "development"
Version = "v2.7, by v1.21.0 "
Tags = ""
MakeVersion = ""
)

92
models/actions/run.go Normal file
View File

@ -0,0 +1,92 @@
package actions
import (
"context"
gitea_actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
gitea_repo_model "code.gitea.io/gitea/models/repo"
"xorm.io/builder"
)
func InsertRun(ctx context.Context, run *gitea_actions_model.ActionRun, jobs []*gitea_actions_model.ActionRunJob) error {
ctx, commiter, err := db.TxContext(ctx)
if err != nil {
return err
}
defer commiter.Close()
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
if err != nil {
return err
}
run.Index = index
run.ID = 0
if err := db.Insert(ctx, run); err != nil {
return err
}
if run.Repo == nil {
repo, err := gitea_repo_model.GetRepositoryByID(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
}
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err
}
runJobs := make([]*gitea_actions_model.ActionRunJob, 0, len(jobs))
for _, job := range jobs {
runJobs = append(runJobs, &gitea_actions_model.ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
IsForkPullRequest: run.IsForkPullRequest,
Name: job.Name,
WorkflowPayload: job.WorkflowPayload,
JobID: job.JobID,
Needs: job.Needs,
RunsOn: job.RunsOn,
Status: gitea_actions_model.StatusWaiting,
})
}
if err := db.Insert(ctx, runJobs); err != nil {
return err
}
if err := gitea_actions_model.IncreaseTaskVersion(ctx, run.OwnerID, run.RepoID); err != nil {
return err
}
return commiter.Commit()
}
func updateRepoRunsNumbers(ctx context.Context, repo *gitea_repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).
SetExpr("num_action_runs",
builder.Select("count(*)").From("action_run").
Where(builder.Eq{"repo_id": repo.ID}),
).
SetExpr("num_closed_action_runs",
builder.Select("count(*)").From("action_run").
Where(builder.Eq{
"repo_id": repo.ID,
}.And(
builder.In("status",
gitea_actions_model.StatusSuccess,
gitea_actions_model.StatusFailure,
gitea_actions_model.StatusCancelled,
gitea_actions_model.StatusSkipped,
),
),
),
).
Update(repo)
return err
}

View File

@ -2,15 +2,18 @@ package convert
import (
"encoding/json"
"fmt"
"strings"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/repo"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
gitea_convert "code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/gitdiff"
api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)
@ -92,3 +95,28 @@ func ToHookTask(t *webhook.HookTask) *api.HookTask {
ResponseContent: responseContent,
}
}
func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit string) *api.ChangedFile {
file := &api.ChangedFile{
Filename: f.GetDiffFileName(),
OldName: f.OldName,
Index: f.Index,
Type: f.Type,
IsBin: f.IsBin,
IsCreated: f.IsCreated,
IsDeleted: f.IsDeleted,
IsLFSFile: f.IsLFSFile,
IsRenamed: f.IsRenamed,
IsSubmodule: f.IsSubmodule,
Additions: f.Addition,
Deletions: f.Deletion,
Changes: f.Addition + f.Deletion,
Sha: commit,
HTMLURL: fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit),
RawURL: fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
}
return file
}

View File

@ -2,6 +2,8 @@ package git
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"strconv"
"strings"
@ -36,8 +38,8 @@ func GetFirstAndLastCommitByPath(repo *gitea_git.Repository, revision, relpath s
}
// CommitsByFileAndRange return the commits according revision file and the page
func CommitsByFileAndRange(repo *gitea_git.Repository, revision, file string, page, pageSize int) ([]*gitea_git.Commit, error) {
skip := (page - 1) * pageSize
func AllCommitsByFileAndRange(repo *gitea_git.Repository, opts gitea_git.CommitsByFileAndRangeOptions, keyword string, pageSize int) ([]*gitea_git.Commit, error) {
skip := (opts.Page - 1) * pageSize
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
@ -46,35 +48,68 @@ func CommitsByFileAndRange(repo *gitea_git.Repository, revision, file string, pa
}()
go func() {
stderr := strings.Builder{}
gitCmd := gitea_git.NewCommand(repo.Ctx, "log", prettyLogFormat, "--follow").AddDynamicArguments("--max-count=" + strconv.Itoa(pageSize))
gitCmd.AddDynamicArguments(revision)
gitCmd.AddDynamicArguments("--" + file)
gitCmd := gitea_git.NewCommand(repo.Ctx, "rev-list").
AddOptionFormat("--max-count=%d", pageSize*opts.Page).
AddOptionFormat("--skip=%d", skip).
AddOptionFormat("--grep=%s", keyword)
gitCmd.AddDynamicArguments(opts.Revision)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
gitCmd.AddArguments("--all")
// gitCmd.AddDashesAndList(opts.File)
err := gitCmd.Run(&gitea_git.RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = stdoutWriter.CloseWithError(gitea_git.ConcatenateError(err, (&stderr).String()))
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
} else {
_ = stdoutWriter.Close()
}
}()
if skip > 0 {
_, err := io.CopyN(io.Discard, stdoutReader, int64(skip*41))
if err != nil {
commits := []*gitea_git.Commit{}
shaline := [41]byte{}
var sha1 gitea_git.SHA1
for {
n, err := io.ReadFull(stdoutReader, shaline[:])
if err != nil || n < 40 {
if err == io.EOF {
return []*gitea_git.Commit{}, nil
err = nil
}
_ = stdoutReader.CloseWithError(err)
return commits, err
}
n, err = hex.Decode(sha1[:], shaline[0:40])
if n != 20 {
err = fmt.Errorf("invalid sha %q", string(shaline[:40]))
}
if err != nil {
return nil, err
}
commit, err := repo.GetCommit(sha1.String())
if err != nil {
return nil, err
}
commits = append(commits, commit)
}
}
func GetAllCommitsCount(repo *gitea_git.Repository, keyword string) (int64, error) {
cmd := gitea_git.NewCommand(repo.Ctx, "rev-list").
AddOptionFormat("--grep=%s", keyword)
cmd.AddArguments("--exclude=" + "refs/pull/" + "*")
cmd.AddArguments("--all", "--count")
stdout, _, err := cmd.RunStdString(&gitea_git.RunOpts{Dir: repo.Path})
if err != nil {
return 0, err
}
stdout, err := io.ReadAll(stdoutReader)
if err != nil {
return nil, err
}
return parsePrettyFormatLogToList(repo, stdout)
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}

View File

@ -119,7 +119,7 @@ func GetRepoContributorsNew(repo *gitea_git.Repository, page, pageSize int) (int
return err
}
name := l[strings.Index(l, "\t")+1 : strings.Index(l, " <")]
email := l[strings.Index(l, "<")+1 : strings.Index(l, ">")]
email := strings.ToLower(l[strings.Index(l, "<")+1 : strings.Index(l, ">")])
totalContributions += commitsInt
// committer is not system user
if existedContributorInfo, ok := contributorInfoHash[email]; ok {

View File

@ -0,0 +1,6 @@
package structs
type RestoreBranchOption struct {
BranchID int64 `json:"branch_id"`
BranchName string `json:"name"`
}

View File

@ -6,7 +6,7 @@ import (
type CreateHookOption struct {
// required: true
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,jianmu,softbot
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,jianmu,softbot,reposync
Type string `json:"type" binding:"Required"`
// required: true
Config gitea_api.CreateHookOptionConfig `json:"config" binding:"Required"`

View File

@ -4,6 +4,7 @@ import (
"time"
gitea_api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/gitdiff"
)
type PullRequest struct {
@ -39,3 +40,23 @@ type PullRequest struct {
CommitNum int `json:"commit_num"`
ChangedFiles int `json:"changed_files"`
}
type ChangedFile struct {
Filename string `json:"filename"`
OldName string `json:"old_name"`
Index int `json:"index"`
Type gitdiff.DiffFileType `json:"type"`
IsBin bool `json:"is_bin"`
IsCreated bool `json:"is_created"`
IsDeleted bool `json:"is_deleted"`
IsLFSFile bool `json:"is_lfs_file"`
IsRenamed bool `json:"is_renamed"`
IsSubmodule bool `json:"is_submodule"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Changes int `json:"changes"`
Sha string `json:"sha"`
HTMLURL string `json:"html_url,omitempty"`
ContentsURL string `json:"contents_url,omitempty"`
RawURL string `json:"raw_url,omitempty"`
}

View File

@ -0,0 +1,18 @@
package structs
import (
gitea_api "code.gitea.io/gitea/modules/structs"
)
type Branch struct {
*gitea_api.Branch
IsDeleted bool `json:"is_deleted"`
DeletedUnix int `json:"deleted_unix"`
ID int64 `json:"id"`
DeletedBy *BranchDeleteUser `json:"deleted_by"`
}
type BranchDeleteUser struct {
Name string `json:"name"`
Email string `json:"email"`
}

View File

@ -0,0 +1,11 @@
本协议是您(如下也称“用户”)与国防科技大学计算机学院国产基础软件工程研究中心(以下简称“中心”),关于麒麟如意内核软件(以下简称“本软件”)的协议,请认真阅读。
1.申请使用本软件的用户均需填写本协议,并遵守本协议各项条件的约束。
2.用户须在本平台内使用本软件,不得以拷贝、刻录等方式将本软件传播至本平台以外的任何第三方平台或主体。若需要向本平台以外的第三方平台或主体传播,用户需向中心提出申请并获书面授权。
3.对于未签署本协议的第三者,一旦申请使用本软件,即表示第三者同意接受本协议各项条件的约束。如果第三者不同意本协议的条件,则不能获得使用本软件的权利。
4.用户有权提出对软件的使用反馈及意见,用户可以发表对软件的使用体验及感受,但不得随意诽谤、中伤。
5.用户可以在软件上添加、修改源代码,但必须遵守本软件的开源许可协议的规定。
6.用户须保证其添加、修改的代码内容所涉及的知识产权均为其个体所有,或经过知识产权所有者的授权,用户须保证其对于前述代码内容造成的侵权并不知情。若用户提供的代码内容涉及其它第三方开源平台,用户需保证其传播方式满足该开源平台的相关约定。
7.本中心/本协议鼓励用户将开发成果返回至开发团队,或在如意内核软件中开源,开发者将在代码中署名。
8.未经本中心书面许可,用户不得将该软件用于商业用途;若该软件被用户用于学术研究,则相关学术成果中应明示引用如意内核软件。
9.您获得的只是对于本软件非商业用途的使用权。本软件仅限中华人民共和国公民申请使用。
10.本协议的最终解释权归中心。

View File

@ -1,7 +1,6 @@
package hat
import (
gocontext "context"
"fmt"
"net/http"
"strings"
@ -30,6 +29,7 @@ import (
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/admin"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/org"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo/actions"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/user"
"github.com/go-chi/cors"
)
@ -95,7 +95,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
}
}
func Routers(ctx gocontext.Context) *web.Route {
func Routers() *web.Route {
m := web.NewRoute()
m.Use(securityHeaders())
@ -123,6 +123,17 @@ func Routers(ctx gocontext.Context) *web.Route {
m.Post("/create_pr_version", bind(gitea_api.PullRequestPayload{}), repo.CreatePrVersion)
m.Group("/repos", func() {
m.Group("/{username}/{reponame}", func() {
m.Combo("").Delete(reqToken(), reqOwner(), repo.Delete)
m.Group("/actions", func() {
m.Get("", context.ReferencesGitRepo(), actions.ListActions)
m.Post("/disable", reqAdmin(), actions.DisableWorkflowFile)
m.Post("/enable", reqAdmin(), actions.EnableWorkflowFile)
m.Post("/runs/{run}/jobs/{job}", context.ReferencesGitRepo(), bind(actions.ViewRequest{}), actions.ListJobs)
m.Post("/runs/{run}/jobs/{job}/rerun", reqRepoWriter(unit_model.TypeActions), actions.Rerun)
m.Get("/runs/{run}/jobs/{job}/logs", actions.Logs)
m.Post("/runs/{run}/rerun", reqRepoWriter(unit_model.TypeActions), actions.Rerun)
m.Post("/runs", context.ReferencesGitRepo(), reqRepoWriter(unit_model.TypeActions), actions.Run)
}, reqRepoReader(unit_model.TypeActions))
m.Post("/transfer", reqOwner(), bind(gitea_api.TransferRepoOption{}), repo.Transfer)
m.Get("/branch_name_set", context.ReferencesGitRepo(), repo.BranchNameSet)
m.Get("/tag_name_set", context.ReferencesGitRepo(), repo.TagNameSet)
@ -130,25 +141,45 @@ func Routers(ctx gocontext.Context) *web.Route {
m.Group("/branches", func() {
m.Get("", context.ReferencesGitRepo(), repo.ListBranches)
m.Get("/branches_slice", context.ReferencesGitRepo(), repo.ListBranchesSlice)
m.Post("/restore", context.ReferencesGitRepo(), bind(hat_api.RestoreBranchOption{}), repo.RestoreBranch)
}, reqRepoReader(unit_model.TypeCode))
m.Group("/commits", func() {
m.Get("/{sha}/diff", repo.GetCommitDiff)
m.Group("/{sha}/files", func() {
m.Get("", repo.GetCommitFiles)
m.Get("/*", repo.GetCommitFilesByPath)
})
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
m.Group("/tags", func() {
m.Get("", repo.ListTags)
m.Get("/*", repo.GetTag)
}, reqRepoReader(unit_model.TypeCode), context.ReferencesGitRepo(true))
m.Group("/wiki", func() {
m.Get("/page_names", repo.ListWikiPageNames)
})
m.Group("/readme", func() {
m.Get("", repo.GetReadmeContents)
m.Get("/*", repo.GetReadmeContentsByPath)
})
m.Get("/commits_slice", repo.GetAllCommitsSliceByTime)
m.Get("/recent_commits", context.ReferencesGitRepo(), repo.GetRecentCommits)
m.Get("/compare/*", reqRepoReader(unit_model.TypeCode), repo.CompareDiff)
// m.Group("/compare/{shaFrom}...{shaTo}", func() {
// m.Get("", repo.CompareDiff)
// m.Group("/files", func() {
// m.Get("", repo.CompareFiles)
// m.Get("/*", repo.CompareFilesByPath)
// })
// }, reqRepoReader(unit_model.TypeCode))
m.Group("/pulls", func() {
m.Group("/{index}", func() {
m.Combo("").Get(repo.GetPullRequest).
Patch(bind(gitea_api.EditPullRequestOption{}), repo.EditPullRequest)
m.Get("/commits", context.ReferencesGitRepo(), repo.GetPullCommits)
m.Get("/files", context.ReferencesGitRepo(), repo.GetPullFiles)
m.Group("/files", func() {
m.Get("", repo.GetPullFiles)
m.Get("/*", repo.GetPullFilesByPath)
}, context.ReferencesGitRepo())
m.Group("/versions", func() {
m.Get("", repo.ListPullRequestVersions)
m.Get("/{versionId}/diff", context.ReferencesGitRepo(), repo.GetPullRequestVersionDiff)

View File

@ -0,0 +1,686 @@
package actions
import (
"bytes"
"errors"
"fmt"
"net/http"
"strings"
"time"
stdCtx "context"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/actions"
actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/convert"
hat_actions_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/actions"
jobparser "github.com/nektos/act/pkg/jobparser"
"github.com/nektos/act/pkg/model"
"xorm.io/builder"
)
func Run(ctx *context.APIContext) {
workflow := ctx.FormString("workflow")
ref := ctx.FormString("ref")
if ref == "" {
ref = ctx.Repo.Repository.DefaultBranch
}
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err.Error())
return
}
entries, err := ListWorkflowsByWorkflow(commit, workflow)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListWorkflowsByWorkflow", err.Error())
return
}
workflows := make([]*actions_module.DetectedWorkflow, 0, len(entries))
for _, entry := range entries {
content, err := actions_module.GetContentFromEntry(entry)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetContentFromEntry", err.Error())
return
}
dwf := &actions_module.DetectedWorkflow{
EntryName: entry.Name(),
Content: content,
}
workflows = append(workflows, dwf)
}
for _, dwf := range workflows {
run := &actions_model.ActionRun{
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
RepoID: ctx.Repo.Repository.ID,
OwnerID: ctx.Repo.Repository.OwnerID,
WorkflowID: dwf.EntryName,
TriggerUserID: ctx.Doer.ID,
Ref: ref,
CommitSHA: commit.ID.String(),
Status: actions_model.StatusWaiting,
}
jobs, err := jobparser.Parse(dwf.Content)
if err != nil {
ctx.Error(http.StatusInternalServerError, "jobparser.Parse", err.Error())
return
}
if err := actions_model.InsertRun(ctx, run, jobs); err != nil {
ctx.Error(http.StatusInternalServerError, "InsertRun", err.Error())
return
}
alljobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID})
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindRunJobs", err.Error())
return
}
actions_service.CreateCommitStatus(ctx, alljobs...)
}
ctx.Status(http.StatusNoContent)
}
func ListWorkflowsByWorkflow(commit *git.Commit, workflow string) (git.Entries, error) {
tree, err := commit.SubTree(".gitea/workflows")
if _, ok := err.(git.ErrNotExist); ok {
tree, err = commit.SubTree(".github/workflows")
}
if _, ok := err.(git.ErrNotExist); ok {
return nil, nil
}
if err != nil {
return nil, err
}
entries, err := tree.ListEntriesRecursiveFast()
if err != nil {
return nil, err
}
ret := make(git.Entries, 0, len(entries))
for _, entry := range entries {
if workflow == entry.Name() && (strings.HasSuffix(entry.Name(), ".yml") || strings.HasSuffix(entry.Name(), ".yaml")) {
ret = append(ret, entry)
}
}
return ret, nil
}
type Workflow struct {
Name string
ErrMsg string
}
type ResponseAction struct {
Workflows []Workflow
CurWorkflow string
ActionsConfig *repo_model.ActionsConfig
AllowDisableOrEnableWorkflow bool
CurWorkflowDisabled bool
CurActor int64
CurStatus int
IsFiltered bool
Runs actions_model.RunList
Actors []*user.User
StatusInfoList []actions_model.StatusInfo
}
func ListActions(ctx *context.APIContext) {
workflow := ctx.FormString("workflow")
actorID := ctx.FormInt64("actor")
status := ctx.FormInt("status")
var workflows []Workflow
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
ctx.Error(http.StatusInternalServerError, "IsEmpty", err.Error())
return
} else if !empty {
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err.Error())
return
}
entries, err := actions.ListWorkflows(commit)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListWorkflows", err.Error())
return
}
opts := actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID,
WithAvailable: true,
}
runners, err := actions_model.FindRunners(ctx, opts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindRunners", err.Error())
}
allRunnerLabels := make(container.Set[string])
for _, r := range runners {
allRunnerLabels.AddMultiple(r.AgentLabels...)
}
workflows = make([]Workflow, 0, len(entries))
for _, entry := range entries {
workflow := Workflow{Name: entry.Name()}
content, err := actions.GetContentFromEntry(entry)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetContentFromEntry", err.Error())
return
}
wf, err := model.ReadWorkflow(bytes.NewReader(content))
if err != nil {
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", err.Error())
workflows = append(workflows, workflow)
continue
}
// Check whether have matching runner
for _, j := range wf.Jobs {
runsOnList := j.RunsOn()
for _, ro := range runsOnList {
if strings.Contains(ro, "${{") {
// Skip if it contains expressions.
// The expressions could be very complex and could not be evaluated here,
// so just skip it, it's OK since it's just a tooltip message.
continue
}
if !allRunnerLabels.Contains(ro) {
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_runner_helper", ro)
break
}
}
if workflow.ErrMsg != "" {
break
}
}
workflows = append(workflows, workflow)
}
}
responseAction := ResponseAction{}
responseAction.Workflows = workflows
page := ctx.FormInt("page")
if page <= 0 {
page = 1
}
responseAction.CurWorkflow = workflow
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
responseAction.ActionsConfig = actionsConfig
if len(workflow) > 0 && ctx.Repo.IsAdmin() {
responseAction.AllowDisableOrEnableWorkflow = true
responseAction.CurWorkflowDisabled = actionsConfig.IsWorkflowDisabled(workflow)
}
responseAction.CurActor = actorID
responseAction.CurStatus = status
if actorID > 0 && status > int(actions_model.StatusUnknown) {
responseAction.IsFiltered = true
}
opts := actions_model.FindRunOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
},
RepoID: ctx.Repo.Repository.ID,
WorkflowID: workflow,
TriggerUserID: actorID,
}
if actions_model.Status(status) != actions_model.StatusUnknown {
opts.Status = []actions_model.Status{actions_model.Status(status)}
}
runs, total, err := actions_model.FindRuns(ctx, opts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindRuns", err.Error())
return
}
for _, run := range runs {
run.Repo = ctx.Repo.Repository
}
if err := runs.LoadTriggerUser(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadTriggerUser", err.Error())
return
}
responseAction.Runs = runs
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetActors", err.Error())
return
}
responseAction.Actors = repo.MakeSelfOnTop(ctx.Doer, actors)
responseAction.StatusInfoList = actions_model.GetStatusInfoList(ctx)
ctx.SetLinkHeader(int(total), ctx.FormInt("limit"))
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, responseAction)
}
type ViewRequest struct {
LogCursors []struct {
Step int `json:"step"`
Cursor int64 `json:"cursor"`
Expanded bool `json:"expanded"`
} `json:"logCursors"`
}
type ViewResponse struct {
State struct {
Run struct {
Link string `json:"link"`
Title string `json:"title"`
Status string `json:"status"`
CanCancel bool `json:"canCancel"`
CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve
CanRerun bool `json:"canRerun"`
Done bool `json:"done"`
Jobs []*ViewJob `json:"jobs"`
Commit ViewCommit `json:"commit"`
} `json:"run"`
CurrentJob struct {
Title string `json:"title"`
Detail string `json:"detail"`
Steps []*ViewJobStep `json:"steps"`
} `json:"currentJob"`
} `json:"state"`
Logs struct {
StepsLog []*ViewStepLog `json:"stepsLog"`
} `json:"logs"`
}
type ViewJob struct {
ID int64 `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
CanRerun bool `json:"canRerun"`
Duration string `json:"duration"`
}
type ViewCommit struct {
LocaleCommit string `json:"localeCommit"`
LocalePushedBy string `json:"localePushedBy"`
ShortSha string `json:"shortSHA"`
Link string `json:"link"`
Pusher ViewUser `json:"pusher"`
Branch ViewBranch `json:"branch"`
}
type ViewUser struct {
DisplayName string `json:"displayName"`
Link string `json:"link"`
}
type ViewBranch struct {
Name string `json:"name"`
Link string `json:"link"`
}
type ViewJobStep struct {
Summary string `json:"summary"`
Duration string `json:"duration"`
Status string `json:"status"`
}
type ViewStepLog struct {
Step int `json:"step"`
Cursor int64 `json:"cursor"`
Lines []*ViewStepLogLine `json:"lines"`
Started int64 `json:"started"`
}
type ViewStepLogLine struct {
Index int64 `json:"index"`
Message string `json:"message"`
Timestamp float64 `json:"timestamp"`
}
func ListJobs(ctx *context.APIContext) {
req := web.GetForm(ctx).(*ViewRequest)
runIndex := ctx.ParamsInt64("run")
jobIndex := ctx.ParamsInt64("job")
current, jobs := getRunJobs(ctx, runIndex, jobIndex)
if ctx.Written() {
return
}
run := current.Run
if err := run.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err.Error())
return
}
resp := &ViewResponse{}
resp.State.Run.Title = run.Title
resp.State.Run.Link = run.Link()
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.Done = run.Status.IsDone()
resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json
resp.State.Run.Status = run.Status.String()
for _, v := range jobs {
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &ViewJob{
ID: v.ID,
Name: v.Name,
Status: v.Status.String(),
CanRerun: v.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions),
Duration: v.Duration().String(),
})
}
pusher := ViewUser{
DisplayName: run.TriggerUser.GetDisplayName(),
Link: run.TriggerUser.HomeLink(),
}
branch := ViewBranch{
Name: run.PrettyRef(),
Link: run.RefLink(),
}
resp.State.Run.Commit = ViewCommit{
LocaleCommit: ctx.Tr("actions.runs.commit"),
LocalePushedBy: ctx.Tr("actions.runs.pushed_by"),
ShortSha: base.ShortSha(run.CommitSHA),
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
Pusher: pusher,
Branch: branch,
}
var task *actions_model.ActionTask
if current.TaskID > 0 {
var err error
task, err = actions_model.GetTaskByID(ctx, current.TaskID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetTaskByID", err.Error())
return
}
task.Job = current
if err := task.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err.Error())
return
}
}
resp.State.CurrentJob.Title = current.Name
resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale)
if run.NeedApproval {
resp.State.CurrentJob.Detail = ctx.Locale.Tr("actions.need_approval_desc")
}
resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead fo 'null' in json
resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json
if task != nil {
steps := actions.FullSteps(task)
for _, v := range steps {
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &ViewJobStep{
Summary: v.Name,
Duration: v.Duration().String(),
Status: v.Status.String(),
})
}
for _, cursor := range req.LogCursors {
if !cursor.Expanded {
continue
}
step := steps[cursor.Step]
logLines := make([]*ViewStepLogLine, 0) // marshal to '[]' instead fo 'null' in json
index := step.LogIndex + cursor.Cursor
validCursor := cursor.Cursor >= 0 &&
// !(cursor.Cursor < step.LogLength) when the frontend tries to fetch next line before it's ready.
// So return the same cursor and empty lines to let the frontend retry.
cursor.Cursor < step.LogLength &&
// !(index < task.LogIndexes[index]) when task data is older than step data.
// It can be fixed by making sure write/read tasks and steps in the same transaction,
// but it's easier to just treat it as fetching the next line before it's ready.
index < int64(len(task.LogIndexes))
if validCursor {
length := step.LogLength - cursor.Cursor
offset := task.LogIndexes[index]
var err error
logRows, err := actions.ReadLogs(ctx, task.LogInStorage, task.LogFilename, offset, length)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ReadLogs", err.Error())
return
}
for i, row := range logRows {
logLines = append(logLines, &ViewStepLogLine{
Index: cursor.Cursor + int64(i) + 1, // start at 1
Message: row.Content,
Timestamp: float64(row.Time.AsTime().UnixNano()) / float64(time.Second),
})
}
}
resp.Logs.StepsLog = append(resp.Logs.StepsLog, &ViewStepLog{
Step: cursor.Step,
Cursor: cursor.Cursor + int64(len(logLines)),
Lines: logLines,
Started: int64(step.Started),
})
}
}
ctx.JSON(http.StatusOK, resp)
}
func Rerun(ctx *context.APIContext) {
runIndex := ctx.ParamsInt64("run")
jobIndex := ctx.ParamsInt64("job")
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRunByIndex", err.Error())
return
}
// can not rerun job when workflow is disabled
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
cfg := cfgUnit.ActionsConfig()
if cfg.IsWorkflowDisabled(run.WorkflowID) {
ctx.Error(http.StatusInternalServerError, "IsWorkflowDisabled", ctx.Locale.Tr("actions.workflow.disabled"))
return
}
job, jobs := getRunJobs(ctx, runIndex, jobIndex)
if ctx.Written() {
return
}
if jobIndex != 0 {
jobs = []*actions_model.ActionRunJob{job}
}
// if err := hat_actions_model.InsertRun(ctx, run, jobs); err != nil {
// ctx.Error(http.StatusInternalServerError, "InsertRun", err.Error())
// return
// }
// for _, j := range jobs {
if err := newRerunJob(ctx, run, jobs); err != nil {
ctx.Error(http.StatusInternalServerError, "rerunJob", err.Error())
return
}
// }
ctx.Status(http.StatusNoContent)
}
func newRerunJob(ctx *context.APIContext, run *actions_model.ActionRun, jobs []*actions_model.ActionRunJob) error {
if err := db.WithTx(ctx, func(ctx stdCtx.Context) error {
err := hat_actions_model.InsertRun(ctx, run, jobs)
return err
}); err != nil {
return err
}
actions_service.CreateCommitStatus(ctx, jobs...)
return nil
}
func rerunJob(ctx *context.APIContext, job *actions_model.ActionRunJob) error {
status := job.Status
if !status.IsDone() {
return nil
}
job.TaskID = 0
job.Status = actions_model.StatusWaiting
job.Started = 0
job.Stopped = 0
if err := db.WithTx(ctx, func(ctx stdCtx.Context) error {
_, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped")
return err
}); err != nil {
return err
}
actions_service.CreateCommitStatus(ctx, job)
return nil
}
func Logs(ctx *context.APIContext) {
runIndex := ctx.ParamsInt64("run")
jobIndex := ctx.ParamsInt64("job")
job, _ := getRunJobs(ctx, runIndex, jobIndex)
if ctx.Written() {
return
}
if job.TaskID == 0 {
ctx.Error(http.StatusNotFound, "", "job is not started")
return
}
err := job.LoadRun(ctx)
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadRun", err.Error())
return
}
task, err := actions_model.GetTaskByID(ctx, job.TaskID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetTaskByID", err.Error())
return
}
if task.LogExpired {
ctx.Error(http.StatusNotFound, "", "logs have been cleaned up")
return
}
reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenLogs", err.Error())
return
}
defer reader.Close()
workflowName := job.Run.WorkflowID
if p := strings.Index(workflowName, "."); p > 0 {
workflowName = workflowName[0:p]
}
ctx.ServeContent(reader, &context.ServeHeaderOptions{
Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, job.Name, task.ID),
ContentLength: &task.LogSize,
ContentType: "text/plain",
ContentTypeCharset: "utf-8",
Disposition: "attachment",
})
}
func getRunJobs(ctx *context.APIContext, runIndex, jobIndex int64) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) {
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, "GetRunByIndex", err.Error())
return nil, nil
}
ctx.Error(http.StatusInternalServerError, "GetRunByIndex", err.Error())
return nil, nil
}
run.Repo = ctx.Repo.Repository
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRunJobsByRunID", err.Error())
return nil, nil
}
if len(jobs) == 0 {
ctx.Error(http.StatusNotFound, "", err.Error())
return nil, nil
}
for _, v := range jobs {
v.Run = run
}
if jobIndex >= 0 && jobIndex < int64(len(jobs)) {
return jobs[jobIndex], jobs
}
return jobs[0], jobs
}
func DisableWorkflowFile(ctx *context.APIContext) {
disableOrEnableWorkflowFile(ctx, false)
}
func EnableWorkflowFile(ctx *context.APIContext) {
disableOrEnableWorkflowFile(ctx, true)
}
func disableOrEnableWorkflowFile(ctx *context.APIContext, isEnable bool) {
workflow := ctx.FormString("workflow")
if len(workflow) == 0 {
ctx.ServerError("workflow", nil)
return
}
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
cfg := cfgUnit.ActionsConfig()
if isEnable {
cfg.EnableWorkflow(workflow)
} else {
cfg.DisableWorkflow(workflow)
}
if err := repo_model.UpdateRepoUnit(cfgUnit); err != nil {
ctx.ServerError("UpdateRepoUnit", err)
return
}
ctx.Status(http.StatusNoContent)
}

View File

@ -4,17 +4,20 @@ import (
"fmt"
"net/http"
"sort"
"strings"
"time"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/modules/context"
gitea_git "code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
gitea_api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
hat_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
hat_branch_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/convert"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
@ -23,7 +26,7 @@ import (
func ListBranches(ctx *context.APIContext) {
var totalNumOfBranches int64
var apiBranches []*gitea_api.Branch
var apiBranches []*hat_api.Branch
searchName := ctx.FormString("name")
listOptions := utils.GetListOptions(ctx)
@ -32,11 +35,20 @@ func ListBranches(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil)
return
}
var isDeleted util.OptionalBool
switch ctx.FormString("state") {
case "all":
isDeleted = util.OptionalBoolNone
case "deleted":
isDeleted = util.OptionalBoolTrue
default:
isDeleted = util.OptionalBoolFalse
}
branchOpts := git_model.FindBranchOptions{
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: util.OptionalBoolFalse,
IsDeletedBranch: isDeleted,
Keyword: searchName,
}
@ -46,8 +58,8 @@ func ListBranches(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if totalNumOfBranches < 2 { // sync branches immediately because non-empty repository should have at least 1 branch
_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
return
@ -66,9 +78,14 @@ func ListBranches(ctx *context.APIContext) {
return
}
apiBranches = make([]*gitea_api.Branch, 0, len(branches))
apiBranches = make([]*hat_api.Branch, 0, len(branches))
for i := range branches {
c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
err := branches[i].LoadDeletedBy(ctx)
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadDeletedBy", err)
return
}
c, err := ctx.Repo.GitRepo.GetCommit(branches[i].CommitID)
if err != nil {
// Skip if this branch doesn't exist anymore.
if gitea_git.IsErrNotExist(err) {
@ -80,7 +97,7 @@ func ListBranches(ctx *context.APIContext) {
}
branchProtection := rules.GetFirstMatched(branches[i].Name)
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
apiBranch, err := hat_branch_convert.ToBranch(ctx, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
@ -189,3 +206,48 @@ func BranchesSliceByProtection(ctx *context.APIContext, branchList []*git.Branch
}
return branchSlice
}
func RestoreBranch(ctx *context.APIContext) {
form := web.GetForm(ctx).(*hat_api.RestoreBranchOption)
branchID := form.BranchID
branchName := form.BranchName
deletedBranch, err := git_model.GetDeletedBranchByID(ctx, ctx.Repo.Repository.ID, branchID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetDeletedBranchByID: %v", err)
return
} else if deletedBranch == nil {
ctx.Error(http.StatusInternalServerError, "RestoreBranch: Can't restore branch '%s', as it does not exist", branchName)
return
}
if err := gitea_git.Push(ctx, ctx.Repo.Repository.RepoPath(), gitea_git.PushOptions{
Remote: ctx.Repo.Repository.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, gitea_git.BranchPrefix, deletedBranch.Name),
Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
}); err != nil {
if strings.Contains(err.Error(), "already exists") {
ctx.Error(http.StatusInternalServerError, "RestoreBranch: Can't restore branch '%s', since one with same name already exist", deletedBranch.Name)
return
}
ctx.Error(http.StatusInternalServerError, "RestoreBranch: CreateBranch: %v", err)
return
}
if err := repo_service.PushUpdate(
&repo_module.PushUpdateOptions{
RefFullName: gitea_git.RefNameFromBranch(deletedBranch.Name),
OldCommitID: gitea_git.EmptySHA,
NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name,
RepoUserName: ctx.Repo.Owner.Name,
RepoName: ctx.Repo.Repository.Name,
}); err != nil {
ctx.Error(http.StatusInternalServerError, "RestoreBranch: Update: %v", err)
return
}
ctx.Status(http.StatusCreated)
}

View File

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/services/gitdiff"
hat_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
hat_git "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)
func GetAllCommitsSliceByTime(ctx *context.APIContext) {
@ -127,6 +128,77 @@ func toResponseCommit(ctx *context.APIContext, repo *repo.Repository, gitRepo *g
}, nil
}
func GetRecentCommits(ctx *context.APIContext) {
if ctx.Repo.Repository.IsEmpty {
ctx.JSON(http.StatusConflict, api.APIError{
Message: "Git Repository is empty",
URL: setting.API.SwaggerURL,
})
return
}
keyword := ctx.FormString("keyword")
listOptions := utils.GetListOptions(ctx)
if listOptions.Page <= 0 {
listOptions.Page = 1
}
if listOptions.PageSize > setting.Git.CommitsRangeSize {
listOptions.PageSize = setting.Git.CommitsRangeSize
}
var baseCommit *git.Commit
var commitsCountTotal int64
var err error
head, err := ctx.Repo.GitRepo.GetHEADBranch()
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetHeadBranch", err)
return
}
baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(head.Name)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
return
}
commitsCountTotal, err = hat_git.GetAllCommitsCount(ctx.Repo.GitRepo, keyword)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CommitsCountFiles", err)
return
}
pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize)))
commits, err := hat_git.AllCommitsByFileAndRange(ctx.Repo.GitRepo,
git.CommitsByFileAndRangeOptions{
Revision: baseCommit.ID.String(),
File: ".",
Page: listOptions.Page,
}, keyword, listOptions.PageSize)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
return
}
userCache := make(map[string]*user_model.User)
apiCommits := make([]*api.Commit, len(commits))
for i, commit := range commits {
apiCommits[i], err = convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache, convert.ParseCommitOptions(ctx))
if err != nil {
ctx.Error(http.StatusInternalServerError, "ToCommit", err)
return
}
}
ctx.Resp.Header().Set("X-Page", strconv.Itoa(listOptions.Page))
ctx.Resp.Header().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
ctx.Resp.Header().Set("X-Total", strconv.FormatInt(commitsCountTotal, 10))
ctx.Resp.Header().Set("X-PageCount", strconv.Itoa(pageCount))
ctx.Resp.Header().Set("X-HasMore", strconv.FormatBool(listOptions.Page < pageCount))
ctx.SetLinkHeader(int(commitsCountTotal), listOptions.PageSize)
ctx.Resp.Header().Set("X-Total-Count", fmt.Sprintf("%d", commitsCountTotal))
ctx.JSON(http.StatusOK, apiCommits)
}
func GetFileAllCommits(ctx *context.APIContext) {
if ctx.Repo.Repository.IsEmpty {
ctx.JSON(http.StatusConflict, api.APIError{
@ -183,7 +255,12 @@ func GetFileAllCommits(ctx *context.APIContext) {
}
pageCount := int(math.Ceil(float64(commitsCountTotal) / float64(listOptions.PageSize)))
commits, err := hat_git.CommitsByFileAndRange(ctx.Repo.GitRepo, baseCommit.ID.String(), treePath, listOptions.Page, listOptions.PageSize)
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: baseCommit.ID.String(),
File: treePath,
Page: listOptions.Page,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
return
@ -236,6 +313,111 @@ func GetSingleCommit(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, json)
}
func GetCommitFiles(ctx *context.APIContext) {
commitID := ctx.Params(":sha")
gitRepo := ctx.Repo.GitRepo
commit, err := gitRepo.GetCommit(commitID)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(commitID)
return
}
ctx.Error(http.StatusInternalServerError, "gitRepo.GetCommit", err)
return
}
if len(commitID) != 40 {
commitID = commit.ID.String()
}
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
AfterCommitID: commitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: setting.Git.MaxGitDiffLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: -1,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
})
if err != nil {
ctx.NotFound("GetDIff", err)
return
}
listOptions := utils.GetListOptions(ctx)
totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, end := listOptions.GetStartEnd()
if end > totalNumberOfFiles {
end = totalNumberOfFiles
}
lenFiles := end - start
if lenFiles < 0 {
lenFiles = 0
}
apiFiles := make([]*hat_api.ChangedFile, 0, lenFiles)
for i := start; i < end; i++ {
apiFiles = append(apiFiles, hat_convert.ToChangedFile(diff.Files[i], ctx.Repo.Repository, commitID))
}
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(totalNumberOfFiles))
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
ctx.RespHeader().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore")
ctx.JSON(http.StatusOK, struct {
Files []*hat_api.ChangedFile `json:"files"`
TotalAddition int `json:"total_addition"`
TotalDeletion int `json:"total_deletion"`
}{
Files: apiFiles,
TotalAddition: diff.TotalAddition,
TotalDeletion: diff.TotalDeletion,
})
}
func GetCommitFilesByPath(ctx *context.APIContext) {
commitID := ctx.Params(":sha")
gitRepo := ctx.Repo.GitRepo
path := ctx.Params("*")
commit, err := gitRepo.GetCommit(commitID)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(commitID)
return
}
ctx.Error(http.StatusInternalServerError, "gitRepo.GetCommit", err)
return
}
if len(commitID) != 40 {
commitID = commit.ID.String()
}
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
AfterCommitID: commitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: setting.Git.MaxGitDiffLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: -1,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
}, path)
if err != nil {
ctx.NotFound("GetDIff", err)
return
}
ctx.JSON(http.StatusOK, &diff)
}
func GetCommitDiff(ctx *context.APIContext) {
commitID := ctx.Params(":sha")
gitRepo := ctx.Repo.GitRepo

614
routers/hat/repo/githttp.go Normal file
View File

@ -0,0 +1,614 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"bytes"
"compress/gzip"
gocontext "context"
"fmt"
"net/http"
"os"
"path"
"regexp"
"strconv"
"strings"
"sync"
"time"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/go-chi/cors"
)
func HTTPGitEnabledHandler(ctx *context.Context) {
if setting.Repository.DisableHTTPGit {
ctx.Resp.WriteHeader(http.StatusForbidden)
_, _ = ctx.Resp.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
}
}
func CorsHandler() func(next http.Handler) http.Handler {
if setting.Repository.AccessControlAllowOrigin != "" {
return cors.Handler(cors.Options{
AllowedOrigins: []string{setting.Repository.AccessControlAllowOrigin},
AllowedHeaders: []string{"Content-Type", "Authorization", "User-Agent"},
})
}
return func(next http.Handler) http.Handler {
return next
}
}
// httpBase implementation git smart HTTP protocol
func httpBase(ctx *context.Context) *serviceHandler {
username := ctx.Params(":username")
reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
if ctx.FormString("go-get") == "1" {
context.EarlyResponseForGoGetMeta(ctx)
return nil
}
var isPull, receivePack bool
service := ctx.FormString("service")
if service == "git-receive-pack" ||
strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
isPull = false
receivePack = true
} else if service == "git-upload-pack" ||
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
isPull = true
} else if service == "git-upload-archive" ||
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
isPull = true
} else {
isPull = ctx.Req.Method == "GET"
}
var accessMode perm.AccessMode
if isPull {
accessMode = perm.AccessModeRead
} else {
accessMode = perm.AccessModeWrite
}
isWiki := false
unitType := unit.TypeCode
var wikiRepoName string
if strings.HasSuffix(reponame, ".wiki") {
isWiki = true
unitType = unit.TypeWiki
wikiRepoName = reponame
reponame = reponame[:len(reponame)-5]
}
owner := ctx.ContextUser
if !owner.IsOrganization() && !owner.IsActive {
ctx.PlainText(http.StatusForbidden, "Repository cannot be accessed. You cannot push or open issues/pull-requests.")
return nil
}
repoExist := true
repo, err := repo_model.GetRepositoryByName(owner.ID, reponame)
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
if redirectRepoID, err := repo_model.LookupRedirect(owner.ID, reponame); err == nil {
context.RedirectToRepo(ctx.Base, redirectRepoID)
return nil
}
repoExist = false
} else {
ctx.ServerError("GetRepositoryByName", err)
return nil
}
}
// Don't allow pushing if the repo is archived
if repoExist && repo.IsArchived && !isPull {
ctx.PlainText(http.StatusForbidden, "This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.")
return nil
}
// Only public pull don't need auth.
isPublicPull := repoExist && !repo.IsPrivate && isPull
var (
askAuth = !isPublicPull || setting.Service.RequireSignInView
environ []string
)
// don't allow anonymous pulls if organization is not public
if isPublicPull {
if err := repo.LoadOwner(ctx); err != nil {
ctx.ServerError("LoadOwner", err)
return nil
}
askAuth = askAuth || (repo.Owner.Visibility != structs.VisibleTypePublic)
}
// check access
if askAuth {
// rely on the results of Contexter
if !ctx.IsSigned {
// TODO: support digit auth - which would be Authorization header with digit
//ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
ctx.Error(http.StatusUnauthorized)
return nil
}
context.CheckRepoScopedToken(ctx, repo, auth_model.GetScopeLevelFromAccessMode(accessMode))
if ctx.Written() {
return nil
}
if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && ctx.Data["IsActionsToken"] != true {
_, err = auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
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
ctx.PlainText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page")
return nil
} else if !auth_model.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
return nil
}
}
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
ctx.PlainText(http.StatusForbidden, "Your account is disabled.")
return nil
}
environ = []string{
repo_module.EnvRepoUsername + "=" + username,
repo_module.EnvRepoName + "=" + reponame,
repo_module.EnvPusherName + "=" + ctx.Doer.Name,
repo_module.EnvPusherID + fmt.Sprintf("=%d", ctx.Doer.ID),
repo_module.EnvAppURL + "=" + setting.AppURL,
}
if repoExist {
// Because of special ref "refs/for" .. , need delay write permission check
if git.SupportProcReceive {
accessMode = perm.AccessModeRead
}
if ctx.Data["IsActionsToken"] == true {
taskID := ctx.Data["ActionsTaskID"].(int64)
task, err := actions_model.GetTaskByID(ctx, taskID)
if err != nil {
ctx.ServerError("GetTaskByID", err)
return nil
}
if task.RepoID != repo.ID {
ctx.PlainText(http.StatusForbidden, "User permission denied")
return nil
}
if task.IsForkPullRequest {
if accessMode > perm.AccessModeRead {
ctx.PlainText(http.StatusForbidden, "User permission denied")
return nil
}
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeRead))
} else {
if accessMode > perm.AccessModeWrite {
ctx.PlainText(http.StatusForbidden, "User permission denied")
return nil
}
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeWrite))
}
} else {
p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil
}
if !p.CanAccess(accessMode, unitType) {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}
}
if !isPull && repo.IsMirror {
ctx.PlainText(http.StatusForbidden, "mirror repository is read-only")
return nil
}
}
if !ctx.Doer.KeepEmailPrivate {
environ = append(environ, repo_module.EnvPusherEmail+"="+ctx.Doer.Email)
}
if isWiki {
environ = append(environ, repo_module.EnvRepoIsWiki+"=true")
} else {
environ = append(environ, repo_module.EnvRepoIsWiki+"=false")
}
}
if !repoExist {
if !receivePack {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}
if isWiki { // you cannot send wiki operation before create the repository
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}
if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg {
ctx.PlainText(http.StatusForbidden, "Push to create is not enabled for organizations.")
return nil
}
if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser {
ctx.PlainText(http.StatusForbidden, "Push to create is not enabled for users.")
return nil
}
// Return dummy payload if GET receive-pack
if ctx.Req.Method == http.MethodGet {
dummyInfoRefs(ctx)
return nil
}
repo, err = repo_service.PushCreateRepo(ctx, ctx.Doer, owner, reponame)
if err != nil {
log.Error("pushCreateRepo: %v", err)
ctx.Status(http.StatusNotFound)
return nil
}
}
if isWiki {
// Ensure the wiki is enabled before we allow access to it
if _, err := repo.GetUnit(ctx, unit.TypeWiki); err != nil {
if repo_model.IsErrUnitTypeNotExist(err) {
ctx.PlainText(http.StatusForbidden, "repository wiki is disabled")
return nil
}
log.Error("Failed to get the wiki unit in %-v Error: %v", repo, err)
ctx.ServerError("GetUnit(UnitTypeWiki) for "+repo.FullName(), err)
return nil
}
}
environ = append(environ, repo_module.EnvRepoID+fmt.Sprintf("=%d", repo.ID))
w := ctx.Resp
r := ctx.Req
cfg := &serviceConfig{
UploadPack: true,
ReceivePack: true,
Env: environ,
}
r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
dir := repo_model.RepoPath(username, reponame)
if isWiki {
dir = repo_model.RepoPath(username, wikiRepoName)
}
return &serviceHandler{cfg, w, r, dir, cfg.Env}
}
var (
infoRefsCache []byte
infoRefsOnce sync.Once
)
func dummyInfoRefs(ctx *context.Context) {
infoRefsOnce.Do(func() {
tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-info-refs-cache")
if err != nil {
log.Error("Failed to create temp dir for git-receive-pack cache: %v", err)
return
}
defer func() {
if err := util.RemoveAll(tmpDir); err != nil {
log.Error("RemoveAll: %v", err)
}
}()
if err := git.InitRepository(ctx, tmpDir, true); err != nil {
log.Error("Failed to init bare repo for git-receive-pack cache: %v", err)
return
}
refs, _, err := git.NewCommand(ctx, "receive-pack", "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Dir: tmpDir})
if err != nil {
log.Error(fmt.Sprintf("%v - %s", err, string(refs)))
}
log.Debug("populating infoRefsCache: \n%s", string(refs))
infoRefsCache = refs
})
ctx.RespHeader().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
ctx.RespHeader().Set("Pragma", "no-cache")
ctx.RespHeader().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
ctx.RespHeader().Set("Content-Type", "application/x-git-receive-pack-advertisement")
_, _ = ctx.Write(packetWrite("# service=git-receive-pack\n"))
_, _ = ctx.Write([]byte("0000"))
_, _ = ctx.Write(infoRefsCache)
}
type serviceConfig struct {
UploadPack bool
ReceivePack bool
Env []string
}
type serviceHandler struct {
cfg *serviceConfig
w http.ResponseWriter
r *http.Request
dir string
environ []string
}
func (h *serviceHandler) setHeaderNoCache() {
h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
h.w.Header().Set("Pragma", "no-cache")
h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
}
func (h *serviceHandler) setHeaderCacheForever() {
now := time.Now().Unix()
expires := now + 31536000
h.w.Header().Set("Date", fmt.Sprintf("%d", now))
h.w.Header().Set("Expires", fmt.Sprintf("%d", expires))
h.w.Header().Set("Cache-Control", "public, max-age=31536000")
}
func containsParentDirectorySeparator(v string) bool {
if !strings.Contains(v, "..") {
return false
}
for _, ent := range strings.FieldsFunc(v, isSlashRune) {
if ent == ".." {
return true
}
}
return false
}
func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
func (h *serviceHandler) sendFile(contentType, file string) {
if containsParentDirectorySeparator(file) {
log.Error("request file path contains invalid path: %v", file)
h.w.WriteHeader(http.StatusBadRequest)
return
}
reqFile := path.Join(h.dir, file)
fi, err := os.Stat(reqFile)
if os.IsNotExist(err) {
h.w.WriteHeader(http.StatusNotFound)
return
}
h.w.Header().Set("Content-Type", contentType)
h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
http.ServeFile(h.w, h.r, reqFile)
}
// one or more key=value pairs separated by colons
var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`)
func prepareGitCmdWithAllowedService(service string, h *serviceHandler) (*git.Command, error) {
if service == "receive-pack" && h.cfg.ReceivePack {
return git.NewCommand(h.r.Context(), "receive-pack"), nil
}
if service == "upload-pack" && h.cfg.UploadPack {
return git.NewCommand(h.r.Context(), "upload-pack"), nil
}
return nil, fmt.Errorf("service %q is not allowed", service)
}
func serviceRPC(h *serviceHandler, service string) {
defer func() {
if err := h.r.Body.Close(); err != nil {
log.Error("serviceRPC: Close: %v", err)
}
}()
expectedContentType := fmt.Sprintf("application/x-git-%s-request", service)
if h.r.Header.Get("Content-Type") != expectedContentType {
log.Error("Content-Type (%q) doesn't match expected: %q", h.r.Header.Get("Content-Type"), expectedContentType)
h.w.WriteHeader(http.StatusUnauthorized)
return
}
cmd, err := prepareGitCmdWithAllowedService(service, h)
if err != nil {
log.Error("Failed to prepareGitCmdWithService: %v", err)
h.w.WriteHeader(http.StatusUnauthorized)
return
}
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
reqBody := h.r.Body
// Handle GZIP.
if h.r.Header.Get("Content-Encoding") == "gzip" {
reqBody, err = gzip.NewReader(reqBody)
if err != nil {
log.Error("Fail to create gzip reader: %v", err)
h.w.WriteHeader(http.StatusInternalServerError)
return
}
}
// set this for allow pre-receive and post-receive execute
h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service)
if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
}
var stderr bytes.Buffer
cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.dir)
cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir))
if err := cmd.Run(&git.RunOpts{
Dir: h.dir,
Env: append(os.Environ(), h.environ...),
Stdout: h.w,
Stdin: reqBody,
Stderr: &stderr,
UseContextTimeout: true,
}); err != nil {
if err.Error() != "signal: killed" {
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
}
return
}
}
// ServiceUploadPack implements Git Smart HTTP protocol
func ServiceUploadPack(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
serviceRPC(h, "upload-pack")
}
}
// ServiceReceivePack implements Git Smart HTTP protocol
func ServiceReceivePack(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
serviceRPC(h, "receive-pack")
}
}
func getServiceType(r *http.Request) string {
serviceType := r.FormValue("service")
if !strings.HasPrefix(serviceType, "git-") {
return ""
}
return strings.TrimPrefix(serviceType, "git-")
}
func updateServerInfo(ctx gocontext.Context, dir string) []byte {
out, _, err := git.NewCommand(ctx, "update-server-info").RunStdBytes(&git.RunOpts{Dir: dir})
if err != nil {
log.Error(fmt.Sprintf("%v - %s", err, string(out)))
}
return out
}
func packetWrite(str string) []byte {
s := strconv.FormatInt(int64(len(str)+4), 16)
if len(s)%4 != 0 {
s = strings.Repeat("0", 4-len(s)%4) + s
}
return []byte(s + str)
}
// GetInfoRefs implements Git dumb HTTP
func GetInfoRefs(ctx *context.Context) {
h := httpBase(ctx)
if h == nil {
return
}
h.setHeaderNoCache()
service := getServiceType(h.r)
cmd, err := prepareGitCmdWithAllowedService(service, h)
if err == nil {
if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
}
h.environ = append(os.Environ(), h.environ...)
refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir})
if err != nil {
log.Error(fmt.Sprintf("%v - %s", err, string(refs)))
}
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
h.w.WriteHeader(http.StatusOK)
_, _ = h.w.Write(packetWrite("# service=git-" + service + "\n"))
_, _ = h.w.Write([]byte("0000"))
_, _ = h.w.Write(refs)
} else {
updateServerInfo(ctx, h.dir)
h.sendFile("text/plain; charset=utf-8", "info/refs")
}
}
// GetTextFile implements Git dumb HTTP
func GetTextFile(p string) func(*context.Context) {
return func(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
h.setHeaderNoCache()
file := ctx.Params("file")
if file != "" {
h.sendFile("text/plain", "objects/info/"+file)
} else {
h.sendFile("text/plain", p)
}
}
}
}
// GetInfoPacks implements Git dumb HTTP
func GetInfoPacks(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
h.setHeaderCacheForever()
h.sendFile("text/plain; charset=utf-8", "objects/info/packs")
}
}
// GetLooseObject implements Git dumb HTTP
func GetLooseObject(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
h.setHeaderCacheForever()
h.sendFile("application/x-git-loose-object", fmt.Sprintf("objects/%s/%s",
ctx.Params("head"), ctx.Params("hash")))
}
}
// GetPackFile implements Git dumb HTTP
func GetPackFile(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
h.setHeaderCacheForever()
h.sendFile("application/x-git-packed-objects", "objects/pack/pack-"+ctx.Params("file")+".pack")
}
}
// GetIdxFile implements Git dumb HTTP
func GetIdxFile(ctx *context.Context) {
h := httpBase(ctx)
if h != nil {
h.setHeaderCacheForever()
h.sendFile("application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
}
}

View File

@ -197,7 +197,7 @@ func isValidHookHttpMethod(name string) bool {
func isValidHookTaskType(name string) bool {
// 建木devops
if name == "jianmu" || name == "softbot" {
if name == "jianmu" || name == "softbot" || name == "reposync" {
return true
}

View File

@ -3,7 +3,9 @@ package repo
import (
"errors"
"fmt"
"math"
"net/http"
"strconv"
"strings"
"time"
@ -14,6 +16,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/gitdiff"
"code.gitea.io/gitea/modules/context"
@ -28,6 +31,7 @@ import (
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
hat_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
hat_pull_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/pull"
)
@ -387,6 +391,119 @@ func GetPullFiles(ctx *context.APIContext) {
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
})
if err != nil {
ctx.ServerError("GetDiff", err)
return
}
isNew := ctx.FormString("isNew")
if isNew != "" {
listOptions := utils.GetListOptions(ctx)
totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, end := listOptions.GetStartEnd()
if end > totalNumberOfFiles {
end = totalNumberOfFiles
}
lenFiles := end - start
if lenFiles < 0 {
lenFiles = 0
}
apiFiles := make([]*hat_api.ChangedFile, 0, lenFiles)
for i := start; i < end; i++ {
apiFiles = append(apiFiles, hat_convert.ToChangedFile(diff.Files[i], ctx.Repo.Repository, headCommitID))
}
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(totalNumberOfFiles))
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
ctx.RespHeader().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore")
ctx.JSON(http.StatusOK, struct {
Files []*hat_api.ChangedFile `json:"files"`
TotalAddition int `json:"total_addition"`
TotalDeletion int `json:"total_deletion"`
}{
Files: apiFiles,
TotalAddition: diff.TotalAddition,
TotalDeletion: diff.TotalDeletion,
})
} else {
fileDiff := struct {
*gitdiff.Diff
LatestSha string
}{
Diff: diff,
LatestSha: endCommitID,
}
ctx.JSON(http.StatusOK, fileDiff)
}
}
func GetPullFilesByPath(ctx *context.APIContext) {
path := ctx.Params("*")
pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
}
return
}
if err := pr.LoadBaseRepo(ctx); err != nil {
ctx.InternalServerError(err)
return
}
if err := pr.LoadHeadRepo(ctx); err != nil {
ctx.InternalServerError(err)
return
}
baseGitRepo := ctx.Repo.GitRepo
var prInfo *git.CompareInfo
if pr.HasMerged {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName(), true, false)
} else {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName(), true, false)
}
if err != nil {
ctx.ServerError("GetCompareInfo", err)
return
}
headCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
ctx.ServerError("GetRefCommitID", err)
return
}
startCommitID := prInfo.MergeBase
endCommitID := headCommitID
diff, err := gitdiff.GetDiff(baseGitRepo, &gitdiff.DiffOptions{
BeforeCommitID: startCommitID,
AfterCommitID: endCommitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: setting.Git.MaxGitDiffLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: -1,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
}, path)
if err != nil {
ctx.ServerError("GetDiff", err)
return

View File

@ -2,7 +2,9 @@ package repo
import (
"fmt"
"math"
"net/http"
"strconv"
"strings"
"time"
@ -15,10 +17,15 @@ import (
"code.gitea.io/gitea/modules/git"
gitea_git "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/gitdiff"
hat_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
hat_git "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
hat_repo_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/repository"
)
func PrepareComapreDiff(
@ -27,7 +34,7 @@ func PrepareComapreDiff(
headRepo *repo_model.Repository,
headGitRepo *gitea_git.Repository,
compareInfo *gitea_git.CompareInfo,
baseBranch, headBranch string) bool {
baseBranch, headBranch string, path ...string) bool {
var (
err error
)
@ -60,12 +67,14 @@ func PrepareComapreDiff(
defer gitRepo.Close()
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
BeforeCommitID: compareInfo.MergeBase,
AfterCommitID: headCommitID,
MaxLines: setting.Git.MaxGitDiffLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: setting.Git.MaxGitDiffFiles,
})
BeforeCommitID: compareInfo.MergeBase,
AfterCommitID: headCommitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: setting.Git.MaxGitDiffLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: -1,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
}, path...)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetDiff", err)
@ -90,13 +99,237 @@ type CompareCommit struct {
}
func CompareDiff(ctx *context.APIContext) {
isFiles := ctx.FormString("isFiles")
if isFiles == "" {
// headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
_, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
defer headGitRepo.Close()
// _ = PrepareComapreDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch)
result := make([]CompareCommit, 0)
for _, commit := range compareInfo.Commits {
compareCommit := CompareCommit{
Commit: commit,
Sha: commit.ID.String(),
}
for _, p := range commit.Parents {
compareCommit.ParentShas = append(compareCommit.ParentShas, p.String())
}
result = append(result, compareCommit)
}
different := struct {
Commits []CompareCommit
Diff interface{}
CommitsCount int
Latestsha string
FilesCount int
}{
Commits: result,
// Diff: ctx.Data["Diff"],
}
different.CommitsCount = len(compareInfo.Commits)
different.Latestsha = compareInfo.HeadCommitID
different.FilesCount = compareInfo.NumFiles
ctx.JSON(http.StatusOK, different)
} else {
if ctx.FormString("filepath") == "" {
headUser, headRepo, headGitRepo, compareInfo, _, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
defer headGitRepo.Close()
headCommitID := headBranch
repoPath := repo_model.RepoPath(headUser.Name, headRepo.Name)
gitRepo, _ := gitea_git.OpenRepository(ctx, repoPath)
defer gitRepo.Close()
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
BeforeCommitID: compareInfo.MergeBase,
AfterCommitID: headCommitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: setting.Git.MaxGitDiffLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: -1,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetDiff", err)
return
}
listOptions := utils.GetListOptions(ctx)
totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, end := listOptions.GetStartEnd()
if end > totalNumberOfFiles {
end = totalNumberOfFiles
}
lenFiles := end - start
if lenFiles < 0 {
lenFiles = 0
}
apiFiles := make([]*hat_api.ChangedFile, 0, lenFiles)
for i := start; i < end; i++ {
apiFiles = append(apiFiles, hat_convert.ToChangedFile(diff.Files[i], ctx.Repo.Repository, headCommitID))
}
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(totalNumberOfFiles))
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
ctx.RespHeader().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore")
ctx.JSON(http.StatusOK, struct {
Files []*hat_api.ChangedFile `json:"files"`
TotalAddition int `json:"total_addition"`
TotalDeletion int `json:"total_deletion"`
}{
Files: apiFiles,
TotalAddition: diff.TotalAddition,
TotalDeletion: diff.TotalDeletion,
})
} else {
path := util.PathEscapeSegments(ctx.FormString("filepath"))
headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
defer headGitRepo.Close()
_ = PrepareComapreDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch, path)
result := make([]CompareCommit, 0)
for _, commit := range compareInfo.Commits {
compareCommit := CompareCommit{
Commit: commit,
Sha: commit.ID.String(),
}
for _, p := range commit.Parents {
compareCommit.ParentShas = append(compareCommit.ParentShas, p.String())
}
result = append(result, compareCommit)
}
different := struct {
Commits []CompareCommit
Diff interface{}
CommitsCount int
Latestsha string
}{
Commits: result,
Diff: ctx.Data["Diff"],
}
different.CommitsCount = len(compareInfo.Commits)
different.Latestsha = compareInfo.HeadCommitID
ctx.JSON(http.StatusOK, different)
}
}
}
func CompareFiles(ctx *context.APIContext) {
headUser, headRepo, headGitRepo, compareInfo, _, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
defer headGitRepo.Close()
headCommitID := headBranch
repoPath := repo_model.RepoPath(headUser.Name, headRepo.Name)
gitRepo, _ := gitea_git.OpenRepository(ctx, repoPath)
defer gitRepo.Close()
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
BeforeCommitID: compareInfo.MergeBase,
AfterCommitID: headCommitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: setting.Git.MaxGitDiffLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: -1,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetDiff", err)
return
}
listOptions := utils.GetListOptions(ctx)
totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, end := listOptions.GetStartEnd()
if end > totalNumberOfFiles {
end = totalNumberOfFiles
}
lenFiles := end - start
if lenFiles < 0 {
lenFiles = 0
}
apiFiles := make([]*hat_api.ChangedFile, 0, lenFiles)
for i := start; i < end; i++ {
apiFiles = append(apiFiles, hat_convert.ToChangedFile(diff.Files[i], ctx.Repo.Repository, headCommitID))
}
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(totalNumberOfFiles))
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
ctx.RespHeader().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore")
ctx.JSON(http.StatusOK, struct {
Files []*hat_api.ChangedFile `json:"files"`
TotalAddition int `json:"total_addition"`
TotalDeletion int `json:"total_deletion"`
}{
Files: apiFiles,
TotalAddition: diff.TotalAddition,
TotalDeletion: diff.TotalDeletion,
})
}
func CompareFilesByPath(ctx *context.APIContext) {
path := ctx.Params("*")
headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
defer headGitRepo.Close()
_ = PrepareComapreDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch)
_ = PrepareComapreDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch, path)
result := make([]CompareCommit, 0)
for _, commit := range compareInfo.Commits {
@ -612,3 +845,29 @@ func ListCodeStats(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, stats)
}
func Delete(ctx *context.APIContext) {
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
canDelete, err := repo_module.CanUserDelete(ctx, repo, ctx.Doer)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
return
} else if !canDelete {
ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
return
}
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
if err := hat_repo_service.DeleteRepository(ctx, ctx.Doer, repo, true); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
return
}
log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
ctx.Status(http.StatusNoContent)
}

View File

@ -44,6 +44,20 @@ func ListTags(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, &apiTags)
}
func GetTag(ctx *context.APIContext) {
tagName := ctx.Params("*")
tag, err := ctx.Repo.GitRepo.GetTag(tagName)
if err != nil {
ctx.NotFound(tagName)
}
apiTag, err := hat_convert.ToTag(ctx.Repo.Repository, ctx.Repo.GitRepo, tag)
if err != nil {
ctx.Error(http.StatusInternalServerError, "hat_convert.ToTag", err)
}
ctx.JSON(http.StatusOK, apiTag)
}
func BranchTagCount(ctx *context.APIContext) {
_, tagsTotal, err := ctx.Repo.GitRepo.GetTagInfos(0, 0)
if err != nil {

75
routers/hat/repo/wiki.go Normal file
View File

@ -0,0 +1,75 @@
package repo
import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
util "code.gitea.io/gitea/modules/util"
wiki_service "code.gitea.io/gitea/services/wiki"
)
func ListWikiPageNames(ctx *context.APIContext) {
wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
if err != nil {
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
}
return
}
commit, err := wikiRepo.GetBranchCommit("master")
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
}
return
}
if wikiRepo != nil {
defer wikiRepo.Close()
}
if ctx.Written() {
return
}
entries, err := commit.ListEntries()
if err != nil {
ctx.ServerError("ListEntries", err)
return
}
pages := make([]*api.WikiPageMetaData, 0, len(entries))
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
if err != nil {
if repo_model.IsErrWikiInvalidFileName(err) {
continue
}
ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
return
}
subURL := string(wikiName)
_, title := wiki_service.WebPathToUserTitle(wikiName)
pages = append(pages, &api.WikiPageMetaData{
Title: title,
HTMLURL: util.URLJoin(ctx.Repo.Repository.HTMLURL(), "wiki", subURL),
SubURL: subURL,
})
}
ctx.SetTotalCountHeader(int64(len(entries)))
ctx.JSON(http.StatusOK, pages)
}

98
routers/hat/web/base.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"errors"
"fmt"
"net/http"
"os"
"path"
"strings"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/routing"
)
func storageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) http.HandlerFunc {
prefix = strings.Trim(prefix, "/")
funcInfo := routing.GetFuncInfo(storageHandler, prefix)
if storageSetting.MinioConfig.ServeDirect {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" && req.Method != "HEAD" {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
routing.UpdateFuncInfo(req.Context(), funcInfo)
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
rPath = util.PathJoinRelX(rPath)
u, err := objStore.URL(rPath, path.Base(rPath))
if err != nil {
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
log.Warn("Unable to find %s %s", prefix, rPath)
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), http.StatusInternalServerError)
return
}
http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
})
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" && req.Method != "HEAD" {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
routing.UpdateFuncInfo(req.Context(), funcInfo)
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
rPath = util.PathJoinRelX(rPath)
if rPath == "" || rPath == "." {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
fi, err := objStore.Stat(rPath)
if err != nil {
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
log.Warn("Unable to find %s %s", prefix, rPath)
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
return
}
fr, err := objStore.Open(rPath)
if err != nil {
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
return
}
defer fr.Close()
httpcache.ServeContentWithCacheControl(w, req, path.Base(rPath), fi.ModTime(), fr)
})
}

View File

@ -0,0 +1,46 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"code.gitea.io/gitea/routers/web/repo"
"net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
context_service "code.gitea.io/gitea/services/context"
//"code.gitea.io/gitea/routers/web/repo"
hat_repo "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo"
)
func requireSignIn(ctx *context.Context) {
if !setting.Service.RequireSignInView {
return
}
// rely on the results of Contexter
if !ctx.IsSigned {
// TODO: support digit auth - which would be Authorization header with digit
//ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="."`)
ctx.Error(http.StatusUnauthorized)
}
}
func gitHTTPRouters(m *web.Route) {
m.Group("", func() {
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
m.GetOptions("/info/refs", hat_repo.GetInfoRefs)
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
}

93
routers/hat/web/goget.go Normal file
View File

@ -0,0 +1,93 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"fmt"
"html"
"net/http"
"net/url"
"path"
"strings"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
func goGet(ctx *context.Context) {
if ctx.Req.Method != "GET" || len(ctx.Req.URL.RawQuery) < 8 || ctx.FormString("go-get") != "1" {
return
}
parts := strings.SplitN(ctx.Req.URL.EscapedPath(), "/", 4)
if len(parts) < 3 {
return
}
ownerName := parts[1]
repoName := parts[2]
// Quick responses appropriate go-get meta with status 200
// regardless of if user have access to the repository,
// or the repository does not exist at all.
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
trimmedRepoName := strings.TrimSuffix(repoName, ".git")
if ownerName == "" || trimmedRepoName == "" {
_, _ = ctx.Write([]byte(`<!doctype html>
<html>
<body>
invalid import path
</body>
</html>
`))
ctx.Status(http.StatusBadRequest)
return
}
branchName := setting.Repository.DefaultBranch
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
if err == nil && len(repo.DefaultBranch) > 0 {
branchName = repo.DefaultBranch
}
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
appURL, _ := url.Parse(setting.AppURL)
insecure := ""
if appURL.Scheme == string(setting.HTTP) {
insecure = "--insecure "
}
goGetImport := context.ComposeGoGetImport(ownerName, trimmedRepoName)
var cloneURL string
if setting.Repository.GoGetCloneURLProtocol == "ssh" {
cloneURL = repo_model.ComposeSSHCloneURL(ownerName, repoName)
} else {
cloneURL = repo_model.ComposeHTTPSCloneURL(ownerName, repoName)
}
goImportContent := fmt.Sprintf("%s git %s", goGetImport, cloneURL /*CloneLink*/)
goSourceContent := fmt.Sprintf("%s _ %s %s", goGetImport, prefix+"{/dir}" /*GoDocDirectory*/, prefix+"{/dir}/{file}#L{line}" /*GoDocFile*/)
goGetCli := fmt.Sprintf("go get %s%s", insecure, goGetImport)
res := fmt.Sprintf(`<!doctype html>
<html>
<head>
<meta name="go-import" content="%s">
<meta name="go-source" content="%s">
</head>
<body>
%s
</body>
</html>`, html.EscapeString(goImportContent), html.EscapeString(goSourceContent), html.EscapeString(goGetCli))
ctx.RespHeader().Set("Content-Type", "text/html")
_, _ = ctx.Write([]byte(res))
}

118
routers/hat/web/home.go Normal file
View File

@ -0,0 +1,118 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"net/http"
"strconv"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/web/auth"
"code.gitea.io/gitea/routers/web/user"
)
const (
// tplHome home page template
tplHome base.TplName = "home"
)
// Home render home page
func Home(ctx *context.Context) {
if ctx.IsSigned {
if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(http.StatusOK, auth.TplActivate)
} else if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
} else if ctx.Doer.MustChangePassword {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
} else {
user.Dashboard(ctx)
}
return
// Check non-logged users landing page.
} else if setting.LandingPageURL != setting.LandingPageHome {
ctx.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
return
}
// Check auto-login.
uname := ctx.GetSiteCookie(setting.CookieUserName)
if len(uname) != 0 {
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}
ctx.Data["PageIsHome"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.HTML(http.StatusOK, tplHome)
}
// HomeSitemap renders the main sitemap
func HomeSitemap(ctx *context.Context) {
m := sitemap.NewSitemapIndex()
if !setting.Service.Explore.DisableUsersPage {
_, cnt, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: 1},
IsActive: util.OptionalBoolTrue,
Visible: []structs.VisibleType{structs.VisibleTypePublic},
})
if err != nil {
ctx.ServerError("SearchUsers", err)
return
}
count := int(cnt)
idx := 1
for i := 0; i < count; i += setting.UI.SitemapPagingNum {
m.Add(sitemap.URL{URL: setting.AppURL + "explore/users/sitemap-" + strconv.Itoa(idx) + ".xml"})
idx++
}
}
_, cnt, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: 1,
},
Actor: ctx.Doer,
AllPublic: true,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
return
}
count := int(cnt)
idx := 1
for i := 0; i < count; i += setting.UI.SitemapPagingNum {
m.Add(sitemap.URL{URL: setting.AppURL + "explore/repos/sitemap-" + strconv.Itoa(idx) + ".xml"})
idx++
}
ctx.Resp.Header().Set("Content-Type", "text/xml")
if _, err := m.WriteTo(ctx.Resp); err != nil {
log.Error("Failed writing sitemap: %v", err)
}
}
// NotFound render 404 page
func NotFound(ctx *context.Context) {
ctx.Data["Title"] = "Page Not Found"
ctx.NotFound("home.NotFound", nil)
}

View File

@ -0,0 +1,33 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"crypto/subtle"
"net/http"
"code.gitea.io/gitea/modules/setting"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Metrics validate auth token and render prometheus metrics
func Metrics(resp http.ResponseWriter, req *http.Request) {
if setting.Metrics.Token == "" {
promhttp.Handler().ServeHTTP(resp, req)
return
}
header := req.Header.Get("Authorization")
if header == "" {
http.Error(resp, "", http.StatusUnauthorized)
return
}
got := []byte(header)
want := []byte("Bearer " + setting.Metrics.Token)
if subtle.ConstantTimeCompare(got, want) != 1 {
http.Error(resp, "", http.StatusUnauthorized)
return
}
promhttp.Handler().ServeHTTP(resp, req)
}

View File

@ -0,0 +1,32 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"fmt"
"net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
)
type nodeInfoLinks struct {
Links []nodeInfoLink `json:"links"`
}
type nodeInfoLink struct {
Href string `json:"href"`
Rel string `json:"rel"`
}
// NodeInfoLinks returns links to the node info endpoint
func NodeInfoLinks(ctx *context.Context) {
nodeinfolinks := &nodeInfoLinks{
Links: []nodeInfoLink{{
fmt.Sprintf("%sapi/v1/nodeinfo", setting.AppURL),
"http://nodeinfo.diaspora.software/ns/schema/2.1",
}},
}
ctx.JSON(http.StatusOK, nodeinfolinks)
}

View File

@ -0,0 +1,25 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
)
// tplSwaggerV1Json swagger v1 json template
const tplSwaggerV1Json base.TplName = "swagger/v1_json"
// SwaggerV1Json render swagger v1 json
func SwaggerV1Json(ctx *context.Context) {
t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json), nil)
if err != nil {
ctx.ServerError("unable to find template", err)
return
}
ctx.Resp.Header().Set("Content-Type", "application/json")
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
ctx.ServerError("unable to execute template", err)
}
}

1544
routers/hat/web/web.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package web
import (
"fmt"
"net/http"
"net/url"
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
type webfingerJRD struct {
Subject string `json:"subject,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Properties map[string]any `json:"properties,omitempty"`
Links []*webfingerLink `json:"links,omitempty"`
}
type webfingerLink struct {
Rel string `json:"rel,omitempty"`
Type string `json:"type,omitempty"`
Href string `json:"href,omitempty"`
Titles map[string]string `json:"titles,omitempty"`
Properties map[string]any `json:"properties,omitempty"`
}
// WebfingerQuery returns information about a resource
// https://datatracker.ietf.org/doc/html/rfc7565
func WebfingerQuery(ctx *context.Context) {
appURL, _ := url.Parse(setting.AppURL)
resource, err := url.Parse(ctx.FormTrim("resource"))
if err != nil {
ctx.Error(http.StatusBadRequest)
return
}
var u *user_model.User
switch resource.Scheme {
case "acct":
// allow only the current host
parts := strings.SplitN(resource.Opaque, "@", 2)
if len(parts) != 2 {
ctx.Error(http.StatusBadRequest)
return
}
if parts[1] != appURL.Host {
ctx.Error(http.StatusBadRequest)
return
}
u, err = user_model.GetUserByName(ctx, parts[0])
case "mailto":
u, err = user_model.GetUserByEmail(ctx, resource.Opaque)
if u != nil && u.KeepEmailPrivate {
err = user_model.ErrUserNotExist{}
}
default:
ctx.Error(http.StatusBadRequest)
return
}
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusNotFound)
} else {
log.Error("Error getting user: %s Error: %v", resource.Opaque, err)
ctx.Error(http.StatusInternalServerError)
}
return
}
if !user_model.IsUserVisibleToViewer(ctx, u, ctx.Doer) {
ctx.Error(http.StatusNotFound)
return
}
aliases := []string{
u.HTMLURL(),
appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
}
if !u.KeepEmailPrivate {
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
}
links := []*webfingerLink{
{
Rel: "http://webfinger.net/rel/profile-page",
Type: "text/html",
Href: u.HTMLURL(),
},
{
Rel: "http://webfinger.net/rel/avatar",
Href: u.AvatarLink(ctx),
},
{
Rel: "self",
Type: "application/activity+json",
Href: appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
},
{
Rel: "http://openid.net/specs/connect/1.0/issuer",
Href: appURL.String(),
},
}
ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
ctx.JSON(http.StatusOK, &webfingerJRD{
Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
Aliases: aliases,
Links: links,
})
}

View File

@ -10,9 +10,17 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
actions_router "code.gitea.io/gitea/routers/api/actions"
packages_router "code.gitea.io/gitea/routers/api/packages"
apiv1 "code.gitea.io/gitea/routers/api/v1"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/routers/private"
//web_routers "code.gitea.io/gitea/routers/web"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/models/migrations"
api_hat "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat"
web_routers "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/web"
hat_pull_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/pull"
)
@ -40,12 +48,6 @@ func GlobalInitInstalled(ctx context.Context) {
}
func InitHatRouters(ctx context.Context, e *web.Route) *web.Route {
e.Mount("/api/hat", api_hat.Routers(ctx))
return e
}
func InitDBEngine(ctx context.Context) (err error) {
log.Info("Beginning hat ORM engine initialization.")
for i := 0; i < setting.Database.DBConnectRetries; i++ {
@ -67,3 +69,37 @@ func InitDBEngine(ctx context.Context) (err error) {
db.HasEngine = true
return nil
}
func NormalRoutes() *web.Route {
_ = templates.HTMLRenderer()
r := web.NewRoute()
r.Use(common.ProtocolMiddlewares()...)
r.Mount("/", web_routers.Routes())
r.Mount("/api/v1", apiv1.Routes())
r.Mount("/api/hat", api_hat.Routers())
r.Mount("/api/internal", private.Routes())
r.Post("/-/fetch-redirect", common.FetchRedirectDelegate)
if setting.Packages.Enabled {
// This implements package support for most package managers
r.Mount("/api/packages", packages_router.CommonRoutes())
// This implements the OCI API (Note this is not preceded by /api but is instead /v2)
r.Mount("/v2", packages_router.ContainerRoutes())
}
if setting.Actions.Enabled {
prefix := "/api/actions"
r.Mount(prefix, actions_router.Routes(prefix))
// TODO: Pipeline api used for runner internal communication with gitea server. but only artifact is used for now.
// In Github, it uses ACTIONS_RUNTIME_URL=https://pipelines.actions.githubusercontent.com/fLgcSHkPGySXeIFrg8W8OBSfeg3b5Fls1A1CwX566g8PayEGlg/
// TODO: this prefix should be generated with a token string with runner ?
prefix = "/api/actions_pipeline"
r.Mount(prefix, actions_router.ArtifactsRoutes(prefix))
}
return r
}

View File

@ -0,0 +1,29 @@
package convert
import (
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
git_model "code.gitea.io/gitea/models/git"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
gitea_convert "code.gitea.io/gitea/services/convert"
)
func ToBranch(ctx *context.APIContext, branch *git_model.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*hat_api.Branch, error) {
gitea_api_branch, err := gitea_convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, bp, user, isRepoAdmin)
if err != nil {
return nil, err
}
hat_api_branch := &hat_api.Branch{}
hat_api_branch.Branch = gitea_api_branch
hat_api_branch.IsDeleted = branch.IsDeleted
hat_api_branch.DeletedUnix = int(branch.DeletedUnix)
hat_api_branch.ID = branch.ID
hat_api_branch.DeletedBy = &hat_api.BranchDeleteUser{
Name: branch.DeletedBy.Name,
Email: branch.DeletedBy.Email,
}
return hat_api_branch, nil
}

View File

@ -0,0 +1,389 @@
package repository
import (
"context"
"fmt"
"code.gitea.io/gitea/models"
actions_model "code.gitea.io/gitea/models/actions"
activities_model "code.gitea.io/gitea/models/activities"
admin_model "code.gitea.io/gitea/models/admin"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
secret_model "code.gitea.io/gitea/models/secret"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
"xorm.io/builder"
)
func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, uid, repoID int64, ignoreOrgTeams ...bool) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
sess := db.GetEngine(ctx)
// Query the action tasks of this repo, they will be needed after they have been deleted to remove the logs
tasks, err := actions_model.FindTasks(ctx, actions_model.FindTaskOptions{RepoID: repoID})
if err != nil {
return fmt.Errorf("find actions tasks of repo %v: %w", repoID, err)
}
// Query the artifacts of this repo, they will be needed after they have been deleted to remove artifacts files in ObjectStorage
artifacts, err := actions_model.ListArtifactsByRepoID(ctx, repoID)
if err != nil {
return fmt.Errorf("list actions artifacts of repo %v: %w", repoID, err)
}
// In case owner is a organization, we have to change repo specific teams
// if ignoreOrgTeams is not true
var org *user_model.User
if len(ignoreOrgTeams) == 0 || !ignoreOrgTeams[0] {
if org, err = user_model.GetUserByID(ctx, uid); err != nil {
return err
}
}
repo := &repo_model.Repository{OwnerID: uid}
has, err := sess.ID(repoID).Get(repo)
if err != nil {
return err
} else if !has {
return repo_model.ErrRepoNotExist{
ID: repoID,
UID: uid,
OwnerName: "",
Name: "",
}
}
// Delete Deploy Keys
deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID})
if err != nil {
return fmt.Errorf("listDeployKeys: %w", err)
}
needRewriteKeysFile := len(deployKeys) > 0
for _, dKey := range deployKeys {
if err := models.DeleteDeployKey(ctx, doer, dKey.ID); err != nil {
return fmt.Errorf("deleteDeployKeys: %w", err)
}
}
if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil {
return err
} else if cnt != 1 {
return repo_model.ErrRepoNotExist{
ID: repoID,
UID: uid,
OwnerName: "",
Name: "",
}
}
if org != nil && org.IsOrganization() {
teams, err := organization.FindOrgTeams(ctx, org.ID)
if err != nil {
return err
}
for _, t := range teams {
if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) {
continue
} else if err = removeRepositoryFromTeam(ctx, t, repo, false); err != nil {
return err
}
}
}
attachments := make([]*repo_model.Attachment, 0, 20)
if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id").
Where("`release`.repo_id = ?", repoID).
Find(&attachments); err != nil {
return err
}
releaseAttachments := make([]string, 0, len(attachments))
for i := 0; i < len(attachments); i++ {
releaseAttachments = append(releaseAttachments, attachments[i].RelativePath())
}
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil {
return err
}
// if _, err := db.GetEngine(ctx).In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"webhook.repo_id": repo.ID})).
// Delete(&webhook.HookTask{}); err != nil {
// return err
// }
if _, err := db.GetEngine(ctx).Exec("DELETE hook_task FROM hook_task INNER JOIN webhook ON webhook.id = hook_task.hook_id WHERE webhook.repo_id = ? ", repo.ID); err != nil {
return err
}
if err := db.DeleteBeans(ctx,
&access_model.Access{RepoID: repo.ID},
&activities_model.Action{RepoID: repo.ID},
&repo_model.Collaboration{RepoID: repoID},
&issues_model.Comment{RefRepoID: repoID},
&git_model.CommitStatus{RepoID: repoID},
&git_model.Branch{RepoID: repoID},
&git_model.LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID},
&issues_model.Milestone{RepoID: repoID},
&repo_model.Mirror{RepoID: repoID},
&activities_model.Notification{RepoID: repoID},
&git_model.ProtectedBranch{RepoID: repoID},
&git_model.ProtectedTag{RepoID: repoID},
&repo_model.PushMirror{RepoID: repoID},
&repo_model.Release{RepoID: repoID},
&repo_model.RepoIndexerStatus{RepoID: repoID},
&repo_model.Redirect{RedirectRepoID: repoID},
&repo_model.RepoUnit{RepoID: repoID},
&repo_model.Star{RepoID: repoID},
&admin_model.Task{RepoID: repoID},
&repo_model.Watch{RepoID: repoID},
&webhook.Webhook{RepoID: repoID},
&secret_model.Secret{RepoID: repoID},
&actions_model.ActionTaskStep{RepoID: repoID},
&actions_model.ActionTask{RepoID: repoID},
&actions_model.ActionRunJob{RepoID: repoID},
&actions_model.ActionRun{RepoID: repoID},
&actions_model.ActionRunner{RepoID: repoID},
&actions_model.ActionScheduleSpec{RepoID: repoID},
&actions_model.ActionSchedule{RepoID: repoID},
&actions_model.ActionArtifact{RepoID: repoID},
); err != nil {
return fmt.Errorf("deleteBeans: %w", err)
}
// Delete Labels and related objects
if err := issues_model.DeleteLabelsByRepoID(ctx, repoID); err != nil {
return err
}
// Delete Pulls and related objects
if err := issues_model.DeletePullsByBaseRepoID(ctx, repoID); err != nil {
return err
}
// Delete Issues and related objects
var attachmentPaths []string
if attachmentPaths, err = issues_model.DeleteIssuesByRepoID(ctx, repoID); err != nil {
return err
}
// Delete issue index
if err := db.DeleteResourceIndex(ctx, "issue_index", repoID); err != nil {
return err
}
if repo.IsFork {
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
return fmt.Errorf("decrease fork count: %w", err)
}
}
if _, err := db.Exec(ctx, "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil {
return err
}
if len(repo.Topics) > 0 {
if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil {
return err
}
}
if err := project_model.DeleteProjectByRepoID(ctx, repoID); err != nil {
return fmt.Errorf("unable to delete projects for repo[%d]: %w", repoID, err)
}
// Remove LFS objects
var lfsObjects []*git_model.LFSMetaObject
if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
return err
}
lfsPaths := make([]string, 0, len(lfsObjects))
for _, v := range lfsObjects {
count, err := db.CountByBean(ctx, &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}})
if err != nil {
return err
}
if count > 1 {
continue
}
lfsPaths = append(lfsPaths, v.RelativePath())
}
if _, err := db.DeleteByBean(ctx, &git_model.LFSMetaObject{RepositoryID: repoID}); err != nil {
return err
}
// Remove archives
var archives []*repo_model.RepoArchiver
if err = sess.Where("repo_id=?", repoID).Find(&archives); err != nil {
return err
}
archivePaths := make([]string, 0, len(archives))
for _, v := range archives {
archivePaths = append(archivePaths, v.RelativePath())
}
if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil {
return err
}
if repo.NumForks > 0 {
if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
log.Error("reset 'fork_id' and 'is_fork': %v", err)
}
}
// Get all attachments with both issue_id and release_id are zero
var newAttachments []*repo_model.Attachment
if err := sess.Where(builder.Eq{
"repo_id": repo.ID,
"issue_id": 0,
"release_id": 0,
}).Find(&newAttachments); err != nil {
return err
}
newAttachmentPaths := make([]string, 0, len(newAttachments))
for _, attach := range newAttachments {
newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath())
}
if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil {
return err
}
if err = committer.Commit(); err != nil {
return err
}
committer.Close()
if needRewriteKeysFile {
if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
log.Error("RewriteAllPublicKeys failed: %v", err)
}
}
// We should always delete the files after the database transaction succeed. If
// we delete the file but the database rollback, the repository will be broken.
// Remove repository files.
repoPath := repo.RepoPath()
system_model.RemoveAllWithNotice(ctx, "Delete repository files", repoPath)
// Remove wiki files
if repo.HasWiki() {
system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath())
}
// Remove archives
for _, archive := range archivePaths {
system_model.RemoveStorageWithNotice(ctx, storage.RepoArchives, "Delete repo archive file", archive)
}
// Remove lfs objects
for _, lfsObj := range lfsPaths {
system_model.RemoveStorageWithNotice(ctx, storage.LFS, "Delete orphaned LFS file", lfsObj)
}
// Remove issue attachment files.
for _, attachment := range attachmentPaths {
system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", attachment)
}
// Remove release attachment files.
for _, releaseAttachment := range releaseAttachments {
system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete release attachment", releaseAttachment)
}
// Remove attachment with no issue_id and release_id.
for _, newAttachment := range newAttachmentPaths {
system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", newAttachment)
}
if len(repo.Avatar) > 0 {
if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil {
return fmt.Errorf("Failed to remove %s: %w", repo.Avatar, err)
}
}
// Finally, delete action logs after the actions have already been deleted to avoid new log files
for _, task := range tasks {
err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename)
if err != nil {
log.Error("remove log file %q: %v", task.LogFilename, err)
// go on
}
}
// delete actions artifacts in ObjectStorage after the repo have already been deleted
for _, art := range artifacts {
if err := storage.ActionsArtifacts.Delete(art.StoragePath); err != nil {
log.Error("remove artifact file %q: %v", art.StoragePath, err)
// go on
}
}
return nil
}
func removeRepositoryFromTeam(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) {
e := db.GetEngine(ctx)
if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil {
return err
}
t.NumRepos--
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
return err
}
// Don't need to recalculate when delete a repository from organization.
if recalculate {
if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
return err
}
}
teamUsers, err := organization.GetTeamUsersByTeamID(ctx, t.ID)
if err != nil {
return fmt.Errorf("getTeamUsersByTeamID: %w", err)
}
for _, teamUser := range teamUsers {
has, err := access_model.HasAccess(ctx, teamUser.UID, repo)
if err != nil {
return err
} else if has {
continue
}
if err = repo_model.WatchRepo(ctx, teamUser.UID, repo.ID, false); err != nil {
return err
}
// Remove all IssueWatches a user has subscribed to in the repositories
if err := issues_model.RemoveIssueWatchersByRepoID(ctx, teamUser.UID, repo.ID); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,29 @@
package repository
import (
"context"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
)
func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, notify bool) error {
if err := pull_service.CloseRepoBranchesPulls(ctx, doer, repo); err != nil {
log.Error("CloseRepoBranchesPulls failed: %v", err)
}
if notify {
// If the repo itself has webhooks, we need to trigger them before deleting it...
notify_service.DeleteRepository(ctx, doer, repo)
}
if err := DeleteRepositoryDirectly(ctx, doer, repo.OwnerID, repo.ID); err != nil {
return err
}
return packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID)
}