多种方式同步 GitHub 代码至镜像仓库

您所在的位置:网站首页 giteeusername 多种方式同步 GitHub 代码至镜像仓库

多种方式同步 GitHub 代码至镜像仓库

2023-03-22 13:26| 来源: 网络整理| 查看: 265

前言

  大多数的开发者都或多或少在GitHub上维护有项目,但是通常GitHub访问起来都很慢,或者无法响应。为了不能正常访问GitHub的用户,一般会将Gitee或其它平台托管作为镜像。

  我们通常只考虑维护在GitHub上的仓库就足够了,而对于其它镜像仓库,更多的是希望在GitHub更新的同时,都以静默的方式自动同步。

  因此以下将以Gitee作为镜像仓库,对比多种同步方式的利弊,跟随此文你将了解到。

同步GitHub和Gitee代码仓库的多种方式 webhooks是什么 什么是GitHub Actions,GitHub Actions可以做什么 GitHub Actions如何自动化部署Pages 同步 维护多个远端

  查看当前仓库关联的远程库。

在这里插入图片描述

推送多次

  删除origin,然后依次关联远程GitHub和Gitee仓库。

  本地关联的远程库名称大多数都是origin,此情况对于单个远端来说很适用。若关联有多个远端,名称最好还是要容易区分。

git remote rm origin git remote add github https://github.com/xxx/repo.git git remote add gitee https://gitee.com/xxx/repo.git 复制代码

  查看关联的远程库。

在这里插入图片描述

  本地代码提交后,分别推送至两个远端。缺点也比较明显,即推送多次显得冗余。

git push github master git push gitee master 复制代码 添加 package.json 脚本命令

  可在package.json中合并两个命令,推送时只运行npm run push即可。实际看似简化了输入,却添加了与项目无关的scripts命令。

// package.json { ... "scripts": { "push": "git push github master && git push gitee master" } } 复制代码 修改 Git 内部配置

  当前远端还是只有origin。

在这里插入图片描述

  添加一条push的远端地址。

git remote set-url --add origin https://gitee.com/xxx/repo.git 复制代码

  可以看到推送push的url多了一条。

在这里插入图片描述

  因此fetch时将从GitHub拉取代码,push时将推送到Gitee和GitHub两个远端。此方式相对可用,但是无法做到部分的自动化功能,例如测试、部署等。

在这里插入图片描述

Gitee 同步按钮

  若仓库还未创建,可在头部选择导入仓库。

在这里插入图片描述

  若仓库已存在,可在仓库主页的管理菜单的功能设置下配置地址。

在这里插入图片描述

  两种方式都将在仓库主页创建同步按钮。

在这里插入图片描述

  注意强制同步将覆盖当前仓库。

在这里插入图片描述

webhooks

  webhooks 即web钩子,是一个可以接收http/s请求(多为post)的URL。大多数情况下都是客户端调用api获取服务端提供的数据。而在webhooks中,服务端则将在特定事件时调用webhooks钩子。

  GitHub也提供了webhooks,当用户向仓库push推送(不单单是推送事件)代码时,GitHub将向配置的URL发送http/s请求,可用于发邮件、自动部署、备份镜像等等。

  代码仓库可访问 GitHub。

准备工作

  根据以上特性,搭建一个express服务器,目的用于启动一个用于同步代码的服务端post接口。

  目录结构如下,app.js为入口文件,webhook-handler用于提供同步代码的核心功能。

├── webhook-handler │ ├── index.js │ ├── mirror.js │ ├── shell.sh │ ├── webhook.config.js ├── app.js ├── const.js ├── package.json ├── ... 复制代码

  app.js中引入webhook-handler处理函数,注意GitHub触发webhooks传递的是json格式的数据,要用到express.json()中间件,继续往下看。

// app.js const handler = require('./webhook-handler') app.use(express.json()) app.post('/mirror', (req, res, next) => { handler( ... ) }) 复制代码 处理函数

  当GitHub调用mirror接口时,会将用户预设的秘钥secret和参数体json进行加密,加密后的序列会携带在请求头header的 x-hub-signature 中。

  工具类函数encrypted,主要用于进行hmacsha1算法加密,参数secret为秘钥,sign为被用于加密的数据。

  函数isEqual,用于对比两字符串是否一致,但是注意判断相等 不建议 用===,而应该使用 恒定时间 字符串比较,有助于提高服务端的安全性。

