关于一次使用 git 子模块功能备份 hexo 主题文件的记录

背景

整个事情源于某天心血来潮又写了篇博客,当准备使用 hexo 发表时,突然想到一个问题之前虽然做到对于博客项目的git备份了,但是最近新换的主题配置修改也备份了吗?,有点像强迫症出门后思考房门有没有锁好的感觉。

背景是这样的:最初我刚接触 hexo 的时候,觉得发现了宝贝,它是个不错的静态博客生成器,风格偏极客风。虽说是博客工具,但要拿给不会技术的人可能用起来还比较蹩脚,因为其虽然声称使用简便,只需要快速地搭建环境,敲敲命令,就可以在网页上展现出漂亮的个人博客网站来。

但其搭建环境基于 Node.js,命令又依赖于 git,还得把本地的博客源文件编译成浏览器可解读的静态文件发布到远程 git 仓库,还得使用支持 pages 服务的 git 仓库,比如 github 的 github pages 或 coding pages。

所以我当初刚接触的时候,对其原理也是一知半解,仅停留在”会用“程度,但随着时间的推移,免不了发生一些其他变动。比如换电脑,这时候我就发现,如果没有对本地博客项目进行版本库的管理和备份,那么只要硬盘上的博客项目文件夹出现丢失或者换电脑,就麻烦了。

当然,换电脑还算好,只要用优盘把旧电脑里的文件夹拷贝过去,在新电脑搭建相同的环境,就能恢复使用了,但这方法不够优雅,不够 geek。于是就在网上查 hexo 博客备份的方法,最后结合查到的方式和自己的推敲尝试,记录了一篇我是如何备份博客的

回顾

但今天再看,当时只解决了博客“根项目”的保存,最近换了新的主题,主题的配置文件改了一大通,当然就是根据自己进行的个性化修改。突然想到,主题项目是博客项目 themes 文件夹的一个子项目,是进到 themes 目录,通过git clone下载到本地的另一个 git 项目。

问题来了,git 项目中嵌套 git 项目,事情看上去不那么简单。好在之前就了解过子项目git submodule的概念,知道这是那块的东西,但当时刚接触子项目的时候觉得有点复杂,就没再理会,现在发现逃不过了,这是最好也是最恰当的解决方案,所以再次开始了搜查。

先是根据猜想结合自己使用 git 的经验进行尝试

思路

首先,理了一下思路,新下载的hexo-theme-matery项目是不应该直接划为子模块的。因为它的远程关联是 github 中原作者的项目,我对其的修改不可能直接提交到人家那里,也提交不上去,所以我首先应该有一份自己的关于hexo-theme-matery项目的拷贝。

无论是本地 git 还是远程仓库,都是自己的,所以先进入到themes/hexo-theme-matery目录,把 git 初始化删掉(后来想到这里其实不用直接把 git 初始化删掉,只要把远程仓库关联改成我自己的就可以了,因为在本地 git 的提交都是我自己的,只要同步到自己的远程仓库就可以了)

删除 git 初始化(使一个项目脱离 git 的管理)

rm -rf ./.git

然后再重新初始化themes/hexo-theme-matery目录成为一个 git 项目,并关联我远程建的空项目

由于远端建的是空项目,所以可以毫无冲突的直接 push 并建立默认分支设置(加参数 -u)

cd hexo-theme-matery
git init .
git add .
git commit -m 'initial matery theme as a divided git project'
git remote add origin git@git.coding.net:daemonG/my-hexo-theme-matery.git
git push -u origin master

接下来要建立子项目关联了,参考了网上的一篇文章在 hexo 中使用 git submodules 管理主题

照猫画虎地进行了如下的操作

按照我对网上文章的理解,以为是通过如下命令,把某个文件夹变成一个项目并命名子项目为 theme-matery

cd hexo-theme-matery
git add submodule . theme-matery

但发现提示错误,发现是命令的参数顺序错了,于是改正为

git submodule add . theme-matery

还不对,提示子模块必须是一个绝对路径的目录,修改为

