☰
单仓库多项目管理:目录设计、Git隔离、CI按需触发与迁移实践
2026/9/30 6:19:01 网站建设 项目流程

三年前我接手过一个挺典型的烂摊子:一个后台系统被拆成了 14 个仓库,前端、后端、几个公共库、部署脚本各占一个。平时看着挺清爽,真动手改一个跨模块的功能就露馅了——改一个公共库的字段,得连着发 5 个 PR,还得记住按顺序合并,顺序错了中间那半天 CI 全红。更难受的是版本对齐,A 项目锁的公共库版本是 1.2,B 项目锁的是 1.3,排查一个诡异的序列化问题时,我在三个仓库之间来回切分支,切到怀疑人生。

后来我把它们整合进了单个 GitHub 仓库,用目录来划分项目边界,也就是大家常说的单仓库多项目管理。整合完之后,跨模块改动变成一个 PR、一次评审、一条 CI 流水线,版本天然对齐。但代价也很实在:仓库体积变大、CI 容易全量触发、权限颗粒度变粗。这篇文章我就把这套东西从目录设计、Git 工作区隔离、多语言工具链、CI 按需触发、版本发布到踩坑排查,完整捋一遍。如果你手上正好是"几个互相关联的小项目"或者"一个产品拆成了前后端加公共库",这套方案大概率能省你不少事;如果你管理的是一堆互不相干的独立产品,那我还是劝你别硬凑。

1. 单仓库多项目到底解决什么问题

1.1 多仓库协作的三个真实痛点

先说清楚为什么要折腾这件事,不然很容易为了架构而架构。多仓库最典型的痛点是原子性缺失:一个功能需要同时改 A 仓库和 B 仓库,你只能拆成两个 PR,合并的时候必然存在一个窗口期,此时主干上是坏的。这种窗口期在业务低峰期还好,要是碰上线上热修,那就是灾难。

第二个痛点是依赖版本漂移。多仓库模式下,公共库的升级是一次"广播式"操作,你需要去每个消费方仓库里手动改版本号、跑测试、发 PR。真实情况往往是:谁有空谁升,最后仓库里同时存在三个大版本。我曾经在一个项目里发现同一个工具库被引了四个版本,打包产物直接大了 2MB。

第三个痛点是重复的工程配置。Lint 规则、格式化配置、CI 模板、提交规范钩子,每个仓库都要抄一份。抄完之后各改各的,一年后你会发现三个仓库的代码风格已经完全是三套标准了,新人接手时一脸懵。

单仓库把这些问题的解法统一成了:一次改动、一次评审、一次验证、一次合并。所有跨模块的变更在同一个提交里完成,主干永远是自洽的。

1.2 单仓库不是银弹:先判断你适不适合

单仓库不是把所有代码倒进一个文件夹那么简单,它有几个硬性代价,你得先接受。第一是克隆体积。项目多了以后,全量克隆动辄几百 MB 到几个 GB,新人入职第一天光等克隆就得半小时,这体验很差——后面我会讲怎么用按需拉取解决。

第二是权限颗粒度。这是最容易被忽略的一点:主流代码托管平台的读权限是仓库级的,不支持按目录授权。也就是说,只要一个人能读这个仓库,他就能读里面所有目录的代码。如果你们有外包团队、有严格的数据隔离要求,这一条基本就否决了单仓库方案。

第三是CI 资源浪费。默认情况下任何一次提交都会触发整条流水线,如果没做路径过滤,改一个文档也会把全部项目的测试跑一遍。

我的经验判断标准很朴素:如果这些项目共享代码、共享发布节奏、由同一批人维护,那单仓库收益远大于成本;如果它们只是被同一个团队"顺手"放在一起,之间没有任何依赖关系,那多仓库更省心。

2. 目录结构怎么设计才不会乱

2.1 三种常见的目录分层方案

目录结构决定了这个仓库能不能长期活下去。我见过三种主流分法,各有适用场景。

第一种是按类型分,顶层是apps/、packages/、services/、tools/。这种分法适合"一个产品 + 若干公共库"的结构,apps放可直接部署的应用,packages放可被引用的库,边界非常清晰。我个人最推荐这一种,因为它天然回答了一个问题:"这个目录里的东西能不能被别人依赖?"

