最近开发的一个项目, 开发环境是win, 服务器环境自然是linux环境, 但遇到了一些问题, 这些问题在win上没有, 但是在部署之后却出现了, 然后尝试在本地打包之后使用打包后的产物部署, 这个问题就消失了, 由此得出这个问题是不同环境打包时候的差异导致的, 因此该项目改为使用打包产物来部署, 但由于项目内需要使用环境变量来区分当前是属于开发/测试/生产哪个环境, 因此部署到测试环境需要打包一次, 部署到生产环境也需要打包一次, dev分支下开发完成打包发测试, 然后合并到master, 再一次打包发生产, 而合并的时候就会出现冲突, 这样造成了非常难受的一个开发体验
同时需要注意的是, 我命令行工具是在vs code中使用, 这样能直接在项目目录位置打开, 以及用的是git bash, 关于windows下使用linux命令相关的知识, 感兴趣的小伙伴可以参考这几篇文章:
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分支
- 在开发仓库的
dev分支进行开发 - 开发完毕, 打包之后将打包产物同步到部署仓库的
dev分支, 覆盖部署仓库dev分支的dist目录 - 将部署仓库的
dev分支部署到测试环境测试 - 测试完毕, 将开发仓库的
dev分支合并到master分支 - 打包开发仓库
master分支, 将打包产物覆盖到部署仓库master分支的dist目录 - 将部署仓库
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是一个环境变量, 开发完成需要部署的时候要进行如下的操作:
- 使用打包
scriptyarn build:test或yarn build将代码打包 - 接着使用
yarn sync这个同步的script将代码从开发仓库同步到部署仓库
我们的sync.sh的作用是用来自动完成代码的同步操作的, 同时还要注意分支的问题, 比如不能出现开发仓库dev分支的dist将部署仓库master分支的dist覆盖的情况, 因此这里还需要检测一下部署仓库的分支状态, 毕竟我们在开发仓库打包的时候是知道开发仓库的分支状态的, 毕竟这样我们才知道是使用yarn build:test还是yarn build, 也就是说分支对了, 则替换文件, 分支错了, 就先切换分支再替换文件, 同时commit message要传递进来以供git commit的时候使用, 思考一番之后不难得出sync.sh的逻辑:
- 有一个检测分支的函数来检测分支
- 有一个复制替换文件的函数来做替换文件的操作
- 获取外部传递进来的
commit message参数 - 先检测部署仓库分支是否和开发仓库分支一致, 一致则替换文件, 不一致则切换部署仓库分支, 使之与开发仓库一致, 然后再替换文件, 同时替换文件的时候不让用户确认, 直接强制替换覆盖(这里开发仓库和部署仓库放在同一个目录下)
- 将部署仓库的变更推送到远程仓库
接下来我们一步一步来
检测分支的函数
我们知道通过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中的函数调用不需要加圆括号, 同时这里的if是shell中的一个检测字符串的方法
复制替换文件的函数
复制命令我们知道是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命令参数的形式传递, 同时就以-m的m来作为实参的名称, 由此, 我们得到获取外部参数的shell代码:
while getopts "m:" params
do
case $params in
m) m=$OPTARG
esac
done
while getopts "m:" params:
getopts是linux系统中的一个内置变量, 一般用在循环中. 每当执行循环时, 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
开发完毕之后我们只需要:
yarn build:testyarn sync -m 提交测试代码这样就可以了
到这就完成了编写一个shell脚本帮助我们提高开发工作效率, 同步两个仓库代码的工作了
如果你觉得这篇文章对你有用的话记得给我点个赞, 点个收藏, 众人拾柴, 愿没有难写的代码, 没有难实现的需求