git submodule add ./ theme-matery

可以了,但发现好像不对,子模块添加成功的提示告诉我,我敲的这个命令是在当前目录添加了一个 theme-matery 目录,内容是当前目录的所有,这并不是我想要的,于是准备删掉这次操作的产物

git submodule remove theme-matery

提示并没有 remove 这个命令,并提示了我有哪些关于子模块的命令可用,于是

git submodule deinit

提示需要对全部文件进行子模块撤销

git submodule deinit --all

提示新建立的子模块已经有了 git 修改记录,如果此时要撤销子模块,需要进行强制撤销操作

git submodule deinit --all -f

这样发现,新建的theme-matery文件夹消失了,但还残留一个.gitmodules文件,这是 git 子模块的描述文件,既然刚才是一次失败的尝试,这个也删掉

rm -rf .gitmodules

终于恢复如初了,再来,继续尝试其他方法。既然刚才的确生成了子模块描述文件和对应的子项目,那么方向不错,只是生成的位置和预期不符,于是我退到项目根路径重新操作

cd ..
cd ..
git submodule add themes/hexo-theme-matery/ theme-matery

同样提示了刚才犯的一个错误,指定的子模块要以绝对路径给出

git submodule add ./themes/hexo-theme-matery/ theme-matery

这回的确在项目根路径生成子模块了,但也不对,原来命令中的最后一个参数是最终生成的子模块文件夹名称,而不是对于子模块定义的别名,我理解错了,所以还得删掉重来

git rm theme-matery
git rm theme-matery -f
git rm .gitmodules

这我就迷茫了,到底应该怎么添加子模块呢,再看网上查的文章,里面举的例子是把一个远程 git 项目下载到根项目中并作为子模块,前提是我原来只有一个 git 根项目,然后再添加一个远程 git 项目到本地的 git 项目的一个子文件夹作为一个子模块存在,下载+子模块初始化两步合并操作,但我的情况是,我本地已经是一个 git 根项目里套着一个别人的 git 项目了,想在本地操作,把这个子 git 项目变成根 git 项目的子模块。

emmmmm…

解决

灵光一闪,有了办法

反正刚才已经把本地的主题项目同步到远端新建的空项目了,所以也就是我修改过配置文件的主题项目已经在远端有了备份,所以我可以把本地的删掉,然后按照参考文章里的做法,通过下载远端项目到本地并初始化为子模块的方式进行操作了

git submodule add git@git.coding.net:daemonG/my-hexo-theme-matery.git themes/my-hexo-theme-matery

完美,效果是我想要的

接下来还有提交的问题,我已经预料到这个问题,虽然是子模块,但肯定也存在提交的问题,毕竟是两个独立的 git 项目,只是存在父子关联关系,应该存在单独提交父项目单独提交子项目同时提交父项目和子项目三种情况

先试一下

git add .
git commit -m 'add theme/matery as submodule'

在根目录发现这样提交并不会提交子模块的修改

难道是子模块的修改没被 git 管理到?

git submodule add .
git add . --recursive

呃,这都是无用操作

再试了一下参考文章里的git commit -am 'update config of submodule',也不行

感到无助的时候回归官方文档Git 工具 - 子模块和另一篇文章的参考Git Submodule 的使用,有一句说法醍醐灌顶

主 git 仓库中存在.gitmodules 文件,它记录了 submodule 的基本信息。例如 remote 地址。

同时在某处记录了主 git 仓库所用的 submodule 的 commit 号。

主 git 仓库并不同步 submodule 中的所有代码,而是同步其 remote 地址和 commit 号,每个 clone 都是根据这两个信息自行到 remote 地址获取到该 commit 版本的内容。所以,如果你要更新 submodule 必须做上面的操作步骤。而你操作完成后,你的 git 仓库中 submodule 的 commit 号得到更新。

所以,根项目想要把子模块的修改一并作为主项目的一部分进行提交,需要察觉到子模块中的 commit 号改变

cd ..
cd ..
git add .
git commit -m 'update config of submodule'