// webhook-handler/index.js function handler(req, res, cb) { const sign = req.headers['x-hub-signature'] const encrypted = encrypt(GITHUB_WEBHOOK_SECRET, req.body) ... } function encrypt(secret, sign) { return `sha1=${crypto.createHmac('sha1', secret).update(JSON.stringify(sign)).digest('hex')}` } function isEqual(value = '', other = '') { if (value.length !== other.length) return false return crypto.timingSafeEqual(Buffer.from(value), Buffer.from(other)) } 复制代码

  思考一下,为什么GitHub会将用户预设的秘钥和参数体加密呢?

  秘钥加密可以理解,因为不能明文传递。

  以上提供的post接口,对于GitHub的任意仓库都能触发,别人的仓库是不是也可以呢。那么如何区分是否是我们的仓库触发了呢,秘钥就派上用场了。当服务端保存的静态秘钥与GitHub仓库预设的秘钥一致时,就能确定是我们的仓库了。

同步代码

  mirror.js用于启动子进程,运行shell脚本来同步代码。

  要明确的是,当我们向https://github.com/xxx/repo.git推送代码时,是无法登录验证权限的,而应当携带上用户名密码来推送,即推送到https://username:[email protected]/xxx/repo.git。

// webhook-handler/mirror.js const fullPath = getFullPath(DIST_REPO, GITEE_USERNAME, GITEE_PASSWORD) function getFullPath(url, username, password) { const index = url.indexOf('//') const protocol = url.slice(0, index + 2) const path = url.slice(index + 2) return `${protocol}${username}:${password}@${path}` } 复制代码

  shell脚本中,接收两个参数,分别为源仓库和目标仓库地址,注意$1和$2没有语义,可声明变量来保存。

// webhook-handler/mirror.js const shPath = path.join(__dirname, 'shell.sh') const command = `${shPath} ${SRC_REPO} ${fullPath}` // webhook-handler/shell.sh SRC_REPO=$1 DIST_REPO=$2 ... 复制代码

  有必要解释下shell.sh脚本的工作流程。

mkdir _temp ...:根目录下创建临时目录_temp,切换工作目录到_temp下 git clone --mirror ...:镜像克隆,完全复制源仓库(包括分支、引用等等) git remote set-url --push ...:将当前副本仓库的推送源地址修改为目标仓库 git push --mirror:镜像推送,完全推送到目标仓库(包括分支、引用等等) cd ...:切换到初始的目录,删除临时仓库_temp // webhook-handler/shell.sh mkdir _temp && cd _temp git clone --mirror "$SRC_REPO" && cd `basename "$SRC_REPO"` git remote set-url --push origin "$DIST_REPO" git push --mirror cd ../../ && rm -rf _temp 复制代码 GitHub 添加 webhooks

  在GitHub仓库下,选中Settings功能的Webhooks菜单,单击Add webhook添加。

在这里插入图片描述

  输入你部署在服务器上的post接口,设置Secret秘钥,注意Content type选择为json,另外触发webhook的事件类型,默认只有推送事件,你也可以自定义选择。

在这里插入图片描述

  当你的仓库发生以上勾选的事件时,GitHub就会自动帮你调用部署在服务器上的mirror接口,进而镜像同步你的仓库代码。

  webhooks的方式相对来说比较可行,但是缺点也很明显,一方面必须额外的服务器(有node环境且安装了Git)支持用来部署接口。另一方面,单个仓库同步还是很便捷,但是如果说多个仓库要镜像同步呢?

  那我们的处理函数就要去判断请求接口的GitHub仓库来源,同时每多同步一组仓库,就要在服务端新增一组源仓库和目标仓库的地址。

提供一个 Gitee 官方的webhooks,用于Gitee和GitHub双向同步的功能,但是注意目前尚处于内测期,只能 申请 开通

GitHub Actions

  GitHub Actions 即是一个免费的虚拟机,提供了三种可选的操作系统(Ubuntu Linux、Microsoft Windows和macOS),用以执行用户自定义的工作流程。

  那么何为工作流程呢?就是一个以.yml为后缀的文件(YAML语法),注意此文件要放置在代码仓库中的目录.github/workflows下才会生效。

注意对于每个工作流程,GitHub都会在预先配置好的全新虚拟机中执行

Hello world

  我们就用GitHub Actions打印Hello world试试,不用太过复杂的例子。

  在GitHub仓库上选中Actions,单击set up a workflow yourself创建工作流程。

在这里插入图片描述

  然后在代码编辑器粘贴以下代码,以下为相关命令的含义,更多可 参考。

