# netlify-plugin-branch-sync

This is a Netlify build plugin.

Its purpose is to sync a configured source branch to other configured destination branches.

The configuration is done inside of index.js instead of using environment variables so that the branch configurations are version controlled.

`SYNC_SOURCE` is the source branch that will be pushed to the destination branches.

`SYNC_DEST` is an array of destination branches that will receive the push from `SYNC_SOURCE`.

This plugin only executes its sync operations when the branch being built in Netlify is the `SYNC_SOURCE` branch and the build context is 'Branch build'. Any other branch or build context is ignored and this plugin does nothing.

## How to setup

In Bitbucket, create an access token for the repository this plugin will act on, e.g. https://bitbucket.org/scandic-swap/web/admin/access-tokens.

It needs to have the following permissions:

- `repository`
- `repository:write`

Add the following environment variables in the Netlify dashboard.

> **IMPORTANT!**
>
> Be sure to set them to only be available in the "Builds" scope and only set a value for the "Branch deploys" configuration.

- **BITBUCKET_USER_EMAIL**: The email for the access token (Bitbucket generates this and provides it when creating an access token)
- **BITBUCKET_ACCESS_TOKEN**: The access token

## How it works

It uses two build plugin events:

- `onPreBuild`
- `onPostBuild`

It fails the whole build if the plugin does not complete the sync to all the branches.

Since it uses `git` directly and we want to sync to latest always, the plugin can be rerun in case of failures and `git` will resume where needed.

### Event: onPreBuild

- Checks that the current branch being built is `SYNC_SOURCE` and the build context is a branch build, otherwise does nothing.
- Checks for the presence of the required environment variables.
- Uses `git` to configure the `user.email` and set it to environment variable `BITBUCKET_USER_EMAIL`
- Attempts to restore the clone from the build cache and if that fails proceeds to clone it.
- Uses `git` to fetches the configured branches, `SYNC_SOURCE` and `SYNC_DEST` from origin, skipping all tags.
- Loops over the branches and uses `git` to sequentially push the latest ref on `SYNC_SOURCE` to each `SYNC_DEST` branch respectively. Since a single push completes fast (under 200 ms), we opt to not push in parallel to try and avoid any rate limits.
- Reports to Netlify the completion and provides some info to the "Deploy summary" for the build.
- If anything throws an error during execution the **whole build is failed**

### Event: onPostBuild

- Checks that the current branch being built is `SYNC_SOURCE` and the build context is a branch build, otherwise does nothing.
- Attempts to save the clone to the build cache. If it fails by throwing an error the **whole build is failed**. If it fails without throwing an error the build will succeed.

## Under the hood

### Bare repository

Since this plugin only does git operations that do not need a working copy, it clones the repository it uses for the operations as a bare repository.

The layout of a bare repository differs from a working copy. The way git identifies a working copy with through the presence of a folder named `.git`. Bare repositories do not have this folder. Instead git uses different heuristics to determine if the directory is a clone, one of which is the presence of a folder named `refs`.

The folder named `refs` only contains two folders after a bare clone operation, `heads` and `tags`. These two folder are both empty themselves.

This fact poses a challenge for the build cache because it only caches files and not empty directories.

#### Build cache usage

This plugin uses the [utilities for caching files in Netlify Build](https://github.com/netlify/build/blob/main/packages/cache-utils/README.md).

This utility uses [cpy](https://www.npmjs.com/package/cpy) to **copy** files to and from the cache and uses [move-file](https://www.npmjs.com/package/move-file) to **move** files to and from the cache.

The use of `cpy` is what causes the build cache to not include empty directories because `cpy` only copies files and skips empty directories.

So to work around the problem this plugin uses the `move mode` of the build cache API which instead uses `move-file` instead of `cpy`, keeping the full directory layout as git requires it.

The `move mode` is required for both the `restore()` and `save()` build cache operations for the workaround to work. It is done by passing `move: true` as options to the calls.
