Backup your repos via GitHub Actions

Anything may be lost unless you have a backup for them.

This article tell you how to backup your git repositories via GitHub Actions.

Our target

In this article, our final target is to backup git repositories under the user-a to another user named user-b .

Preparation

Before all, we need to create a new git repository used to run the backup workflow, and there will be some files in it and you can use the command tree to have a check:

.
├── .github
│   └── workflows
│       └── mirror.yaml

Normally, we wouldn’t to expose our secrets directly, so we need to create some secrets in the GitHub repository settings which will be used to clone, create and push git repo:

  • GH_TOKEN_A is the user-a ‘s GitHub AccessToken
  • GH_TOKEN_B is the user-b ‘s GitHub AccessToken
  • SLACK_WEBHOOK is used to notify us while the workflow started or ended

Mirror Workflow

List user-a ‘s repos

We’ll use the command-line tool gh to list user-a ‘s github repositories:

echo ${GH_TOKEN_A} > gh_token_a
gh auth login --with-token < gh_token_a
gh repo list user-a -L 1000 > a_repos
cat a_repos
cat a_repos | wc -l

Create repo with the same name

Before push the git repo, we need to make the repo existed under the user user-b , like list repos above, we also use gh to create a git repo with the same name:

gh repo create user-b/${repo_name} --private --description "${repo}" -y || true

Clone repo

While we make sure that the repo with the same name is already existed under the user user-b , we can clone the git repo from the user user-a first.

Unlike normal git clone, we should use the flag --bare to
clone the repo:

git clone --bare https://${GH_TOKEN_A}@github.com/user-a/${repo_name}.git ${repo_name}

Push repo

Finally, we can push the cloned repo to user-b ‘s repo which has the same name.

And we will use two flags to push them:

  • --all means push all branches
  • --mirror means push all refs, contains all branches and tags
cd ${repo_name}
mirror_repo="https://${GH_TOKEN_B}@github.com/user-b/${repo_name}.git"
git push --all -f ${mirror_repo} || true
git push --mirror -f ${mirror_repo} || true

Full workflow script

This is the full workflow script:

name: Mirror repos

on:
  schedule:
    - cron: "0 18 * * 0-4" # 设置定时任务,周一到周五的凌晨2点进行备份
  workflow_dispatch: # 手动触发构建

jobs:
  mirror:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: GitHub CLI version
        run: gh --version

      - name: List repos
        env:
          GH_TOKEN_A: ${{ secrets.GH_TOKEN_A }}
        run: |
          echo ${GH_TOKEN_A} > gh_token_a
          gh auth login --with-token < gh_token_a
          gh repo list user-a -L 1000 > a_repos
          cat a_repos
          cat a_repos | wc -l

      - name: Mirror repos
        env:
          GH_TOKEN_A: ${{ secrets.GH_TOKEN_A }}
          GH_TOKEN_B: ${{ secrets.GH_TOKEN_B }}
          IGNORE_REPOS: "/repo_a/repo_b/"
          ONLY_BRANCH_REPOS: "/repo_c/repo_d/"
        run: |
          echo ${GH_TOKEN_B} > gh_token_b
          gh auth login --with-token < gh_token_b

          mkdir repos
          cd repos
          set -x
          cat ${GITHUB_WORKSPACE}/a_repos | while read repo; do
            repo_name=$(echo ${repo} | awk '{print $1}' | awk -F/ '{print $2}')
            [[ ${IGNORE_REPOS} =~ "/${repo_name}/" ]] && continue || true

            gh repo create user-b/${repo_name} --private --description "${repo}" -y || true
            rm -rf ${repo_name}

            git clone --bare https://${GH_TOKEN_A}@github.com/user-a/${repo_name}.git ${repo_name}

            cd ${repo_name}
            mirror_repo="https://${GH_TOKEN_B}@github.com/user-b/${repo_name}.git"
            git push --all -f ${mirror_repo} || true
            [[ ${ONLY_BRANCH_REPOS} =~ "/${repo_name}/" ]] && continue || true
            git push --mirror -f ${mirror_repo} || true
            cd -
          done

      - name: Slack Notification
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

18