入职第一天拉了23个仓库,我写了个脚本解放双手

当"下载代码"变成"等待戈多":一个Git批量操作的觉醒故事

------你有过这样的经历吗?新入职第一天,leader扔给你一个文档,上面列着23个Git仓库地址,说"先把代码都拉下来熟悉一下"。你看着终端里一个个git clone命令,陷入了沉思......


一、噩梦的开始:"新人的第一课"

2026年3月,我跳槽到了一家做金融科技的中型公司。

入职第一天,mentor张哥扔给我一个飞书文档,标题是《开发环境搭建指南》。我打开一看,第3节赫然写着:

3.1 克隆所有微服务仓库

请执行以下命令,将23个前端仓库克隆到本地:

bash 复制代码
git clone git@gitlab.company.com:frontend/order-service.git
git clone git@gitlab.company.com:frontend/user-service.git
git clone git@gitlab.company.com:frontend/payment-service.git
# ... 此处省略20行

我默默数了数,整整23个。

"张哥,这些都......必须拉吗?"

"必须的,而且每天早上要执行一次git pull --all,确保代码是最新的。"张哥头也不回,"对了,有些仓库有子模块(submodule),你记得加--recursive。"

我打开终端,开始机械地复制粘贴。

第一个仓库: 克隆成功,2.3GB(包含node_modules)。

第二个仓库: 克隆成功,1.8GB。

第三个仓库: 克隆到一半,网络超时,重新来。

第四个仓库: 发现有submodule,忘了加--recursive,重新来。

第五个仓库: 磁盘空间不足......

那天下午,我什么都没干,就在那里等着代码下载。整整4个小时,23个仓库终于全部躺在了我的硬盘里。

"入职第一天,收获23个文件夹,和一颗想辞职的心。"我在朋友圈里写道。

二、"人肉运维"的三个月

接下来的三个月,我成了团队的"Git管理员"。

每天早上9:00,准时执行:

bash 复制代码
cd ~/workspace/order-service && git pull --all && cd ..
cd ~/workspace/user-service && git pull --all && cd ..
# ... 重复23次

然后打开每个项目的.env文件,检查有没有新增的环境变量需要配置。偶尔有同事说"我这边CI构建失败了,帮我看看是不是依赖没更新",我得挨个检查各个仓库的版本。

"这也太原始了吧?"我跟张哥抱怨。

张哥叹了口气:"我也知道,但是没时间搞自动化啊。你看这23个仓库,有的用main分支,有的用master,还有两个用develop。有的有submodule,有的没有。有的用了Git LFS,有的没用......"

"就没有统一的规范吗?"

张哥沉默了三秒:"有,但那是五年前定的,现在大家各搞各的。"

我看了眼自己的终端,那里躺着一段手写的Shell脚本,已经迭代了6个版本,到处是if-else判断分支名、处理异常情况。我给它取名叫**"弗兰肯斯坦脚本"**。

三、导火索:那场"灾难性"的合并

真正的转机出现在6月的一个周五。

那天,CTO宣布公司要进行技术架构升级,所有微服务要统一从Webpack 4升级到Webpack 5,同时切换打包工具到Rspack。

"所有仓库,必须在两周内完成升级,并且保持功能不变。"

整个前端组炸了锅。

23个仓库,每个都要改配置、升级依赖、测试回归。更麻烦的是,有些仓库的依赖版本互相冲突------A服务需要React 17,B服务需要React 18,而C服务的组件库被A和B同时依赖。

张哥在会上提出:"我们必须建立一个'批量操作'机制,否则两周时间连改配置都改不完。"

"我的脚本该升级了。"我在角落里小声嘀咕。

四、破局:从"手动档"到"自动变速箱"

那天晚上,我决定不再忍受"人肉运维"了。

第一步:用"清单文件"管理所有仓库

我创建了一个repos.json,作为所有仓库的"户口本":