name: ...:工作流程的名称为Console hello world on: ...:仓库发生推送push事件时执行 jobs:表示执行的一项或多项任务,当前仅有一个任务console console:任务名为console runs-on ...:任务console运行的虚拟机为最新的Ubuntu Linux环境 steps:任务console的运行步骤,当前仅有一个步骤 - run ...:运行命令echo Hello world # .github/gitflows/main.yml name: Console hello world on: push jobs: console: runs-on: ubuntu-latest steps: - run: echo Hello world 复制代码

  点击Start Commit然后Commit new file提交。

在这里插入图片描述

  仓库目录下将生成main.yml文件,另外会创建一次提交记录Create main.yml。

├── .github │ ├── workflows │ │ ├── main.yml ├── ... 复制代码

  由于代码是在GitHub上提交的,相当于是本地代码推送push了一次,而脚本的执行条件就是push事件的发生,因此GitHub Actions将会触发。

在这里插入图片描述

  单击Create main.yml查看此次推送执行的工作流程,成功在虚拟机下打印出Hello world。

在这里插入图片描述

准备工作

  先要保证本机环境有Git公钥和私钥。

  然后在Gitee用户SSH公钥中,添加标题并粘贴公钥保存。

在这里插入图片描述

  然后在GitHub的仓库repo下,Settings功能选择Actions,单击New repository secret添加秘钥。

在这里插入图片描述

  呐,粘贴你的私钥,准备工作就完成了。

在这里插入图片描述

添加脚本

  修改.github/workflow/main.yml。

  根据刚才Hello world的例子,很容易知道当前工作流程的名称为Mirror to Gitee repo,仓库在推送push代码、删除delete分支或者创建create分支时,将执行此脚本。

  脚本包含一个任务,名为mirror,运行的虚拟机为最新的Ubuntu Linux环境。而此任务下又包含两个名为Config private key和Clone repo and push的步骤。

# .github/gitflows/main.yml name: Mirror to Gitee repo on: [ push, delete, create ] jobs: mirror: runs-on: ubuntu-latest steps: - name: Config private key env: SSH_PRIVATE_KEY: ${{ secrets.GITEE_PRIVATE_KEY }} run: | mkdir -p ~/.ssh echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa echo "StrictHostKeyChecking no" >> ~/.ssh/config - name: Clone repo and push env: SRC_REPO: "https://github.com/xxx/repo.git" DIST_REPO: "[email protected]:xxx/repo.git" run: | git clone --mirror "$SRC_REPO" cd `basename "$SRC_REPO"` git remote set-url --push origin "$DIST_REPO" git push --mirror 复制代码

  Config private key用于在虚拟机的用户目录中写入私钥。env下有环境变量SSH_PRIVATE_KEY,变量值由 secrets 上下文获取而来。

  有没有觉得GITEE_PRIVATE_KEY很眼熟呢?没错,就是刚才保存在仓库目录下的私钥。

  当用户向诸如[email protected]:xxx/xx.git的仓库推送代码时,遵循SSH传输协议。在推送前,Git将提交用户根目录下的私钥id_rsa到远端,远端则会将私钥和公钥(GitHub或者Gitee用户在服务端添加的公钥id_rsa.pub)对一起做验证,判别此私钥是否有推送权限。

  而我们已经将公钥保存在了Gitee远端服务器上,私钥保存在GitHub仓库内的GITEE_PRIVATE_KEY上,GitHub平台会将私钥传递在yml脚本中的secrets上下文内,然后脚本获取后,将其写在了虚拟机的用户根目录下。此时虚拟机要推送的话,它当然是有权限的。

  还是不太清楚的话,可以理解为。依托GitHub平台和yml脚本,我们本机的私钥将会被复制成为虚拟机的私钥。

  以下为Config private key写入私钥的过程。

mkdir -p ...:虚拟机根目录下创建.ssh文件夹。-p表示即使上级目录不存在,也要按目录层级自动创建 echo ...:将私钥写入.ssh文件夹下的id_rsa文件中 chmod ...:修改id_rsa的权限为600(仅所有者可读写)

  创建的id_rsa的访问权限0644过于开放,Git要求私钥文件不能被其他人访问。

在这里插入图片描述

echo ...:关闭初次连接服务器时的提示

  当第一次连接服务器时,例如push提交,Git将弹出公钥确认的提示,将导致自动化任务中断。

在这里插入图片描述

