不是所有公司都需要"重型"的 GitLab EE。10 人以下小团队、单仓库 / 单分支、偶尔需要自托管——这种场景下,Gitea、Gitblit、Gogs 三款轻量自托管 Git 服务更合适:镜像小、启动快、吃内存少(一个 Gitea 跑 200 MB 内存,GitLab 起步就是 4 GB)。
但自托管 Git 平台只是入口——真正让团队"省心"的是把代码规范检查内置到提交链路里:客户端 pre-commit 钩子挡住本地漏改的代码,服务端 pre-receive 钩子挡住别人 PR 漏改的代码。
阅读对象:需要从零搭自托管 Git 服务的小团队 leader,或想给现有 Gitea 加代码规范检查的开发者
覆盖范围:Gitea / Gitblit / Gogs 三款服务的镜像部署、对比、客户端 p3c-pmd pre-commit 检查、服务端 pre-receive 钩子、黑白名单机制
一、三款 Git 平台对比
| 维度 | Gitea | Gitblit | Gogs |
|---|
| 语言 | Go | Java | Go |
| 镜像大小 | ~50 MB | ~150 MB | ~30 MB |
| 内存占用 | 200 MB~ | 500 MB~ | 200 MB~ |
| 启动时间 | 秒级 | 30 秒 | 秒级 |
| 维护活跃度 | 极高(社区驱动,月级发版) | 低(停更风险) | 中(社区维护) |
| 镜像选择 | gitea/gitea 官方 | gitblit/gitblit 第三方 | gogs/gogs 官方 |
| Web UI | 现代(仿 GitHub) | 复古(类似 Bitbucket) | 简洁(仿 GitHub) |
| 权限模型 | 完善(团队/组织/仓库级) | 中等 | 基础 |
| CI/CD 内置 | 有(Gitea Actions) | 无 | 无 |
| 包管理(Packages) | 有 | 无 | 无 |
| 迁移工具 | 一键从 GitHub/GitLab/Gogs 导入 | 导入脚本 | 一键从 GitHub 导入 |
When to use:
- 新项目 / 长期维护 → Gitea(社区活跃、UI 现代、CI 内置)
- 老牌 Java 团队 / 不想装 Go 运行时 → Gitblit(纯 Java 部署)
- 极致轻量 / 资源紧张 → Gogs(镜像最小、维护尚可)
二、Gitea 部署(推荐)
2.1 镜像选择
| 镜像 | 用途 |
|---|
gitea/gitea:latest | 最新版(开发版,不推荐生产) |
gitea/gitea:1.19 | 1.19 系列(推荐 LTS) |
gitea/gitea:1.23-jdk8 | 1.23 + JDK8(给需要跑 java 工具的钩子用) |
2.2 docker run 启动
1
2
3
4
5
6
7
8
9
10
11
| docker run -d \
--name gitea \
--privileged=true \
--restart=always \
-e TZ=Asia/Shanghai \
-e USER_UID=1000 \
-e USER_GID=1000 \
-p 13000:3000 \
-p 10022:22 \
-v /d/release/gitea:/data \
gitea/gitea:1.19.0-rc1
|
坑 1:--privileged=true 是为了让钩子脚本能正常运行(pre-receive 等钩子会涉及 chown、chmod)。不用 --privileged 会导致钩子"无法执行"。
2.3 docker-compose 启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # docker-compose.yml
version: "3"
services:
gitea:
image: gitea/gitea:1.23.5-jdk8
container_name: gitea
restart: always
privileged: true
environment:
- TZ=Asia/Shanghai
- USER_UID=1000
- USER_GID=1000
ports:
- "13000:3000" # Web
- "10022:22" # SSH
volumes:
- "/home/docker/gitea/data:/data"
|
坑 2:gitea/gitea:1.23.5-jdk8 的 -jdk8 后缀是给"需要在 Gitea 容器内跑 Java 工具"(如 p3c-pmd)的场景用的。如果你的代码规范检查不在 Gitea 容器内跑,用普通 gitea/gitea:1.23 镜像更小。
2.4 关键配置
容器内 /data/gitea/conf/app.ini:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| [server]
DOMAIN = <YOUR_DOMAIN>
SSH_DOMAIN = <YOUR_DOMAIN>
HTTP_PORT = 3000
ROOT_URL = http://<YOUR_DOMAIN>:13000/
[security]
INTERNAL_TOKEN = <RANDOM_32_BYTES>
[service]
DISABLE_REGISTRATION = false
ENABLE_NOTIFY_MAIL = false
[migrations]
ALLOWED_DOMAINS = 127.0.0.1,<INTERNAL_HOST>,<PUBLIC_HOST>,gitee.com
ALLOW_LOCALNETWORKS = true
[oauth2]
ENABLED = true
|
坑 3:ALLOWED_DOMAINS + ALLOW_LOCALNETWORKS=true 是让 Gitea 能从内网 IP 拉代码(clone 公共仓库),不配这个导入外部仓库会报 403。
2.5 创建第一个仓库
Web 端:登录 → + → 创建新仓库
命令行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 从命令行创建新仓库
mkdir myproject && cd myproject
git init
git checkout -b main
echo "# My Project" > README.md
git add .
git commit -m "first commit"
git remote add origin ssh://git@<GITEA_HOST>:10022/<USERNAME>/myproject.git
git push -u origin main
# 推送已存在的仓库
cd existing-project
git remote add origin ssh://git@<GITEA_HOST>:10022/<USERNAME>/existing-project.git
git push -u origin main
|
2.6 升级路径
gitea:jdk8 → gitea:1.23.5-jdk8 这种升级数据完全兼容——直接换镜像 tag 即可:
1
2
3
4
| docker-compose down
# 改 docker-compose.yml 的 image tag
docker-compose up -d
# 启动后会自动跑数据库 migration
|
三、Gitblit 部署
Gitblit 的特点是纯 Java——如果你公司强制 Java 技术栈、或不想在服务器装 Go 运行时,选它。
1
2
3
4
5
| # 第三方镜像(Gitblit 官方未提供 Docker 镜像)
docker pull jacekkowalski/gitblit
docker run -d -p 8080:8080 -p 8443:8443 \
-v /data/gitblit:/opt/gitblit-data \
jacekkowalski/gitblit
|
访问 http://<HOST>:8080/,默认账号 admin/admin(首次登录后改密)。
坑 4:Gitblit 镜像社区维护,版本和官方 release 可能有滞后;建议自己 build:
1
2
3
4
5
6
| FROM openjdk:8-jre
RUN wget https://github.com/gitblit/gitblit/releases/download/v1.9.3/gitblit-1.9.3.tar.gz && \
tar -xzf gitblit-1.9.3.tar.gz -C /opt && \
rm gitblit-1.9.3.tar.gz
EXPOSE 8080 8443
CMD ["/opt/gitblit-1.9.3/gitblit.sh"]
|
四、Gogs 部署
Gogs 是 Gitea 的"前辈"——Gitea 团队最初就是从 Gogs fork 出来的(2016 年 fork 原因是"想更积极地迭代")。
1
2
3
4
5
6
7
| docker run -d \
--name gogs \
--restart=always \
-p 13000:3000 \
-p 10022:22 \
-v /data/gogs:/data \
gogs/gogs:0.13
|
坑 5:Gogs 0.13 是最后的稳定版,2020 年后社区基本被 Gitea 吸引。新项目不建议选 Gogs——除非你正在维护一个老 Gogs 实例(数据迁移到 Gitea 是免费的)。
五、客户端提交前检查:p3c-pmd
“我自己在本地忘了跑规范检查”——这是最常见的"提交违规"原因。解决:把规范检查做成 git pre-commit 钩子,每次 git commit 自动跑。
5.1 准备 p3c-pmd 工具
1
2
3
4
5
| # 1. 下载 p3c-pmd 项目
git clone https://github.com/alibaba/p3c.git
cd p3c/p3c-pmd
mvn clean package
# 产出: p3c-pmd-2.1.1-jar-with-dependencies.jar
|
坑 6:必须用 p3c-pmd,不要用 pmd-dist——p3c-pmd 是阿里《Java 开发手册》对应的 PMD 规则集(命名规范、注释规范、OOP 规约、集合处理、并发处理等)。
5.2 手动跑一次(验证工具)
1
2
3
4
| java -cp p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
-d <PROJECT_DIR> \
-R rulesets/java/ali-comment.xml,rulesets/java/ali-concurrent.xml,rulesets/java/ali-constant.xml,rulesets/java/ali-exception.xml,rulesets/java/ali-flowcontrol.xml,rulesets/java/ali-naming.xml,rulesets/java/ali-oop.xml,rulesets/java/ali-orm.xml,rulesets/java/ali-other.xml,rulesets/java/ali-set.xml \
-f text -shortnames -no-cache -min 1
|
输出形如:
1
2
| /home/user/project/src/main/java/com/example/Foo.java:42: Avoid start with number for class name...
/home/user/project/src/main/java/com/example/Bar.java:88: Method name should be lowerCamelCase...
|
5.3 Maven 集成(开发期自动跑)
pom.xml 加:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| <properties>
<maven-pmd-plugin.version>3.21.2</maven-pmd-plugin.version>
<p3c-pmd.version>2.1.1</p3c-pmd.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>${maven-pmd-plugin.version}</version>
<configuration>
<verbose>true</verbose>
<rulesets>
<ruleset>/rulesets/java/ali-comment.xml</ruleset>
<ruleset>/rulesets/java/ali-concurrent.xml</ruleset>
<ruleset>/rulesets/java/ali-constant.xml</ruleset>
<ruleset>/rulesets/java/ali-exception.xml</ruleset>
<ruleset>/rulesets/java/ali-flowcontrol.xml</ruleset>
<ruleset>/rulesets/java/ali-naming.xml</ruleset>
<ruleset>/rulesets/java/ali-oop.xml</ruleset>
<ruleset>/rulesets/java/ali-orm.xml</ruleset>
<ruleset>/rulesets/java/ali-other.xml</ruleset>
<ruleset>/rulesets/java/ali-set.xml</ruleset>
</rulesets>
<printFailingErrors>true</printFailingErrors>
<minimumPriority>1</minimumPriority>
</configuration>
<executions>
<execution>
<id>pmd-check-verify</id>
<phase>package</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
<execution>
<id>pmd-pmd-site</id>
<phase>site</phase>
<goals>
<goal>cpd</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.alibaba.p3c</groupId>
<artifactId>p3c-pmd</artifactId>
<version>${p3c-pmd.version}</version>
</dependency>
</dependencies>
</plugin>
<!-- 自动安装 git pre-commit 钩子 -->
<plugin>
<groupId>io.github.phillipuniverse</groupId>
<artifactId>githook-maven-plugin</artifactId>
<version>1.0.5</version>
<executions>
<execution>
<goals>
<goal>install</goal>
</goals>
<configuration>
<hooks>
<pre-commit>
echo running validation build
exec mvn clean package -DskipTests
</pre-commit>
</hooks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
|
第一次 mvn package 时,githook-maven-plugin 会自动把 pre-commit 钩子装到 .git/hooks/pre-commit。之后每次 git commit 都会先跑 mvn clean package,失败就阻止 commit。
坑 7:minimumPriority=1 是 PMD 规则的"最高优先级"——只有"严重"问题会被拦下来。如果设为 5,所有问题都拦,开发体验会很差。建议从 1 起步,逐步放开到 2 / 3。
5.4 手动安装 pre-commit 钩子(不依赖 Maven)
1
2
3
4
5
6
7
8
9
10
11
12
13
| cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "[pre-commit] running p3c-pmd check"
java -cp /path/to/p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
-d . \
-R rulesets/java/ali-comment.xml,rulesets/java/ali-concurrent.xml,rulesets/java/ali-constant.xml,rulesets/java/ali-exception.xml,rulesets/java/ali-flowcontrol.xml,rulesets/java/ali-naming.xml,rulesets/java/ali-oop.xml,rulesets/java/ali-orm.xml,rulesets/java/ali-other.xml,rulesets/java/ali-set.xml \
-f text -shortnames -no-cache -min 1
if [ $? -ne 0 ]; then
echo "[pre-commit] p3c-pmd found violations, commit rejected"
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
|
六、服务端提交检查:pre-receive 钩子
客户端检查是"自愿"的——开发者完全可以 --no-verify 绕过。真正守门的是服务端 pre-receive 钩子——push 时服务端自动跑,违规直接 reject。
6.1 在 Gitea 容器内装 p3c
Gitea 默认镜像是 Alpine(没有 JDK),所以需要 gitea:1.23.5-jdk8 这个带 JDK8 的版本。
把 JDK 复制到 Gitea 容器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| docker cp /usr/local/jdk gitea:/usr/local/
docker exec -it gitea bash
# 容器内: 配置环境变量
vim /etc/profile
export JAVA_HOME=/usr/local/jdk/jdk1.8.0_371
export CLASSPATH=$JAVA_HOME/bin:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=.:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
source /etc/profile
# 容器内: 装 glibc(alpine 镜像需要)
sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
apk update
apk add glibc-2.35-r1.apk
apk add glibc-bin-2.35-r1.apk
apk add glibc-i18n-2.35-r1.apk
ln -sf /usr/glibc-compat/lib/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
# 验证
java -version
|
把 p3c-pmd 工具复制到容器:
1
2
3
4
5
| # 宿主机: 编译 p3c-pmd
cd p3c-pmd
mvn clean package
docker cp p3c-pmd-2.1.1-jar-with-dependencies.jar gitea:/
docker cp rulesets gitea:/var/opt
|
容器内跑一次(验证):
1
2
3
4
5
| java -Dfile.encoding=utf-8 -Dpmd.language=zh \
-cp /data/p3cjava/p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
-d /data/p3cjava/tmp \
-R /data/p3cjava/rulesets/java/ali-comment.xml,/data/p3cjava/rulesets/java/ali-concurrent.xml,... \
-f text -shortnames -no-cache
|
6.2 写 pre-receive 钩子
Gitea 仓库路径:/data/git/repositories/<org>/<repo>.git/hooks/pre-receive
1
| chmod 755 /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive
|
钩子脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| #!/usr/bin/env bash
# /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive
set -e
# 读取 push 信息
# 格式: <oldrev> <newrev> <refname>
while read oldrev newrev refname; do
# 找到这次 push 涉及的所有 .java 文件
if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
# 新分支 push
files=$(git diff-tree --root --no-commit-id --name-only -r $newrev | grep '\.java$' || true)
else
# 普通 push
files=$(git diff --name-only $oldrev $newrev | grep '\.java$' || true)
fi
if [ -z "$files" ]; then
continue
fi
# 复制这些文件到 p3c 检查目录
mkdir -p /data/p3cjava/tmp
for f in $files; do
mkdir -p "/data/p3cjava/tmp/$(dirname "$f")"
git show "$newrev:$f" > "/data/p3cjava/tmp/$f" 2>/dev/null || continue
done
# 跑 p3c-pmd
result=$(java -Dfile.encoding=utf-8 -Dpmd.language=zh \
-cp /data/p3cjava/p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD \
-d /data/p3cjava/tmp \
-R /data/p3cjava/rulesets/java/ali-comment.xml,/data/p3cjava/rulesets/java/ali-naming.xml,... \
-f text -shortnames -no-cache 2>&1 || true)
if [ -n "$result" ]; then
echo "$result"
echo ""
echo "p3c-pmd found violations, push rejected"
exit 1
fi
done
# 检查通过
exit 0
|
1
| chmod +x /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive
|
6.3 黑白名单机制
真实场景:某些"特殊项目"(如 demo 仓库、迁移中的老代码)暂时不想被 p3c 拦截——又不能让所有人手动去改钩子。
思路:
- 黑名单:默认所有项目都不拦截;只有
BLACKLIST 里的项目才走 p3c 检查 - 白名单:黑名单 + p3c 检查完后,如果在
WHITELIST 里 → 强制通过 - 用户白名单:某些核心开发(架构师)有"豁免权"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| #!/usr/bin/env bash
# /data/git/repositories/<org>/<repo>.git/hooks/pre-receive.d/pre-receive
set -e
REPO_NAME=$(basename $(pwd) .git) # 仓库名
USER_NAME=$(echo $GL_REPOSITORY | awk -F/ '{print $NF}')
BLACKLIST=("legacy-code" "demo-project" "old-billing")
WHITELIST=("template" "scaffolding")
USER_WHITELIST=("admin" "architect")
# 黑名单检查
should_check=false
for b in "${BLACKLIST[@]}"; do
if [ "$REPO_NAME" = "$b" ]; then
should_check=true
break
fi
done
if [ "$should_check" = false ]; then
exit 0
fi
# 用户白名单检查
for u in "${USER_WHITELIST[@]}"; do
if [ "$USER_NAME" = "$u" ]; then
echo "[pre-receive] $USER_NAME is in user whitelist, skip p3c check"
exit 0
fi
done
# 跑 p3c 检查(见 6.2)
# ...
|
坑 8:GL_REPOSITORY 是 Gitea 内置变量,只有在 Gitea 容器内跑的钩子才能拿到。如果在客户端写钩子用这个变量是空的。
七、常见排错
7.1 Gitea SSH 推送失败 “permission denied”
症状:git push 报 Permission denied (publickey)。
排查:
- 客户端 SSH 密钥是否加到 Gitea(
User Settings → SSH Keys) - Gitea 容器内
/data/git/.ssh 是否存在 - 容器端口映射是否对(
10022:22) - 客户端
~/.ssh/config 是否写错了 host 别名
7.2 p3c-pmd 报错 “java not found”
症状:Gitea 容器内跑 p3c 报 java: command not found。
解决:
- 用
gitea:1.23.5-jdk8 镜像(自带 JDK) - 或手动复制 JDK 进容器并配
JAVA_HOME(见 6.1)
7.3 pre-receive 钩子 “权限拒绝”
症状:钩子写好了,但 push 时没执行。
排查:
1
2
3
4
5
6
7
| # 1. 钩子文件是否可执行
ls -la /data/git/repositories/<org>/<repo>.git/hooks/pre-receive
# 必须是 -rwxr-xr-x
chmod +x /data/git/repositories/<org>/<repo>.git/hooks/pre-receive
# 2. Gitea 用户是否能写
chown -R git:git /data/git/repositories/<org>/<repo>.git/hooks
|
7.4 客户端 p3c 跑得慢
症状:每次 git commit 跑 p3c 要 30 秒+。
解决:
- 用
maven-pmd-plugin 自带的 cache 选项 - 缩小规则集(先只跑
ali-naming.xml 和 ali-comment.xml) - 在
pre-commit 脚本里加 git diff——只检查本次 commit 修改的文件,不扫全工程
1
2
| # 优化后的 pre-commit: 只检查 staged 文件
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.java$')
|
八、写在最后
Gitea / Gitblit / Gogs 三选一,绝大多数情况直接选 Gitea——社区活跃、CI 内置、UI 现代、迁移工具完善。配 p3c-pmd 做提交前检查才是真正的"省心"——把代码规范固化到提交链路里,而不是每次都"代码 review 时再吐槽"。
下一步建议:
- Gitea Actions:和 GitHub Actions 兼容的 CI,仓库根目录
.gitea/workflows/*.yml - Gitea 自定义主题:通过
templates/custom/ 改 logo / 配色 - Gitea → Gitea Federation:跨 Gitea 实例同步仓库(实验特性)
参考资料