git submodule、git subtree 介绍及差异见: submodule、subtree方案详解
一、子仓库难点,分支操作不同频
子仓库分支与主仓库分支不会主动关联,不便管理,开发中需要较为小心
二、解决方式
增加node脚本,支持主仓库子仓库同频分支操作,保证主、子分支的一致性
三、同频操作分支源码及使用介绍
-
介绍
支持分支创建、切换、合并、拉取、创建Tags的同频操作
-
操控面板
-
脚本目录结构
-
⚠️使用步骤
-
初始化子仓库
git submodule add [子仓库git地址] -
增加packagen.json的script配置
"scripts": { "branch": "node bin/branch.js" },
-
修改bin/branchMode/index.js 内的submoduleName变量
替换为子仓库的文件夹名称 -
运行脚本
npm run branch
-
-
bin/branch.js
/** * 用于主仓库 & 子仓库 的分支同步 */ const { checkGitStatus, slcBranchType, BRANCH_CHECKOUT_TYPE } = require('./branchMode/index') const createBranch = require('./branchMode/createBranch.js') const toggleBranch = require('./branchMode/toggleBranch.js') const mergeBranch = require('./branchMode/mergeBranch.js') const pullBranch = require('./branchMode/pullBranch.js') const createBranchTags = require('./branchMode/createBranchTags.js') /** * 二、branch操作 */ const main = async() => { // 工作空间干净才允许执行操作 await checkGitStatus() // git操作类型选择 const featType = await slcBranchType() switch (featType) { // 创建 case BRANCH_CHECKOUT_TYPE.create: createBranch() break // 切换 case BRANCH_CHECKOUT_TYPE.toggle: toggleBranch() break // 合并 case BRANCH_CHECKOUT_TYPE.merge: mergeBranch() break // 拉取 case BRANCH_CHECKOUT_TYPE.pull: pullBranch() break // 创建Tags case BRANCH_CHECKOUT_TYPE.pushTags: createBranchTags() break default: break } } module.exports = main()
-
bin/util.js
const inquirer = require('inquirer') const execa = require('execa') function log() { const str = [...arguments].join(', ') console.warn( `\n\u001b[33m ${str} \u001b[39m\n` ) } function logGreen() { const str = [...arguments].join(', ') console.warn( `\n\u001b[32m ${str} \u001b[0m\n` ) } function inputPrompt(msg, backup = null) { return inquirer.prompt([{ name: 'detail', type: 'input', default: backup, message: msg }]).then(({ detail }) => detail) } function confirmPrompt(msg, backup = true) { return inquirer.prompt([{ name: 'yes', type: 'confirm', default: backup, message: msg }]).then(({ yes }) => yes) } function selectPrompt(msg, list = [], backup = 0) { return inquirer.prompt([{ name: 'select', type: 'list', choices: [ new inquirer.Separator(' '), ...list, new inquirer.Separator(' ') ], default: backup, message: msg }]).then(({ select }) => select) } /** * 获取本地分支列表 * @param {Boolean} isOrigin 查看远程分支 * @param {String} cwd 执行路径 * @returns branchList */ const getBranchList = async(isOrigin = false, cwd = '') => { if (isOrigin) await execa.command('git fetch origin', { ...(cwd ? { cwd } : {}) }) const currentBranch = execa.commandSync('git rev-parse --abbrev-ref HEAD', { ...(cwd ? { cwd } : {}) }).stdout let branchList = execa.commandSync(`git branch ${isOrigin ? '-r' : ''}`, { ...(cwd ? { cwd } : {}) }).stdout branchList = branchList.split('\n').map(item => { return item.split(' ').slice(-1)[0] }) branchList = branchList.filter(item => item !== currentBranch) return branchList } const returnLog = (text = '') => { log(text) process.exit() } // 同步for运行Command const awaitForCommand = async(commandList = []) => { for (let i = 0; i < commandList.length; i++) { const item = commandList[i] await execa.command(item[0], { ...(item[1] ? { cwd: item[1] } : {}) }) } } /** * 查看文件修改状态 * @param {String} type 指定类型 */ const getStatusList = (type = '') => { let statusList = execa.commandSync('git status -s').stdout statusList = statusList.split('\n').map(item => { return item.split(' ').filter(item => item) }) if (type) { statusList = statusList.filter(item => { return item?.[0]?.toUpperCase() === type.toUpperCase() }) } return statusList } module.exports = { log, logGreen, inputPrompt, confirmPrompt, selectPrompt, getBranchList, returnLog, awaitForCommand, getStatusList }
-
bin/branchMode/index.js
const execa = require('execa') const { selectPrompt, returnLog } = require('../util') // 子仓库目录 const submoduleName = 'child-ui' // 当前分支 const currentBranch = execa.commandSync('git rev-parse --abbrev-ref HEAD').stdout // 分支切换类型 const BRANCH_CHECKOUT_TYPE = { create: 1, toggle: 2, merge: 3, pull: 4, pushTags: 5 } // 枚举是否 const ISOK_ENMU = { yes: 1, no: 2 } // 选择是否 const ISOK_OPTIONS = [ { name: '是', value: ISOK_ENMU.yes }, { name: '否', value: ISOK_ENMU.no } ] // (1)、检测当前工作空间是否有未提交的文件 const checkGitStatus = async() => { const gitStatus = await execa.command('git status') if (!gitStatus.stdout.includes('nothing to commit')) { returnLog('检测到主仓库代码未提交, 请自行检查!') } const childStatus = await execa.command('git status', { cwd: `./${submoduleName}` }) if (!childStatus.stdout.includes('nothing to commit')) { returnLog('检测到子仓库代码未提交, 请自行检查!') } } // (2)、选择分支操作类型 const slcBranchType = async() => { const selectProjectName = await selectPrompt('branch操作类型选择', [ { name: '创建', value: BRANCH_CHECKOUT_TYPE.create }, { name: '切换', value: BRANCH_CHECKOUT_TYPE.toggle }, { name: '合并', value: BRANCH_CHECKOUT_TYPE.merge }, { name: '拉取', value: BRANCH_CHECKOUT_TYPE.pull }, ...( currentBranch === 'master' ? [{ name: '创建Tags', value: BRANCH_CHECKOUT_TYPE.pushTags }] : [] ) ] ) return selectProjectName } // (3)、子分支切换 & pull const toggleChildBranch = async(customBranch = currentBranch) => { const childBranch = execa.commandSync('git rev-parse --abbrev-ref HEAD', { cwd: `./${submoduleName}` }).stdout if (childBranch !== customBranch) { await execa.command(`git checkout ${customBranch}`, { cwd: `./${submoduleName}` }) } await execa.command('git pull', { cwd: `./${submoduleName}` }) } module.exports = { submoduleName, currentBranch, BRANCH_CHECKOUT_TYPE, ISOK_ENMU, ISOK_OPTIONS, checkGitStatus, slcBranchType, toggleChildBranch }
-
bin/branchMode/createBranch.js
/** * 创建分支(主 + 子) */ const { logGreen, inputPrompt, getBranchList, returnLog, awaitForCommand } = require('../util') const { submoduleName, checkGitStatus, toggleChildBranch } = require('./index') const createBranch = async() => { // (1) 子分支切换 & pull await toggleChildBranch() // (1.1) 主pull await awaitForCommand([['git pull']]) // (2) 检测暂存区 await checkGitStatus() // (3) 输入分支名 const createName = await inputPrompt(`请输入分支名:`) if (!createName) return const localBranchList = await getBranchList() if (localBranchList.includes(createName)) { returnLog('分支已存在') } // (4) 创建主分支 const parentOriginList = await getBranchList(true) // (4.1) 远程分支存在,创建本地 & 关联远程 if (parentOriginList.includes(`origin/${createName}`)) { await awaitForCommand([[`git checkout -b ${createName} origin/${createName}`]]) // (4.2) 创建分支 & 推至远程 } else { await awaitForCommand([ [`git checkout -b ${createName}`], [`git push --set-upstream origin ${createName}`] ]) } // (5) 创建子分支 const childLocalList = await getBranchList(false, `./${submoduleName}`) const childOriginList = await getBranchList(true, `./${submoduleName}`) // (5.1) 本地存在 if (childLocalList.includes(createName)) { await awaitForCommand([[`git checkout ${createName}`, `./${submoduleName}`]]) // (5.2) 远程存在 } else if (childOriginList.includes(`origin/${createName}`)) { await awaitForCommand([[`git checkout -b ${createName} origin/${createName}`, `./${submoduleName}`]]) // (5.3) 都不存在 } else { await awaitForCommand([ [`git checkout -b ${createName}`, `./${submoduleName}`], [`git push --set-upstream origin ${createName}`, `./${submoduleName}`] ]) } logGreen('已成功创建') } module.exports = createBranch
-
bin/branchMode/mergeBranch.js
1/** * 合并分支(主 + 子) */ const { logGreen, selectPrompt, getBranchList, awaitForCommand, getStatusList } = require('../util') const { submoduleName, currentBranch, checkGitStatus, toggleChildBranch } = require('./index') const mergeBranch = async() => { // (1) 主拉取 await awaitForCommand([ [`git pull`] ]) // (2) 子分支切换 & pull await toggleChildBranch(currentBranch) // (3) 检测暂存区 await checkGitStatus() // (4) 分支选择 const localBranchList = await getBranchList() const branchKey = await selectPrompt('请选择合并分支', localBranchList) // (5) 主分支合并 try { await awaitForCommand([[`git merge ${branchKey}`]]) } catch (error) { // log(`主仓库merge异常: ${error}`) } // (6) 子分支合并 await awaitForCommand([[`git merge ${branchKey}`, `./${submoduleName}`]]) // (7) 自动提交commit同步文件 const statusList = getStatusList() if ( statusList.length === 1 && // 暂存区 || 冲突 ['M', 'UU'].includes(statusList?.[0]?.[0]?.toUpperCase()) && statusList[0][1] === submoduleName ) { await awaitForCommand([ [`git add ${submoduleName}`], [`git commit -m fix:子仓库commit同步`] ]) } // (8) 提交远程 await awaitForCommand([ [`git push`, `./${submoduleName}`], [`git push`] ]) logGreen('已合并') await checkGitStatus() } module.exports = mergeBranch
-
bin/branchMode/pullBranch.js
/** * 拉取分支(主 + 子) */ const { logGreen, awaitForCommand } = require('../util') const { currentBranch, checkGitStatus, toggleChildBranch } = require('./index') const pullBranch = async() => { await awaitForCommand([ [`git pull`], [`git submodule update`] ]) // 子分支切换 & pull await toggleChildBranch(currentBranch) logGreen(`git pull 成功`) await checkGitStatus() } module.exports = pullBranch
-
bin/branchMode/toggleBranch.js
/** * 切换分支(主 + 子) */ const { logGreen, selectPrompt, getBranchList, awaitForCommand } = require('../util') const { submoduleName, checkGitStatus, toggleChildBranch } = require('./index') const toggleBranch = async() => { const localBranchList = await getBranchList() const branchKey = await selectPrompt('请选择分支', localBranchList) // (1) 切换分支 await awaitForCommand([ [`git checkout ${branchKey}`], [`git checkout ${branchKey}`, `./${submoduleName}`] ]) // (2) 检测暂存区 await checkGitStatus() await awaitForCommand([ [`git pull`], [`git submodule update`] ]) // (3) 子分支切换 & pull await toggleChildBranch(branchKey) logGreen(`切换分支:${branchKey} 成功, 并执行git pull`) await checkGitStatus() } module.exports = toggleBranch
-
bin/branchMode/createBranchTags.js
/** * 创建Tags(主 + 子) */ const { logGreen, selectPrompt, inputPrompt, awaitForCommand } = require('../util') const { submoduleName, ISOK_ENMU, ISOK_OPTIONS } = require('./index') const createBranchTags = async() => { // (1) 主仓库Tags选择 // const isTags = await selectPrompt('是否需要打Tags', ISOK_OPTIONS) // if (isTags !== ISOK_ENMU.yes) return // (2) 输入TagsName let tagsName = await inputPrompt(`请输入Tags名称:`) if (!tagsName) return tagsName = `tags/${tagsName}` // (3) 创建主仓库Tags await awaitForCommand([ [`git tag ${tagsName}`], [`git push origin ${tagsName}`] ]) logGreen(`主仓库Tags已推: ${tagsName}`) // (4) 创建子仓库Tags const isChildTags = await selectPrompt('子仓库是否需要打Tags', ISOK_OPTIONS) if (isChildTags !== ISOK_ENMU.yes) return await awaitForCommand([ [`git tag ${tagsName}`, `./${submoduleName}`], [`git push origin ${tagsName}`, `./${submoduleName}`] ]) logGreen(`子仓库Tags已推: ${tagsName}`) } module.exports = createBranchTags
到这里就大工告成了,有用的话麻烦关注收藏一下~
欢迎留言探讨🌹🌹🌹