json 复制代码
{
  "repositories": [
    {
      "name": "order-service",
      "url": "git@gitlab.company.com:frontend/order-service.git",
      "branch": "main",
      "hasSubmodules": false,
      "nodeVersion": "18.17.0",
      "packageManager": "pnpm"
    },
    {
      "name": "legacy-user-service",
      "url": "git@gitlab.company.com:frontend/legacy-user-service.git",
      "branch": "master",
      "hasSubmodules": true,
      "nodeVersion": "16.14.0",
      "packageManager": "npm"
    }
    // ... 21个更多
  ]
}

"有了这个清单,所有操作都可以自动化了。"我兴奋地跟张哥说。

第二步:批量克隆 + 并行下载

传统的git clone是串行的,23个仓库一个接一个。我写了个Node.js脚本,用child_process实现并发克隆:

javascript 复制代码
const { exec } = require('child_process');
const repos = require('./repos.json');

async function cloneRepo(repo) {
  const cmd = `git clone ${repo.url} ${repo.hasSubmodules ? '--recursive' : ''}`;
  return new Promise((resolve, reject) => {
    exec(cmd, { cwd: './workspace' }, (err, stdout, stderr) => {
      if (err) reject(err);
      else resolve(stdout);
    });
  });
}

// 并发克隆,最大并行数5
const BATCH_SIZE = 5;
const batches = chunk(repos, BATCH_SIZE);

for (const batch of batches) {
  await Promise.all(batch.map(repo => cloneRepo(repo)));
  console.log(`✅ 完成一批,剩余 ${batches.length - idx} 批`);
}

效果: 原来4小时的串行克隆,现在40分钟就能全部完成(受限于网络带宽)。

第三步:批量拉取 + 状态报告

每天早上再也不用手动挨个cd了:

javascript 复制代码
async function pullAllRepos() {
  const results = [];
  
  for (const repo of repos) {
    const result = await pullRepo(repo);
    results.push({
      name: repo.name,
      status: result.status, // 'up-to-date' | 'updated' | 'conflict'
      message: result.message
    });
  }
  
  // 输出一份漂亮的报告
  console.table(results);
  
  // 如果有冲突,发送企业微信通知
  const conflicts = results.filter(r => r.status === 'conflict');
  if (conflicts.length > 0) {
    await sendWeChatAlert(`⚠️ ${conflicts.length}个仓库有冲突,请处理: ${conflicts.map(c => c.name).join(', ')}`);
  }
}

每天早上9:00,我的脚本自动执行,2分钟后终端输出一份漂亮的表格:

复制代码
┌───────────────┬────────────┬─────────────────────────┐
│ 仓库           │ 状态       │ 更新信息                │
├───────────────┼────────────┼─────────────────────────┤
│ order-service │ updated    │ 4 commits, +127/-35     │
│ user-service  │ up-to-date │ 无更新                   │
│ payment-srv   │ conflict   │ ❌ 合并冲突: package.json│
└───────────────┴────────────┴─────────────────────────┘

"卧槽,这也太爽了吧!"张哥第一次看到这个输出时惊呼。

第四步:批量执行命令

真正的杀手锏来了------在所有仓库上执行任意命令

javascript 复制代码
async function execInAllRepos(command, filter) {
  const targets = filter 
    ? repos.filter(repo => eval(filter)) 
    : repos;
  
  const results = await Promise.all(
    targets.map(async (repo) => {
      const start = Date.now();
      try {
        const { stdout, stderr } = await execAsync(
          `cd ./workspace/${repo.name} && ${command}`
        );
        return {
          repo: repo.name,
          success: true,
          output: stdout,
          error: stderr,
          duration: Date.now() - start
        };
      } catch (err) {
        return {
          repo: repo.name,
          success: false,
          error: err.message,
          duration: Date.now() - start
        };
      }
    })
  );
  
  // 汇总结果
  const success = results.filter(r => r.success);
  const failed = results.filter(r => !r.success);
  
  console.log(`✅ 成功: ${success.length}, ❌ 失败: ${failed.length}`);
  if (failed.length > 0) {
    console.log('失败的仓库:', failed.map(f => f.repo).join(', '));
  }
  
  return results;
}