同步代码

  当我们向GitHub仓库推送代码时,GitHub Actions将自动运行仓库下的yml脚本。若任务和任务下的步骤都通过,则表示执行成功。

在这里插入图片描述

actions

  现在来思考一个问题,如果要同步另外的GitHub仓库到Gitee呢?

  那么我们是不要复制以上代码,修改源和目的仓库地址呢?可以明确告诉你,不用。

  GitHub想到了一个很好的办法,开发者可以发布不同的工作流程到 官方市场,而用户可以引用别人的actions即可。同步GitHub仓库到Gitee的功能,很早就有团队写好发布了,缺陷相对也很少。刚才讲那么多命令,只是为了更好地帮助你理解GitHub Actions的工作原理。

  以下为 hub-mirror-action 配置参数。

src:源平台账户名 dst:目标平台账户名 dst_key:源仓库下保存的私钥 static_list:仅同步指定的仓库 force_update:强制同步

hub-mirror-action远远不止同步单个仓库,它可以将两个平台下的所有仓库都同步

# .github/gitflows/main.yml name: Mirror to Gitee repo on: [ push, delete, create ] jobs: mirror: runs-on: ubuntu-latest steps: - uses: Yikun/[email protected] with: src: github/username dst: gitee/username dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} static_list: 'repo' force_update: true 复制代码 常见问题 GitHub Actions 如何自动化部署 Pages

  一个很常见的场景,当我们将本地仓库代码推送至远端GitHub仓库时,要同步代码到Gitee,并且自动部署GitHub和Gitee的Pages。

  此处用vuecli3脚手架作为示例,运行vue create app安装vue空脚手架,然后修改vue.config.js中的生产路径。

// vue.config.js module.exports = { publicPath: './', } 复制代码

  根目录app下目录结构,main.yml用于部署Pages。

├── .github │ ├── workflows │ │ ├── main.yml ├── node_modules ├── src │ ├── App.vue │ ├── main.js │ ├── ... ├── ... ├── vue.config.js 复制代码

  部署GitHub Pages相对容易,GitHub Pages关联的分支有更新时将自动部署。

  而Gitee Pages相对麻烦,只能手动更新部署。第三方gitee-pages-action内部实际是利用Gitee用户名和密码登录至平台,调用更新接口的方式来实现的自动部署。

  以下为各个actions的功能及参数的作用。

checkout:检出当前仓库。你可以想象成在虚拟机内克隆了当前仓库,然后就可以运行package.json中的scripts命令,例如npm run build等。 setup-node:虚拟机安装node环境。其中node-version用于指定node版本 actions-gh-pages:部署GitHub Pages页面。将npm run build命令构建出的dist目录,创建为新分支page用于部署。force_orphan表示page分支只生成一次提交记录,full_commit_message为提交说明,allow_empty_commit表示即使dist文件没有更新,也要重新提交 hub-mirror-action:镜像同步仓库 gitee-pages-action:部署Gitee Pages页面。其中GITEE_PASSWORD为GitHub仓库下添加的Gitee平台密码,gitee-repo和branch表示对Gitee下的repo仓库的page分支进行部署 # .github/gitflows/main.yml name: Mirror to Gitee repo and deploy pages on: push: branches: - master jobs: deploy-github: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup node uses: actions/setup-node@v3 with: node-version: '16.14.0' - name: Depend install and build run: | npm install npm run build - name: Deploy GitHub pages uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.GITEE_PRIVATE_KEY }} publish_branch: page publish_dir: dist allow_empty_commit: true force_orphan: true full_commit_message: 'feat: deploy pages' deploy-gitee: needs: deploy-github runs-on: ubuntu-latest steps: - name: Mirror to Gitee repo uses: Yikun/[email protected] with: src: github/username dst: gitee/username dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} static_list: 'repo' force_update: true - name: Deploy Gitee pages uses: yanglbme/[email protected] with: gitee-username: username gitee-password: ${{ secrets.GITEE_PASSWORD }} gitee-repo: repo branch: page 复制代码

  因此main.yml脚本的工作流程也非常清晰了,任务deploy-github用于检出仓库后构建代码,部署至GitHub对应仓库的page分支。任务deploy-gitee用于同步GitHub仓库至Gitee,然后在Gitee平台,对仓库的page分支再单独部署。

  注意任务deploy-gitee一定是在deploy-github后运行的,否则仓库同步将无法达到预期。needs 表示当前任务要等待指定任务完成后才能执行。