第二种是按业务域分,顶层直接是业务模块名,比如order/、user/、payment/,每个业务目录下自带自己的前端、后端、数据库迁移。这种分法适合领域边界非常明确的团队,好处是改一个业务只需要动一个目录,坏处是公共能力的归属容易扯皮——工具函数放哪个业务域都不合适。

第三种是按团队分。我不太推荐,因为组织架构会变,目录结构跟着变意味着一次大规模重构,而且跨团队协作时代码到处乱飞。

选择哪种,本质上是在回答"我未来最频繁的改动是什么样的"。如果高频改动是"跨业务的公共能力升级",选第一种;如果高频改动是"某个业务内部迭代",选第二种。

2.2 命名与边界的硬性约定

目录定下来之后,必须有几条写进文档、并且能被工具强制的约定,否则半年后一定乱。

约定一:依赖方向单向。apps可以依赖packages,packages之间只能按层级依赖,绝不能出现packages/a依赖apps/web这种反向依赖。一旦出现反向依赖,说明你的抽象层级搞反了。

约定二:每个项目目录下必须有独立的清单文件。Node 项目要有自己的package.json,Java 模块要有自己的pom.xml或build.gradle,Python 要有自己的pyproject.toml。这是后面所有"按项目构建/测试"能力的基础。

约定三:跨项目的共享代码只能通过显式声明的包名引用,不允许用相对路径../../other-project/src/utils这种写法穿透目录。相对路径穿透是单仓库里最容易失控的坏味道,它让依赖关系变成隐形的,工具扫不出来,重构时一删就崩。

约定四:根目录只放全局配置。.gitignore、.editorconfig、README、CI 配置、版本管理工具的配置,就这些。根目录一旦开始堆脚本,说明有人在偷懒。

注意:这几条约定如果只写在文档里,基本等于没写。我的做法是在 CI 里加一个轻量的检查脚本,扫描跨目录的相对路径引用和反向依赖,命中就直接让流水线失败。人是有惰性的,只有机器拦得住。

2.3 一份可直接抄的模板结构

下面这个结构是我用得最多、也最稳的一套,你可以直接拿去改:

repo-root/ ├── .github/ │ ├── workflows/ │ ├── CODEOWNERS │ └── pull_request_template.md ├── apps/ │ ├── web-portal/ # 门户前端 │ └── admin-console/ # 管理后台 ├── services/ │ ├── user-service/ │ └── order-service/ ├── packages/ │ ├── ui-kit/ # 通用组件 │ ├── shared-types/ # 类型定义 │ └── utils/ # 通用工具 ├── tools/ │ └── scripts/ # 构建、发布、检查脚本 ├── docs/ ├── .editorconfig ├── .gitignore ├── package.json # 工作区根清单 ├── pnpm-workspace.yaml └── README.md

这里有两个细节值得说。packages/和services/分开,是因为它们的产出物形态完全不同:包是被依赖的,服务是被部署的,混在一起后期很难做发布策略。另外tools/scripts独立出来,是因为这些脚本会被 CI 反复调用,放在根目录会让根目录越来越脏。

3. 让 Git 只认你要的那部分

3.1 sparse-checkout 解决"克隆太大"

仓库合并之后第一个撞上的问题就是克隆慢。解决办法是稀疏检出,让 Git 只把指定目录落到工作区,其他目录在本地根本不存在。

现在推荐用 cone 模式,配置简单且性能好:

git clone --filter=blob:none --sparse https://github.com/your-org/your-repo.git cd your-repo git sparse-checkout set apps/web-portal packages/ui-kit

这三条命令的含义:--filter=blob:none是部分克隆,只拉取提交历史和目录树,文件内容按需下载;--sparse开启稀疏检出;sparse-checkout set声明你真正需要的目录。

实测下来,一个 2GB 的仓库用这套组合,首次克隆能压到 100MB 以内,时间从二十多分钟降到两分钟出头。日常开发你只会下载你碰过的文件,剩下的永远不会落盘。

有一点必须提醒:稀疏检出只影响工作区,不影响提交历史。也就是说git log依然能看到全仓库的历史,git checkout依然能切到任意分支。它是"我本地只要这几个目录",不是"我只能访问这几个目录"。很多人误以为它能当权限用,这是错的。

