├── Dockerfile ├── README.md ├── action.yml ├── entrypoint.sh └── git-sync.sh /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | LABEL "repository"="http://github.com/wei/git-sync" 4 | LABEL "homepage"="http://github.com/wei/git-sync" 5 | LABEL "maintainer"="Wei He " 6 | 7 | RUN apk add --no-cache git openssh-client && \ 8 | echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config 9 | 10 | ADD *.sh / 11 | 12 | ENTRYPOINT ["/entrypoint.sh"] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Sync 2 | 3 | A GitHub Action for syncing between two independent repositories using **force push**. 4 | 5 | ## Features 6 | 7 | - Sync branches between two GitHub repositories 8 | - Sync branches to/from a remote repository 9 | - GitHub action can be triggered on a timer or on push 10 | - To sync with current repository, please checkout [Github Repo Sync](https://github.com/marketplace/actions/github-repo-sync) 11 | 12 | ## Usage 13 | 14 | > Always make a full backup of your repo (`git clone --mirror`) before using this action. 15 | 16 | ### GitHub Actions 17 | 18 | ```yml 19 | # .github/workflows/git-sync.yml 20 | 21 | on: push 22 | jobs: 23 | git-sync: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: git-sync 27 | uses: wei/git-sync@v3 28 | with: 29 | source_repo: "source_org/repository" 30 | source_branch: "main" 31 | destination_repo: "destination_org/repository" 32 | destination_branch: "main" 33 | ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} # optional 34 | source_ssh_private_key: ${{ secrets.SOURCE_SSH_PRIVATE_KEY }} # optional, will override `SSH_PRIVATE_KEY` 35 | destination_ssh_private_key: ${{ secrets.DESTINATION_SSH_PRIVATE_KEY }} # optional, will override `SSH_PRIVATE_KEY` 36 | ``` 37 | 38 | ##### Using shorthand 39 | 40 | You can use GitHub repo shorthand like `username/repository`. 41 | 42 | ##### Using ssh 43 | 44 | > The `ssh_private_key`, or `source_ssh_private_key` and `destination_ssh_private_key` must be supplied if using ssh clone urls. 45 | 46 | ```yml 47 | source_repo: "git@github.com:username/repository.git" 48 | ``` 49 | or 50 | ```yml 51 | source_repo: "git@gitlab.com:username/repository.git" 52 | ``` 53 | 54 | ##### Using https 55 | 56 | > The `ssh_private_key`, `source_ssh_private_key` and `destination_ssh_private_key` can be omitted if using authenticated https urls. 57 | 58 | ```yml 59 | source_repo: "https://username:personal_access_token@github.com/username/repository.git" 60 | ``` 61 | 62 | #### Set up deploy keys 63 | 64 | > You only need to set up deploy keys if repository is private and ssh clone url is used. 65 | 66 | - Either generate different ssh keys for both source and destination repositories or use the same one for both, leave passphrase empty (note that GitHub deploy keys must be unique for each repository) 67 | 68 | ```sh 69 | $ ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 70 | ``` 71 | 72 | - In GitHub, either: 73 | 74 | - add the unique public keys (`key_name.pub`) to _Repo Settings > Deploy keys_ for each repository respectively and allow write access for the destination repository 75 | 76 | or 77 | 78 | - add the single public key (`key_name.pub`) to _Personal Settings > SSH keys_ 79 | 80 | - Add the private key(s) to _Repo > Settings > Secrets_ for the repository containing the action (`SSH_PRIVATE_KEY`, or `SOURCE_SSH_PRIVATE_KEY` and `DESTINATION_SSH_PRIVATE_KEY`) 81 | 82 | #### Advanced: Sync all branches 83 | 84 | To Sync all branches from source to destination, use `source_branch: "refs/remotes/source/*"` and `destination_branch: "refs/heads/*"`. But be careful, branches with the same name including `master` will be overwritten. 85 | 86 | ```yml 87 | source_branch: "refs/remotes/source/*" 88 | destination_branch: "refs/heads/*" 89 | ``` 90 | 91 | #### Advanced: Sync all tags 92 | 93 | To Sync all tags from source to destination, use `source_branch: "refs/tags/*"` and `destination_branch: "refs/tags/*"`. But be careful, tags with the same name will be overwritten. 94 | 95 | ```yml 96 | source_branch: "refs/tags/*" 97 | destination_branch: "refs/tags/*" 98 | ``` 99 | 100 | ### Docker 101 | 102 | ```sh 103 | $ docker run --rm -e "SSH_PRIVATE_KEY=$(cat ~/.ssh/id_rsa)" $(docker build -q .) \ 104 | $SOURCE_REPO $SOURCE_BRANCH $DESTINATION_REPO $DESTINATION_BRANCH 105 | ``` 106 | 107 | ## Author 108 | 109 | [Wei He](https://github.com/wei) _github@weispot.com_ 110 | 111 | ## License 112 | 113 | [MIT](https://wei.mit-license.org) 114 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Git Sync Action 2 | author: Wei He 3 | description: 🔃 Sync between two independent repositories 4 | branding: 5 | icon: 'git-branch' 6 | color: 'gray-dark' 7 | inputs: 8 | source_repo: 9 | description: GitHub repo slug or full url 10 | required: true 11 | source_branch: 12 | description: Branch name to sync from 13 | required: true 14 | destination_repo: 15 | description: GitHub repo slug or full url 16 | required: true 17 | destination_branch: 18 | description: Branch name to sync to 19 | required: true 20 | ssh_private_key: 21 | description: SSH key used to authenticate with source and destination ssh urls provided (optional if public or https url with authentication) 22 | required: false 23 | source_ssh_private_key: 24 | description: SSH key used to authenticate with source ssh url provided (optional if public or https url with authentication) 25 | required: false 26 | destination_ssh_private_key: 27 | description: SSH key used to authenticate with destination ssh url provided (optional if public or https url with authentication) 28 | required: false 29 | runs: 30 | using: 'docker' 31 | image: 'Dockerfile' 32 | env: 33 | SSH_PRIVATE_KEY: ${{ inputs.ssh_private_key }} 34 | SOURCE_SSH_PRIVATE_KEY: ${{ inputs.source_ssh_private_key }} 35 | DESTINATION_SSH_PRIVATE_KEY: ${{ inputs.destination_ssh_private_key }} 36 | args: 37 | - ${{ inputs.source_repo }} 38 | - ${{ inputs.source_branch }} 39 | - ${{ inputs.destination_repo }} 40 | - ${{ inputs.destination_branch }} -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [[ -n "$SSH_PRIVATE_KEY" ]]; then 6 | mkdir -p /root/.ssh 7 | echo "$SSH_PRIVATE_KEY" | sed 's/\\n/\n/g' >/root/.ssh/id_rsa 8 | chmod 600 /root/.ssh/id_rsa 9 | fi 10 | 11 | if [[ -n "$SOURCE_SSH_PRIVATE_KEY" ]]; then 12 | mkdir -p /root/.ssh 13 | echo "$SOURCE_SSH_PRIVATE_KEY" | sed 's/\\n/\n/g' >/root/.ssh/src_rsa 14 | chmod 600 /root/.ssh/src_rsa 15 | fi 16 | 17 | if [[ -n "$DESTINATION_SSH_PRIVATE_KEY" ]]; then 18 | mkdir -p /root/.ssh 19 | echo "$DESTINATION_SSH_PRIVATE_KEY" | sed 's/\\n/\n/g' >/root/.ssh/dst_rsa 20 | chmod 600 /root/.ssh/dst_rsa 21 | fi 22 | 23 | mkdir -p ~/.ssh 24 | cp /root/.ssh/* ~/.ssh/ 2>/dev/null || true 25 | 26 | sh -c "/git-sync.sh $*" 27 | -------------------------------------------------------------------------------- /git-sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | SOURCE_REPO=$1 6 | SOURCE_BRANCH=$2 7 | DESTINATION_REPO=$3 8 | DESTINATION_BRANCH=$4 9 | 10 | if ! echo $SOURCE_REPO | grep -Eq ':|@|\.git\/?$'; then 11 | if [[ -n "$SSH_PRIVATE_KEY" || -n "$SOURCE_SSH_PRIVATE_KEY" ]]; then 12 | SOURCE_REPO="git@github.com:${SOURCE_REPO}.git" 13 | GIT_SSH_COMMAND="ssh -v" 14 | else 15 | SOURCE_REPO="https://github.com/${SOURCE_REPO}.git" 16 | fi 17 | fi 18 | 19 | if ! echo $DESTINATION_REPO | grep -Eq ':|@|\.git\/?$'; then 20 | if [[ -n "$SSH_PRIVATE_KEY" || -n "$DESTINATION_SSH_PRIVATE_KEY" ]]; then 21 | DESTINATION_REPO="git@github.com:${DESTINATION_REPO}.git" 22 | GIT_SSH_COMMAND="ssh -v" 23 | else 24 | DESTINATION_REPO="https://github.com/${DESTINATION_REPO}.git" 25 | fi 26 | fi 27 | 28 | echo "SOURCE=$SOURCE_REPO:$SOURCE_BRANCH" 29 | echo "DESTINATION=$DESTINATION_REPO:$DESTINATION_BRANCH" 30 | 31 | if [[ -n "$SOURCE_SSH_PRIVATE_KEY" ]]; then 32 | # Clone using source ssh key if provided 33 | git clone -c core.sshCommand="/usr/bin/ssh -i ~/.ssh/src_rsa" "$SOURCE_REPO" /root/source --origin source && cd /root/source 34 | else 35 | git clone "$SOURCE_REPO" /root/source --origin source && cd /root/source 36 | fi 37 | 38 | git remote add destination "$DESTINATION_REPO" 39 | 40 | # Pull all branches references down locally so subsequent commands can see them 41 | git fetch source '+refs/heads/*:refs/heads/*' --update-head-ok 42 | 43 | # Print out all branches 44 | git --no-pager branch -a -vv 45 | 46 | if [[ -n "$DESTINATION_SSH_PRIVATE_KEY" ]]; then 47 | # Push using destination ssh key if provided 48 | git config --local core.sshCommand "/usr/bin/ssh -i ~/.ssh/dst_rsa" 49 | fi 50 | 51 | git push destination "${SOURCE_BRANCH}:${DESTINATION_BRANCH}" -f 52 | --------------------------------------------------------------------------------