有了这个,那场Webpack升级大战变得如此轻松:

bash 复制代码
# 在所有仓库安装pnpm
node batch.js --command "npm install -g pnpm"

# 更新所有仓库的Webpack依赖
node batch.js --command "pnpm add -D webpack@5 rspack@latest"

# 统一修改配置文件(用sed批量替换)
node batch.js --command "sed -i 's/webpack4/webpack5/g' webpack.config.js"

# 只修改使用React 17的仓库
node batch.js --command "pnpm upgrade react@18" --filter "repo.nodeVersion === '18.17.0'"

五、"番外篇":那些意外的惊喜

惊喜1:Git Submodule的"递归噩梦"

有个老仓库用了三层嵌套的submodule,每次更新都像俄罗斯套娃。我在脚本里加了git submodule update --init --recursive --remote,自动递归更新所有子模块,并检测子模块的commit变化。

惊喜2:Git LFS的大文件管理

有两个仓库用了Git LFS存储设计稿PSD文件(每个100MB+)。普通的git clone会慢死,我在脚本里检测到LFS仓库时,自动执行git lfs pull,并显示下载进度。

惊喜3:批量分支操作

产品经理突然说:"紧急需求,要在所有仓库创建一个hotfix分支!"

以前这种时候,我得手动切23次分支。现在一条命令搞定:

javascript 复制代码
// 在所有仓库创建并切换到hotfix分支
node batch.js --command "git checkout -b hotfix/20260315 && git push origin hotfix/20260315"

六、收获:不仅仅是"快"

三个月后,这个批量管理工具已经成了前端组的"基础设施"。

数据说话:

操作 优化前 优化后 提升
新员工克隆所有代码 4小时(手动) 40分钟(自动) 83% ↑
每日拉取所有仓库 30分钟 2分钟 93% ↑
批量依赖升级 2天(逐个) 30分钟(自动) 97% ↑
批量创建分支 30分钟 5秒 99% ↑

更重要的是,我们建立了一套统一规范

  • 所有仓库统一使用main分支(通过脚本批量重命名mastermain
  • 所有仓库统一使用pnpm作为包管理器
  • 所有仓库统一Node版本(通过.nvmrc文件)
  • 所有仓库统一Git钩子(通过husky

"你拯救了我们的效率。"张哥在一次组会上当众表扬我。

我笑了笑:"我只是把重复劳动交给了机器。"

七、技术方案复盘

核心架构

复制代码
┌─────────────────────────────────────────────────┐
│                 batch-git.js                    │
│         (Node.js + Commander.js CLI)           │
├─────────────────────────────────────────────────┤
│                                                 │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐        │
│  │ Clone   │  │ Pull    │  │ Exec    │        │
│  │ Manager │  │ Manager │  │ Manager │        │
│  └─────────┘  └─────────┘  └─────────┘        │
│       │            │            │               │
│       └────────────┼────────────┘               │
│                    │                            │
│            ┌───────▼───────┐                    │
│            │  repos.json   │                    │
│            │  (清单文件)    │                    │
│            └───────────────┘                    │
│                    │                            │
│         ┌──────────┴──────────┐                 │
│         ▼          ▼          ▼                 │
│    ┌────────┐ ┌────────┐ ┌────────┐           │
│    │服务A   │ │服务B   │ │服务C   │           │
│    │(main)  │ │(main)  │ │(master)│           │
│    └────────┘ └────────┘ └────────┘           │
└─────────────────────────────────────────────────┘

关键代码片段

1. 并发控制(避免同时太多git进程导致网络阻塞)

javascript 复制代码
class BatchManager {
  constructor(maxConcurrency = 5) {
    this.maxConcurrency = maxConcurrency;
    this.running = 0;
    this.queue = [];
  }
  
  async runBatch(tasks) {
    const results = [];
    const executing = [];
    
    for (const task of tasks) {
      const promise = this.executeTask(task).then(result => {
        results.push(result);
      });
      executing.push(promise);
      
      if (executing.length >= this.maxConcurrency) {
        await Promise.race(executing);
        // 移除已完成的
        // ...
      }
    }
    
    await Promise.all(executing);
    return results;
  }
}

2. 智能冲突检测与自动重试

javascript 复制代码
async function pullWithRetry(repo, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const result = await gitPull(repo);
      if (result.includes('CONFLICT')) {
        // 尝试自动解决(简单的合并冲突)
        await execAsync(`cd ${repo.path} && git mergetool --tool=vscode`);
      }
      return result;
    } catch (err) {
      if (i === maxRetries - 1) throw err;
      console.log(`⚠️ ${repo.name} 拉取失败,第${i+2}次重试...`);
      await sleep(2000 * (i + 1)); // 指数退避
    }
  }
}