3.2 git worktree 解决"同时开多个分支"

单仓库的另一个麻烦是切分支。以前多仓库时,A 项目切到 feature 分支,B 项目还在 main,互不干扰。合成一个仓库后,切分支是全局的,正在跑的服务会被打断。

git worktree就是为这个场景准备的,它允许你把同一个仓库的不同分支同时检出到不同目录:

# 主工作区在 ~/repo,检出 main # 新建一个工作区专门跑 feature 分支 git worktree add ../repo-feature feature/order-refactor # 再开一个专门用来跑构建验证 git worktree add ../repo-test release/1.8

三个目录共享同一个.git对象库,磁盘占用远小于克隆三份。并且不会出现"我在 A 目录 commit 完切分支忘了 push"这种低级事故,因为它们物理上是隔离的。

注意:同一个分支不能被两个 worktree 同时检出,这是硬限制。用完记得git worktree remove ../repo-feature清理,不然会残留一堆目录和元数据。我踩过一次坑,攒了十几个 worktree 之后git status开始变慢,排查半天才发现是这个原因。

3.3 浅克隆与按需拉取的正确姿势

如果你的目的只是临时看一眼代码或者跑一次构建,连历史都不需要,那用浅克隆最划算:

git clone --depth 1 https://github.com/your-org/your-repo.git

--depth 1只拉最近一次提交,速度快到离谱,适合 CI 环境。但要注意浅克隆的仓库不能直接做很多历史操作,比如git blame跨历史、git log找老提交都会失败。

如果 CI 里需要做路径变更检测(判断这次提交改了哪些项目),那就得至少知道上一次提交:

git clone --depth 2 --filter=blob:none <url>

--depth 2让你能用git diff HEAD~1 --name-only拿到变更文件列表。这个技巧我在好几条流水线里都用过,比全量克隆快得多,而且足够支撑大多数变更检测逻辑。

4. 各语言生态的多项目工具链

4.1 JS/TS:workspaces 三家怎么选

前端生态对单仓库的支持是最成熟的,npm、yarn、pnpm 都有自己的 workspace 机制。核心逻辑都一样:根目录声明工作区范围,各子项目通过包名互相引用,安装时自动做软链接,不用手动npm link。

pnpm 的配置最简洁,一个pnpm-workspace.yaml就够:

packages: - 'apps/*' - 'services/*' - 'packages/*'

然后在根package.json里写脚本:

{ "scripts": { "build": "pnpm -r --filter './packages/**' run build", "dev:web": "pnpm --filter web-portal run dev", "test:changed": "pnpm -r --filter '[HEAD^1]' run test" } }

--filter '[HEAD^1]'这个语法是 pnpm 的杀手锏,它会自动识别相对于上一次提交发生变化的包,只在这些包里跑脚本。这一条直接把单仓库最容易被诟病的"改一处跑全量"问题解决了一大半。

三家工具的差异我简单总结一下:npm workspaces 胜在零依赖,但过滤能力弱;yarn 的 PnP 模式能省磁盘,但生态兼容性偶尔出问题;pnpm 的过滤语法最灵活、安装最快、磁盘占用最低,我目前默认选它。唯一的坑是node_modules是符号链接结构,极少数老工具会不认,遇到时加node-linker=hoisted就行。

4.2 Java:Maven 多模块与 Gradle 多项目

Java 这边其实早就有成熟方案,只是大家平时习惯一个仓库一个服务。

Maven 用父 POM 声明模块:

<project> <groupId>com.example</groupId> <artifactId>platform-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>services/user-service</module> <module>services/order-service</module> <module>packages/shared-types</module> </modules> </project>

关键命令是-pl和-am的组合:

# 只构建 order-service 以及它依赖的模块 mvn -pl services/order-service -am clean package

-pl指定要构建的模块,-am(also make)会自动把它依赖的模块一起构建。这个组合在 CI 里非常实用——变更检测算出改动的模块列表,直接喂给-pl,就能做到精准构建。

Gradle 更简单,settings.gradle里 include 一下:

rootProject.name = 'platform' include 'services:user-service' include 'services:order-service' include 'packages:shared-types'

Gradle 有个优势是配置缓存和构建缓存,配合--build-cache,在单仓库里效果特别明显:上游模块没变时,下游直接命中缓存,跳过编译。

