git 多分支实现上传文件但避免冲突检测

文章目录

背景

  • 对于某些通过命令生成的配置文件(如 TypeScript 类型文件等),需要上传,但不需要做冲突检测

实现步骤

  • 专门维护一个分支 B 用于上传不需要检测的文件 .gitignore 不需要忽略,然后在其它分支配置 .gitignore 忽略不需要检测的文件
  • 每次运行命令后,在 B 分支生成最新文件后提交到 B 分支远程,然后再自动切换回 A 分支,这样文件就会保存在本地且不会被冲突检测,不影响其它分支提交
  • 在新的开发启动项目时,运行命令就能够将远程 B 分支维护的文件放在本地,从而正常运行
const fs = require('fs');
const fsex = require('fs-extra');
const chalk = require('chalk');
const globAll = require('glob-all');
const path = require('path');
const { spawn, exec } = require('child_process');

const Hook = {
  beforeGernerate: 'beforeGernerate',
  afterGernerate: 'afterGernerate',
};

// 命令和参数
const command = 'npx';
// 输出文件夹
const outputDir = path.relative(process.cwd(), './src/api_backup');
// 拷贝输出文件夹给其它分支使用
const copyDir = path.relative(process.cwd(), './src/api');
const cacheDir = path.relative(process.cwd(), './stale');

let originalBranch = '';
const targetBranch = 'feature/only_types';

const args = [
  'openapi-typescript-codegen',
  '--input',
  'https://xxxx',
  '--output',
  cacheDir,
  '--client',
  'node',
  '--exportCore',
  false,
  '--exportServices',
  false,
  '--exportModels',
  true,
];

// const spinner = (text) => {
//   const chars = '|/-\\';
//   let x = 0;
//   const id = setInterval(() => {
//     console.clear();
//     console.log(chalk.green(text), chalk.green(chars[x++]));
//     x &= 3;
//   }, 250);

//   return (text) => {
//     clearInterval(id);
//     console.log(chalk.yellow(text));
//   };
// };

const tipTimer = setTimeout(() => {
  // stopStart();
  console.log(
    `${chalk.red(
      'Network loading failed or did not log in to Swagger documentation. Please'
    )} ${chalk.yellow('1. Exit the process')} ${chalk.yellow(
      '2. Log in to the Swagger document'
    )} ${chalk.yellow('3. try run "npm install" again')} ${chalk.yellow(
      '4.try run "npm run type" again'
    )}`
  );
}, 20000);

// const stopStart = spinner('Command executing');

fetchSwagger();

async function fetchSwagger() {
  console.log(chalk.green('Command executing'));
  checkoutBranch();
  const process = spawn(command, args, { stdio: 'inherit' });
  process.on('close', (code) => {
    if (code === 0) {
      ensureAndMakeDir();
      incrementalUpdate(cacheDir, outputDir);
    } else {
      console.log(chalk.red(`Command failed with code ${code}`));
    }
    clearTimeout(tipTimer);
  });
}

async function incrementalUpdate(latestFolder, staleFolder) {
  console.log(chalk.green('Incremental updating ...'));
  const cacheFiles = globAll.sync(latestFolder + '/**/*') || [];

  try {
    const promises = cacheFiles.map((file) => {
      const relativePath = path.relative(latestFolder, file);
      const file1 = path.join(latestFolder, relativePath);
      const file2 = path.join(staleFolder, relativePath);
      return new Promise((resolve, reject) => {
        fs.stat(file1, (err, stats) => {
          if (err) return reject(err);
          if (stats.isFile()) {
            if (fs.existsSync(file2)) {
              compareAndUpdate(file1, file2)
                .then((result) => {
                  resolve(result);
                })
                .catch((err) => reject(err));
            } else {
              fs.copyFile(file1, file2, (err) => {
                if (err) return reject(err);
                resolve(`Added ${file} to ${staleFolder}`);
              });
            }
          } else {
            resolve();
          }
        });
      });
    });
    return await Promise.all(promises)
      .then((results) => {
        results.forEach((result) => {
          if (result) console.log(result);
        });
        rmCacheDir();
        copyOutPutToApi();
      })
      .catch((err) => {
        rmCacheDir();
        console.log(chalk.red(`Incremental update failed! ${err}`));
      });
  } catch (error) {
    rmCacheDir();
    console.log(chalk.red(`Incremental update failed! ${error}`));
  }
}

// 比较文件内容并更新或新增文件
function compareAndUpdate(file1, file2) {
  return new Promise((resolve, reject) => {
    fs.readFile(file1, 'utf8', (err, data1) => {
      if (err) return reject(`Updated ${file2} with content from ${file1} failed!`);
      fs.readFile(file2, 'utf8', (err, data2) => {
        if (err) return reject(`Updated ${file2} with content from ${file1} failed!`);

        const content1 = data1.replace(/\s/g, '');
        const content2 = data2.replace(/\s/g, '');

        if (content1 !== content2) {
          fs.copyFile(file1, file2, (err) => {
            if (err) return reject(`Updated ${file2} with content from ${file1} failed!`);
            resolve(`Updated ${file2} with content from ${file1}`);
          });
        } else {
          resolve();
        }
      });
    });
  });
}