3. 进度条显示(使用cli-progress库)

javascript 复制代码
const progress = new cliProgress.SingleBar({
  format: '克隆进度 |{bar}| {percentage}% | {value}/{total} 仓库',
  barCompleteChar: '\u2588',
  barIncompleteChar: '\u2591',
}, cliProgress.Presets.shades_classic);

progress.start(repos.length, 0);

for (const repo of repos) {
  await cloneRepo(repo);
  progress.increment();
}

progress.stop();

八、最后的彩蛋

工具上线两个月后,公司招了一批新实习生。

入职那天,张哥把《开发环境搭建指南》更新了,第3节只剩一行:

3.1 批量克隆所有仓库

bash 复制代码
npm run clone:all

然后可以去喝杯咖啡,回来代码就准备好了。

实习生小王看着终端里跳动的进度条,眼睛发亮:"这也太方便了吧!"

我在旁边默默喝了口咖啡,想起半年前那个焦头烂额的自己,笑了笑:

"欢迎来到新时代。"


附录:完整工具链推荐

工具 用途 推荐理由
Node.js + child_process 执行Git命令 原生支持,灵活性强
Commander.js CLI命令行解析 支持子命令,易扩展
cli-progress 进度条显示 视觉反馈好
chalk 彩色输出 让日志更清晰
figlet ASCII艺术字 启动时显示大标题
企业微信Webhook 通知推送 异常时即时告警

最佳实践清单:

  1. ✅ 用清单文件(repos.json)统一管理所有仓库元信息
  2. ✅ 并发执行,但控制最大并行数(建议5-8个)
  3. ✅ 失败重试 + 指数退避策略
  4. ✅ 结果汇总 + 彩色日志输出
  5. ✅ 关键操作(如删除分支)增加确认步骤
  6. ✅ 记录操作日志到文件,方便事后审计
  7. ✅ 支持按条件筛选仓库(如--filter "nodeVersion === '18'"
  8. ✅ 支持自定义命令(--exec "pnpm test"
相关推荐
Meya11272 小时前
实时采集 + 全域可视化,打造跨站点机房一体化U位管理方案
大数据·运维·人工智能
Gofarlic_OMS2 小时前
NX浮动许可调度黑名单机制,对比两款谁更合理
java·大数据·运维·开源·制造
Elasticsearch2 小时前
在不到一小时内将 Datadog Kubernetes 仪表板迁移到 Elastic Observability
elasticsearch
万岳科技系统开发2 小时前
智慧医院小程序开发推动医疗服务流程全面线上化
大数据·开发语言·人工智能
.柒宇.3 小时前
Elasticsearch 核心概念与系统架构详解
elasticsearch·系统架构
fajianchen4 小时前
主数据管理
大数据
慧新软件4 小时前
外贸GEO新思路:用客户真实痛点驱动内容生产
大数据
中科天工4 小时前
数智引领 载誉启航|中科天工亮相第一届包装行业数字化大会,以AI驱动智能工厂从“蓝图”到“标杆”
大数据·人工智能
2601_962932514 小时前
河南新流量科技GEO源码的口碑与资质情况
大数据·人工智能·科技