实操心得:Java 多模块最容易踩的坑是依赖版本冲突。单仓库里所有模块共享一个父 POM 的dependencyManagement,好处是版本统一,坏处是升级一个库会同时影响所有模块。我的建议是升级依赖时先跑全量mvn dependency:tree,看看有没有模块被间接影响,别等到上线才发现序列化行为变了。

4.3 Python 与 Go 的做法

Python 的 workspace 支持一直比较弱,直到 uv 和 poetry 出现才好转。uv 的 workspace 配置长这样,写在根pyproject.toml:

[tool.uv.workspace] members = ["services/*", "packages/*"]

子项目之间用 path 依赖互相引用:

[project] dependencies = ["shared-utils"] [tool.uv.sources] shared-utils = { workspace = true }

这套配置的好处是,改shared-utils的代码,所有引用它的服务在本地跑测试时立刻生效,不需要重新安装。

Go 的方案是go.work,这个文件通常不提交到仓库,它是开发者本地的便利工具:

go 1.22 use ( ./services/user-service ./services/order-service ./packages/shared-types )

有了go.work,多个 module 之间的互相引用就不需要replace指令了,本地开发体验和单 module 几乎一样。但记住.gitignore里要加上它,因为 CI 环境一般不需要,提交了反而会造成构建行为不一致。

5. CI/CD 与权限:只跑改动的项目

5.1 用 paths 过滤触发范围

单仓库 CI 的第一原则:没变的项目一次都不该跑。

最基础的用法是工作流级别的路径过滤:

name: web-ci on: push: branches: [main] paths: - 'apps/web-portal/**' - 'packages/ui-kit/**' - '.github/workflows/web-ci.yml' pull_request: paths: - 'apps/web-portal/**' - 'packages/ui-kit/**'

注意packages/ui-kit也要列进去,因为公共库变了,依赖它的应用必须重新验证。这一点特别容易漏,很多人只写自己应用目录,结果公共库改完没触发测试,问题直接上主干。

但paths有个硬限制:它在工作流级别生效,一旦某个工作流因为路径匹配被触发,里面所有 job 都会跑。如果你需要在 job 级别做更细的过滤,就得用变更检测动作。

