使用shell脚本同步两个git仓库

1,078 阅读7分钟

最近开发的一个项目, 开发环境是win, 服务器环境自然是linux环境, 但遇到了一些问题, 这些问题在win上没有, 但是在部署之后却出现了, 然后尝试在本地打包之后使用打包后的产物部署, 这个问题就消失了, 由此得出这个问题是不同环境打包时候的差异导致的, 因此该项目改为使用打包产物来部署, 但由于项目内需要使用环境变量来区分当前是属于开发/测试/生产哪个环境, 因此部署到测试环境需要打包一次, 部署到生产环境也需要打包一次, dev分支下开发完成打包发测试, 然后合并到master, 再一次打包发生产, 而合并的时候就会出现冲突, 这样造成了非常难受的一个开发体验

同时需要注意的是, 我命令行工具是在vs code中使用, 这样能直接在项目目录位置打开, 以及用的是git bash, 关于windows下使用linux命令相关的知识, 感兴趣的小伙伴可以参考这几篇文章:

git-bash.exe如何实现在Windows下运行Linux命令?

How to execute .sh file on Windows?

git cherry-pick

这期间我尝试了git cherry-pick, 但需要将提交合并, merge也要合并, 主要是为了尽可能少的做cherry-pick的操作, 但是当提交比较多, 以及多人协作的时候操作会比较的繁琐

git subtree

同时subtreee也尝试了, 简而言之就是一个目录下面有两个仓库, 一个主仓库(除dist目录之外的其他内容), 一个子仓库(dist目录), 确切的说是一个仓库, 然后分成了主(除dist目录之外的其他内容)/子(dist目录)两个树, 在逻辑上将一个仓库分开了. 一开始打算将dist目录(打包产物所在的目录)当做subtree, 在.gitignore中将dist目录忽略掉, 这样分支合并的时候由于dist目录不会被合并, 那么就不会产生冲突, 可是当dist目录被忽略了之后, 就没法将dits中的变更提交了, 不忽略是可以的, 但这就会产生一开始提到的冲突的问题

因此最后采用了两个仓库的形式, 两个仓库无论是逻辑还是物理上都是分开的, 真正意义上的两个仓库, 一个是开发仓库, 里面正常将dist目录给忽略掉:

|---src
|---dist
|---README.md
...

然后另一个是部署仓库, 里面只有一个dist目录和一个自述文件(对这个仓库的情况做一个解释和说明):

|---dist
|---README.md

具体使用方式如下:

注: 开发仓库和部署仓库都有dev master两个分支, 以及合并分支的操作只在开发分支进行, 部署仓库的分支永远不做合并的操作(不然依旧会出现冲突), 部署仓库dev master分支的内容分别来自开发仓库的dev master分支

  1. 在开发仓库的dev分支进行开发
  2. 开发完毕, 打包之后将打包产物同步到部署仓库的dev分支, 覆盖部署仓库dev分支的dist目录
  3. 将部署仓库的dev分支部署到测试环境测试
  4. 测试完毕, 将开发仓库的dev分支合并到master分支
  5. 打包开发仓库master分支, 将打包产物覆盖到部署仓库master分支的dist目录
  6. 将部署仓库master分支部署到生产环境

然后在package.json中定义一些方便我们使用的script:

  "scripts": {
    "sync": "sh ./sync.sh",
    "build": "cross-env UMI_APP_ENVVAR=prod umi build",
    "build:test": "cross-env UMI_APP_ENVVAR=test umi build"
  }

这里的UMI_APP_ENVVAR是一个环境变量, 开发完成需要部署的时候要进行如下的操作:

  1. 使用打包script yarn build:testyarn build将代码打包
  2. 接着使用yarn sync这个同步的script将代码从开发仓库同步到部署仓库

我们的sync.sh的作用是用来自动完成代码的同步操作的, 同时还要注意分支的问题, 比如不能出现开发仓库dev分支的dist将部署仓库master分支的dist覆盖的情况, 因此这里还需要检测一下部署仓库的分支状态, 毕竟我们在开发仓库打包的时候是知道开发仓库的分支状态的, 毕竟这样我们才知道是使用yarn build:test还是yarn build, 也就是说分支对了, 则替换文件, 分支错了, 就先切换分支再替换文件, 同时commit message要传递进来以供git commit的时候使用, 思考一番之后不难得出sync.sh的逻辑:

  1. 有一个检测分支的函数来检测分支
  2. 有一个复制替换文件的函数来做替换文件的操作
  3. 获取外部传递进来的commit message参数
  4. 先检测部署仓库分支是否和开发仓库分支一致, 一致则替换文件, 不一致则切换部署仓库分支, 使之与开发仓库一致, 然后再替换文件, 同时替换文件的时候不让用户确认, 直接强制替换覆盖(这里开发仓库和部署仓库放在同一个目录下)
  5. 将部署仓库的变更推送到远程仓库