function rmCacheDir() {
  if (fs.existsSync(cacheDir)) {
    fs.rm(cacheDir, { recursive: true, force: true }, (err) => {
      if (err) {
        console.error(err.message);
        return;
      }
      console.log(chalk.green(`Remove stale successfully!`));
    });
  }
}

function rewriteExportToIndex() {
  const latestFiles = globAll.sync(outputDir + '/models/**/*') || [];
  let str = '';
  latestFiles.forEach((item) => {
    const filename = path.basename(item, '.ts');
    str += `export type { ${filename} } from './models/${filename}'; \n`;
  });
  fs.writeFile(outputDir + '/index.ts', str, (err) => {
    if (err) {
      console.error('write failed:', err);
      return;
    }
  });
  fs.writeFile(copyDir + '/index.ts', str, (err) => {
    if (err) {
      console.error('write failed:', err);
      return;
    }
    commitAndResetBranch();
    console.log(chalk.green(`Incremental update successfully!`));
  });
}

function ensureAndMakeDir() {
  if (!fs.existsSync(outputDir)) {
    console.log(chalk.yellow("src/api_backup/models/ directory does not exist! let's create it"));
    fs.mkdirSync(outputDir + '/models', { recursive: true });
  }
  if (!fs.existsSync(copyDir)) {
    console.log(chalk.yellow("src/api/models/ directory does not exist! let's create it"));
    fs.mkdirSync(outputDir + '/models', { recursive: true });
  }
}

async function copyOutPutToApi() {
  try {
    await fsex.copy(outputDir, copyDir);
    rewriteExportToIndex();
  } catch (error) {
    console.log(chalk.red('copy output dir error:', error));
    rmCacheDir();
  }
}

function execAsync(command, hookName) {
  return new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        if (stderr.includes('CONFLICT')) {
          console.log(
            chalk.red(
              'A conflict occurs when pulling a remote branch. Please manually resolve it first!'
            )
          );
          reject(new Error('Git conflict'));
        } else {
          if (hookName === Hook.afterGernerate) {
            if (
              stdout.includes('无文件要提交') ||
              stdout.includes('nothing to commit') ||
              stdout.includes('working tree clean') ||
              stdout.includes('clean workspace')
            ) {
              resolve({ stdout, stderr });
              return;
            }
          }
          reject(error);
        }
        console.log(chalk.red('exec command error:', error, stderr, stdout));
        if (hookName === Hook.beforeGernerate) {
          process.exit(1);
        }
        return;
      }
      resolve({ stdout, stderr });
    });
  });
}

async function checkoutBranch() {
  try {
    const { stdout: currentBranch } = await execAsync(
      'git branch --show-current',
      Hook.beforeGernerate
    );
    originalBranch = currentBranch.trim();
    console.log(`Current branch is: ${originalBranch}`);

    const { stdout: status } = await execAsync('git status --porcelain', Hook.beforeGernerate);
    if (status.trim()) {
      console.log(
        chalk.yellow('There are currently uncommitted changes, please commit them first!')
      );
      process.exit(1);
      return;
    }
    console.log('Pull origin branch');
    const { stdout: pullLog } = await execAsync('git pull origin', Hook.beforeGernerate);
    console.log(pullLog);

    await execAsync(`git checkout ${targetBranch}`, Hook.beforeGernerate);
    console.log(chalk.yellow(`Now current branch is: ${targetBranch}`));
  } catch (error) {
    console.log(chalk.red(`Something Wrong! Reset Branch!`));
    await execAsync(`git checkout ${originalBranch}`);
    process.exit(1);
  }
}

async function commitAndResetBranch() {
  try {
    const commands = [
      `git add ${outputDir}`,
      `git commit -m "feat: auto commit types" --no-verify`,
      `git push origin ${targetBranch}`,
    ];
    // // await execAsync(`git add ${outputDir}`, Hook.beforeGernerate);
    // // const { stdout, stderr }=await execAsync(`git commit -m "feat: auto commit types" --no-verify`, Hook.beforeGernerate);
    // // console.log(chalk.green(`auto commit!`),stdout, stderr);
    // // await execAsync(`git push origin ${targetBranch}`, Hook.beforeGernerate);
    // // console.log(chalk.green(`auto push!`));

    try {
      console.log(chalk.green(`auto add、commit、push!`));
      await execAsync(commands.join(' && '), Hook.afterGernerate);
      console.log(chalk.green(`auto add、commit、push successfully!`));
    } catch (error) {
      console.log(chalk.red('auto commit error', error));
    }
    await execAsync(`git checkout ${originalBranch}`, Hook.afterGernerate);
    console.log(chalk.yellow(`Now current branch is: ${originalBranch}`));
  } catch (error) {
    console.log(chalk.red(`Something Wrong when commit! Reset Branch!`, error));
    await execAsync(`git checkout -- ${outputDir}`, Hook.afterGernerate);
    await execAsync(`git checkout ${originalBranch}`, Hook.afterGernerate);
    rmCacheDir();
  }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值