jobs: detect: runs-on: ubuntu-latest outputs: web: ${{ steps.filter.outputs.web }} api: ${{ steps.filter.outputs.api }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | web: - 'apps/web-portal/**' - 'packages/ui-kit/**' api: - 'services/**' - 'packages/shared-types/**' build-web: needs: detect if: needs.detect.outputs.web == 'true' runs-on: ubuntu-latest steps: - run: echo "构建前端"

这套结构的价值在于,十几个项目共用一个仓库,每次提交真正跑的只有受影响的那一两个,CI 分钟数能省下七八成。

5.2 缓存策略与并行度调优

单仓库的缓存配置比多仓库复杂,因为依赖清单位置分散。核心原则是缓存键要绑定到具体项目的锁文件,不要用全仓库的 hash,否则改一个无关项目就会让所有缓存失效。

- uses: actions/cache@v4 with: path: ~/.local/share/pnpm/store key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | pnpm-${{ runner.os }}-

restore-keys是关键的兜底:精确 key 没命中时,会退化到前缀匹配的旧缓存,虽然不是最新但比空缓存快得多。

并行度方面,如果项目之间没有依赖关系,用矩阵构建能显著缩短总时长:

strategy: matrix: project: [web-portal, admin-console, ui-kit] fail-fast: false

fail-fast: false建议打开,否则一个项目失败会取消其他正在跑的任务,你就看不到完整的问题列表了,还得重跑一遍。

另外强烈建议加并发控制,避免连续推送导致一堆流水线排队:

concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true

5.3 CODEOWNERS 与分支保护

单仓库没法按目录控制读权限,但可以按目录控制评审必须由谁来做,这就是CODEOWNERS的价值。

# 默认归属 * @platform-team # 各项目独立 /apps/web-portal/ @frontend-team /services/ @backend-team /packages/ui-kit/ @frontend-team @design-system /.github/workflows/ @platform-team

配置好之后,改动packages/ui-kit的 PR 会自动请求前端和设计体系团队评审,跨团队影响面一目了然。配合分支保护规则里的"要求代码所有者评审",就形成了事实上的目录级准入控制。

我还会把.github/workflows/单独指派给平台组。因为工作流文件权限很大,能被改就意味着能执行任意脚本,这条线必须守住。

6. 版本、发布与产物管理

6.1 tag 命名约定:给每个项目独立命名空间

单仓库里 tag 是全局的,v1.0.0这种命名会立刻冲突。必须建立带前缀的命名空间:

web-portal-v2.3.1 user-service-v1.8.0 ui-kit-v0.9.4

前缀和目录名保持一致,看到 tag 就知道是哪个项目。发布脚本里用git tag -l 'web-portal-v*'就能筛选出某个项目的历史版本。

版本号的推进策略有两种。统一版本是所有项目共享一个版本号,优点是简单、对齐容易;缺点是一个小改动也会让所有项目版本号跳一次,用户看着很困惑。独立版本是每个项目各自演进,灵活但需要更强的自动化支撑。我倾向后者,但前提是你得有一套自动化的变更记录工具。

6.2 独立版本号的落地方式

前端生态用 changesets 比较顺手。开发者提交 PR 时,同时提交一个描述变更的 markdown 文件,标明影响的包和版本递增类型;合并到主干后,机器人自动开一个"版本发布 PR",把各包的版本号和 CHANGELOG 一起更新。人只需要点一次合并,然后流水线自动打 tag、发产物。

Java 生态的对应方案是flatten-maven-plugin配合版本插件,或者干脆用统一版本省事。Python 用hatch-vcs从 git tag 推导版本号,避免手写版本号导致的不一致。

这里有个必须提前处理的坑:CHANGELOG 文件冲突。如果每个项目各自维护一个 CHANGELOG,且由自动化工具追加内容,多分支并行时冲突概率极高。我的处理方式是让机器人独占这个文件的写入权,人只在 PR 里写变更描述,绝不手改 CHANGELOG。人工介入越少,冲突越少。

7. 常见问题与排查速查

7.1 仓库体积失控怎么救

这是单仓库最大的长期风险。体积失控通常来自三个地方:不小心提交的构建产物、二进制资源文件、以及大量已被删除但仍在历史里的文件。

先定位问题:

# 找出仓库里最大的 10 个对象 git rev-list --objects --all \ | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \ | awk '/^blob/ {print $3, $4}' \ | sort -rn | head -10

找到元凶后,如果只是新提交的,用.gitignore加git rm --cached解决就行。如果是历史遗留的,就得重写历史了,git filter-repo是现在推荐的工具:

# 从所有历史中彻底删除某个体积很大的目录 git filter-repo --path dist/ --invert-paths

注意:重写历史会改变所有提交的哈希值,所有协作者都必须重新克隆,已有的本地分支全部作废。这件事要提前通知、选在低峰期做,最好冻结主干半天。我做过一次没提前通知的,结果三个同事的分支全部报废,重做了一下午。

日常预防比事后补救重要得多。我建议在 CI 里加一条检查:单次提交新增文件总大小超过 5MB 就警告,超过 20MB 就失败,强制走大文件存储或者外部制品仓库。

7.2 CI 误触发和漏触发的排查表

这两类问题在单仓库里出现频率最高,我把常见的成因整理成了一张表:

现象常见原因排查动作
改文档也跑全量 CI工作流没配paths检查on.push.paths是否缺失
公共库改了但应用没测应用工作流没把公共库路径列进去补packages/**到paths
目录改名后 CI 不再触发工作流的路径规则还是旧目录名全局搜索旧路径,重命名时同步改
同一分支连续推送跑多次缺concurrency配置加cancel-in-progress: true
矩阵任务一个失败全取消fail-fast默认为 true设为 false 看完整结果
缓存一直不命中key 用了全仓库 hash改为绑定到具体锁文件

这张表基本覆盖了我两年里遇到的所有情况。其中"目录改名后 CI 不触发"是最阴险的,因为流水线不报错,只是静默地不跑,你以为测试通过了,实际上根本没执行。规避方法很简单:任何人重命名目录,必须同步搜索.github/workflows/下所有引用。

7.3 冲突与合并策略的取舍

单仓库的合并冲突比多仓库频繁,因为大家改的是同一个仓库、同一个主干。缓解手段主要有三个。

第一,缩短分支生命周期。分支开得越久,和主干的差异越大,冲突越难解。我们内部约定是功能分支不超过三天,超过就拆小。

第二,用 rebase 而不是 merge 保持主干线性。线性历史让git bisect和变更检测都更可靠。做法是提交前先git fetch && git rebase origin/main,解决完冲突再推。

第三,给高频冲突文件加保护。像锁文件、CHANGELOG、根配置文件这类,尽量让机器人独占写入。锁文件冲突的处理方式是直接删掉重装:

git checkout --theirs pnpm-lock.yaml pnpm install git add pnpm-lock.yaml

不要试图手工合并锁文件,那是在浪费时间。

8. 实操:从零把一个仓库改造成多项目

8.1 迁移前的准备工作

如果你手上已经有几个独立仓库想合并,别急着动手,先做三件事。

确认工具链版本一致。三个仓库可能用的是三个不同的大版本 Node,合并后同一套 CI 跑不了。先统一到同一个版本,写进.nvmrc或.tool-versions。

确认没有循环依赖。A 依赖 B、B 又依赖 A 的情况在多仓库里可以通过不同版本共存来绕过,合并之后会直接构建失败。用依赖分析工具扫一遍,提前拆开。

统一命名。三个仓库都叫common是常见情况,合并时必须改名。改名会影响所有引用点,建议先在各仓库内部改完、跑通,再做物理合并。

8.2 保留历史的合并步骤

合并最怕丢掉提交历史,出问题时没法追溯。推荐用git subtree,它能把另一个仓库的历史完整搬进指定目录:

# 在目标单仓库里执行 git remote add legacy-web https://github.com/your-org/web-portal.git git fetch legacy-web git subtree add --prefix=apps/web-portal legacy-web main --squash

--squash会把对方的历史压缩成一个提交。如果你要求完整保留逐条历史,去掉这个参数,但代价是提交数量会暴涨,git log变得难以阅读。我的建议是:核心业务仓库保留完整历史,工具类仓库用--squash压缩,因为那些历史价值不大。

如果仓库数量多,用git filter-repo的目录重写功能更高效:

# 在待迁移仓库的克隆副本里执行,把所有内容移到子目录下 git filter-repo --to-subdirectory-filter apps/web-portal

这样整个仓库的所有文件都会被加上apps/web-portal/前缀,历史完整保留,然后作为远程分支拉进目标仓库即可。注意这个操作会改写所有提交哈希,务必在克隆副本上做,不要动原始仓库。

8.3 迁移后的验证清单

合并完成不等于迁移成功,下面这份清单我每次都会走一遍:

  1. 全量克隆能过。让一个没参与迁移的同事从零克隆一次,跑通构建,这是最真实的验证。
  2. CI 全部工作流至少触发一次。挨个改动每个项目下的一个文件,确认对应的流水线被正确触发,其余的不被触发。
  3. 依赖引用全部改为包名。全局搜索../形式的跨项目相对引用,逐个替换。
  4. tag 命名空间检查。确认没有残留的v1.x无前缀 tag,避免后续发布脚本误判。
  5. CODEOWNERS 生效验证。开几个测试 PR,确认评审人自动指派正确。
  6. 归档旧仓库。在旧仓库 README 顶部写明"已迁移至 xxx,本仓库只读",然后设为归档。别直接删,总有人的本地还指着它。

最后分享一个我在实际使用中的体会:单仓库改造最容易失败的地方不在技术,而在迁移的节奏。我见过一个团队想一次性把二十多个仓库全并进来,结果搞了两个月,中间主干一直处于半坏状态,最后项目黄了。稳妥的做法是先并两个关联度最高的,跑满一个月,把 CI 模板、目录规范、发布流程全跑顺,再逐个往里搬。每搬一个都是低风险操作,心态也稳得多。

另外还有一个容易被忽略的小细节:合并之后记得给新人准备一份"这个仓库里有什么"的导览文档,讲清楚每个目录做什么、怎么只拉需要的部分、本地怎么跑起来。单仓库对老成员是便利,对新人反而是门槛——他第一次打开会看到十几个项目的代码铺在面前,比看单个仓库更懵。这份文档花你半小时写,能省掉后面无数次的重复答疑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询