接下来我们一步一步来

检测分支的函数

我们知道通过git status能得到当前工作树的状态, 其中还包括我们目前处于哪个分支上, 比如:

On branch dev
Your branch is up to date with 'origin/dev'.

nothing to commit, working tree clean

shell中, 使用$()执行命令, 同时需要一个变量来储存输出的结果:

gitStatus=$(git status)
echo $gitStatus

使用变量的时候需要在变量名之前加$符号, 以及这里还涉及到字符串的检测, 同时需要注意一点: shell中函数无法返回字符串, 这个时候就需要用一些技巧来解决这个问题, 最终, 我们得到检测分支的函数内容如下:

function testBranch () {
  gitStatus=$(git status)
  dev="dev"
  if [[ $gitStatus =~ $dev ]]
  then
    # 在dev分支
    echo "dev"
  else
    # master分支
    echo "master"
  fi
}

同时需要注意的是, 和大多数编程语言不一样的是, shell中的函数调用不需要加圆括号, 同时这里的ifshell中的一个检测字符串的方法

复制替换文件的函数

复制命令我们知道是cp, 同时由于需要复制的是目录及其内部的子目录 子文件, 以及还不给出提示, 因此需要用到参数-rf, 由此可得到我们的复制替换文件的函数:

function replaceFile () {
  cd ../开发仓库/
  cp -rf dist/ ../部署仓库/
  cd ../部署仓库/
  git add --all
  git commit -m $外部传递进来的git commit message参数
  git push origin $开发仓库分支名
}

复制替换文件操作完成之后紧接着就做push的操作, 同时这里涉及到了外部的一个参数, 以及shell里面的变量, 前者由外部传递进来, 后者由上面的testBranch方法返回, 我们一步步来

获取外部参数

我们希望使用这样的方式来传递参数:

$ yarn sync -m 提交部署

以我们熟悉的linux命令参数的形式传递, 同时就以-mm来作为实参的名称, 由此, 我们得到获取外部参数的shell代码:

while getopts "m:" params
do
  case $params in
    m) m=$OPTARG
  esac
done

while getopts "m:" params:

getoptslinux系统中的一个内置变量, 一般用在循环中. 每当执行循环时, getopts都会检查下一个命令选项(参数),如果这些选项(参数)出现, 就是在""中出现, 比如这里的yarn sync -m 提交部署, m出现了, 则将选项(参数)保存在后面的变量params中, 注意, 这里保存的是m而不是提交部署

do ... done:

这是循环体, 我们在里面使用变量params, 因此在前面加$: $params, 接下来还有一个内置变量OPTARG, 这个就是保存参数值的变量了

接下来从参数里匹配值:

  case $params in
    外部参数名) 自己定义的参数名=$OPTARG
  esac

外部参数名要和外面传递的对应起来, 比如yarn sync -m 提交部署, 那么第一行while getopts "m:" params, 这里的外部参数名也要改成m:

  case $params in
    m) 自己定义的参数名=$OPTARG
  esac

同时我们为了不产生歧义, 自己定义的参数名就用m, 这样其他人阅读我们写的shell的时候, 在接下来使用的时候: git commit -m $m就知道这个m就是message的意思

sync.sh

综上, 我们得到最终的shell文件如下:

# 获取外部参数
while getopts "m:" params
do
  case $params in
    m) m=$OPTARG
  esac
done
# 检测分支的函数
function testBranch () {
  gitStatus=$(git status)
  dev="dev"
  if [[ $gitStatus =~ $dev ]]
  then
    # 在dev分支
    echo "dev"
  else
    # master分支
    echo "master"
  fi
}
# 替换操作的函数(开发仓库和部署仓库在同一目录下)
function replaceFile () {
  cd ../开发仓库/
  cp -rf dist/ ../部署仓库/
  cd ../部署仓库/
  git add --all
  git commit -m $m
  git push origin $devRepoBranchName
}
# 开发仓库分支名
devRepoBranchName=$(testBranch)
cd ../部署仓库/
# 部署仓库分支名
deployRepoBranchName=$(testBranch)
if [[ $devRepoBranchName == $deployRepoBranchName ]]
then
  # 分支相同执行覆盖操作
  replaceFile
else
  # 分支不同切换分支再覆盖
  git checkout $devRepoBranchName
  replaceFile
fi

开发完毕之后我们只需要:

  1. yarn build:test
  2. yarn sync -m 提交测试代码 这样就可以了

到这就完成了编写一个shell脚本帮助我们提高开发工作效率, 同步两个仓库代码的工作了

如果你觉得这篇文章对你有用的话记得给我点个赞, 点个收藏, 众人拾柴, 愿没有难写的代码, 没有难实现的需求