git submodule使用 解决同频操作分支难点


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
    


        到这里就大工告成了,有用的话麻烦关注收藏一下~

        欢迎留言探讨🌹🌹🌹

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值