VuePress 如何多仓库同步和部署

  VuePress 也有仓库的同步和部署Pages的场景,但是相对来说有很大的差异性,主要原因在于部署Pages的代码位于另外的仓库下,而不是当前仓库的page分支。

准备工作

  你的GitHub应该包括两个仓库,一个用于保存VuePress的源码,一个用于静态博客,保存VuePress打包后的代码,另外Gitee仓库也是同理。

  可能你会问,用一个命名分支(例如page)保存打包后的代码,在部署页面时选择此分支不是更好,还能节省一个仓库。

  为什么会这样做呢?

  在Gitee平台中,如果你的用户名为username,当你的仓库名和用户名一致时,就会触发一个 隐藏特性,即访问地址https://username.gitee.io,就能访问你部署在username仓库的静态页面。

一般的Gitee仓库,部署为静态Pages后,访问地址都为二级目录。例如repo仓库,部署后的地址为http://username.gitee.io/repo

  而在GitHub平台中,如果你的用户名为username,当你的仓库名为username.github.io时,也会触发一个隐藏特性,即访问地址https://username.github.io,就能访问部署在username.github.io仓库的静态页面,否则也会是二级目录的形式。

GitHub平台,用户名和仓库名一致时,还会有另外的隐藏特性

  为什么会创建两个仓库,而不是以分支的方式就不用我多说了吧。为了触发平台的隐藏特性,让你的个人主页地址更加简洁,容易记忆。

  因此我们实际要有四个仓库,Gitee平台的vuepress和username仓库,GitHub的vuepress和username.github.io仓库。

添加脚本

  默认你的目录结构为以下,其中deploy-pages.yml用于部署Pages,mirror-repo.yml用于同步VuePress源码仓库。

├── .github │ ├── workflows │ │ ├── deploy-pages.yml │ │ ├── mirror-repo.yml ├── node_modules ├── docs │ ├── .vuepress │ │ ├── config.js │ ├── README.md ├── package.json ├── .gitignore 复制代码

  package.json添加scripts命令。

// package.json { ... "scripts": { "dev": "vuepress dev docs --temp .temp", "build": "vuepress build docs", }, } 复制代码

  然后新增mirror-repo.yml脚本,作用很简单,即将Github平台用户username的仓库vuepress,强制同步到Gitee平台用户username的仓库vuepress。

# .github/workflows/mirror-repo.yml name: Mirror to Gitee repo on: push: branches: - master jobs: mirror: runs-on: ubuntu-latest steps: - uses: Yikun/[email protected] with: src: github/username dst: gitee/username dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} static_list: 'vuepress' force_update: true 复制代码

  最后新增deploy-pages.yml脚本,以下为各步骤作用。

Setup node:签出当前仓库并安装node环境 Depend install and build:安装依赖并打包代码 Deploy GitHub pages:将打包后的代码(位于docs/.vuepress/dist目录下),推送到用户username的username.github.io仓库 Mirror to Gitee repo:同步GitHub平台的仓库username.github.io代码,至Gitee平台的username仓库。其中mappings参数表示仓库名不同时的映射 Deploy Gitee pages:将Gitee平台的用户username下的username仓库,其中的master分支代码部署为Pages # .github/workflows/mirror-repo.yml name: Deploy pages on: push: branches: - master jobs: deploy-github: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup node uses: actions/setup-node@v3 with: node-version: '16.14.0' - name: Depend install and build run: | npm install npm run build - name: Deploy GitHub pages uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.GITEE_PRIVATE_KEY }} external_repository: username/username.github.io publish_branch: master publish_dir: docs/.vuepress/dist allow_empty_commit: true force_orphan: true full_commit_message: 'feat: deploy pages' deploy-gitee: needs: deploy-github runs-on: ubuntu-latest steps: - name: Mirror to Gitee repo uses: Yikun/[email protected] with: src: github/username dst: gitee/username dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} mappings: "username.github.io=>username" static_list: 'username.github.io' force_update: true - name: Deploy Gitee pages uses: yanglbme/[email protected] with: gitee-username: username gitee-password: ${{ secrets.GITEE_PASSWORD }} gitee-repo: username branch: master 复制代码 🎉 写在最后

🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star ✨支持一下哦!

手动码字,如有错误,欢迎在评论区指正💬~

你的支持就是我更新的最大动力💪~

GitHub / Gitee、GitHub Pages、掘金、CSDN 同步更新,欢迎关注😉~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3