这样再回到主项目,子模块的提交就一并提交到主项目的远程了,当然子项目还是“自治”的,子模块的提交还是子模块的提交,它也是一个独立的 git 项目

总结

虽然啰嗦了这么一大篇,主要是为了再现当时的各种失败尝试,这样在日后遇到问题时能够想起当时的思路。但也免不了此时的结论就是最佳的方案,可能还有一些操作是错误的,或者存在隐患的,关于 git 子模块的使用还在进一步的实践和学习中。

补充

刚做了这样的尝试,博客就崩了,发布上去直接白屏。经过多方排查,想到是我重新命名了主题项目的名称,而整个 hexo 的设计都是约定优于配置的,所以改动主题项目名,必定导致配置上对不上,出现报错,并且还不好定位到错误。

还好想到了是这里的问题,所以把根项目配置文件中 theme 的配置项改为修改后的名称,博客恢复正常了。

子模块常用命令

改动子模块

cd 子模块目录
git add .
git commit -m '像正常修改提交git项目一样操作'
git push
cd 父模块目录
git add .
git commit -m '提交父模块中子模块的改动'
git push

核心步骤:进到子模块目录进行提交,再回到父模块项目再次提交

更新子模块

方式一

在父模块目录遍历更新其下的子模块

cd 父模块目录
git submodule foreach git pull
方式二

进入到子模块目录中正常更新

cd 子模块
git pull

下载包含子模块的 git 项目

递归下载,同时下载父模块 git 项目并递归检查其包含的子模块 git 项目一并下载

git clone 项目地址 --recursive

先下载父模块项目,下载后如果父项目包含子模块则会有对应子项目名称的空目录,进入子模块目录初始化子模块

git clone 项目地址
cd 子模块目录
git submodule init
git submodule update

git submodule update用以确保子模块更新到最新和下载完整,比如使用递归下载git clone 项目地址 --recursive时下载不全的情况

移除子模块

git 不支持直接删除子模块

cd 子模块
git rm --cached .
cd 父模块
rm -rf 子模块
rm .gitmodules

hexo 优缺点

优点

  • 高度可定制化
  • 支持二次开发
  • 开源,具备维护性并可参与到功能开发中
  • 满足用户极客心理的诉求
  • 主题拓展性强,有活跃的群体在丰富着可用的主题

缺点

  • 不稳定,区别于知乎、简书这样的商业产品,具备专业团队保证使用的稳定性
  • 使用基于配置,需要对配置规则较熟悉,否则会产生预料之外的错误,并且不易定位到
  • 依赖于 git、git 远程仓库和 pages 服务,如果想通过自己的域名访问博客还依赖域名服务(以及域名租用的费用)

使用建议

对博客源项目进行 git 独立项目备份

因为使用 hexo-deployer 发布到远端 git 仓库的是对源项目进行编译后的文件,与原项目有着完全不同的目录格式,一旦源项目丢失,无法通过已经发布的远程 git 项目逆向生成

对使用到的主题项目进行独立项目备份

因为主题项目一般都是独立的 git 项目,也会独立的更新功能,如果不能很好的将其作为子模块和博客项目关联并对其进行独立 git 项目备份的话,在换电脑或主题项目变更后,很难保留用户自己的修改

对每一次的博客变动在本地发布测试通过后再向远端部署

因为 hexo 是基于配置约定的,所以有时用户会错误的使用一些命令或配置修改,会导致 hexo 在部署到远端的时候发生错误,而产生覆盖远端发布文件,导致博客不可访问的问题。所以对待 hexo 博客要像对待一个 web 项目一样经过本地测试,命令如下(s 为 server 的首字母标识)

hexo s

如果默认的 4000 端口被占用,可通过添加-p参数指定其他端口

hexo s -p 4001

经本地验证无误,符合改动预期后再发布远程

hexo g -d

其他情况

还有可能导致博客无法正常访问的原因可能是 pages 服务配置不正确,或者域名不可用,域名过期等原因,需要查阅网上其他人的解决方案

参考链接