148 lines
3.8 KiB
Markdown
148 lines
3.8 KiB
Markdown
|
## Background
|
||
|
|
||
|
Sometimes you need to merge one Git repo into another. This is a common task
|
||
|
when administrating a monorepo.
|
||
|
|
||
|
Here's a checklist that I follow:
|
||
|
|
||
|
1. Detect leaked secrets.
|
||
|
1. Rotate leaked secrets.
|
||
|
1. Purge leaked secrets from repo history.
|
||
|
1. Create mainline references to branches (for deployments).
|
||
|
1. Subtree-merge into the target repo.
|
||
|
1. Format the code.
|
||
|
1. Celebrate!
|
||
|
|
||
|
## Secrets
|
||
|
|
||
|
**Note:** If you notice any leaked secrets, first and foremost rotate them
|
||
|
before moving on...
|
||
|
|
||
|
`gitleaks` supports `gitleaks protect`, but that doesn't seem to work for `WRN`
|
||
|
level leaks, which in my experience often contain sensitive cleartext. We can
|
||
|
use `git-filter-repo` to purge the cleartext from our repo history.
|
||
|
|
||
|
Let's make a `secrets.txt` file that we can feed `git-filter-repo`:
|
||
|
|
||
|
```shell
|
||
|
λ gitleaks detect -r /tmp/secrets.json
|
||
|
λ jq -r 'map_values(.Secret) | .[]' /tmp/secrets.txt
|
||
|
```
|
||
|
|
||
|
Now for the redacting...
|
||
|
|
||
|
```shell
|
||
|
λ git-filter-repo --force --replace-text /tmp/secrets.txt
|
||
|
```
|
||
|
|
||
|
Verify that the secrets were removed.
|
||
|
|
||
|
```shell
|
||
|
λ rg --hidden '\*\*\*REMOVED\*\*\*'
|
||
|
λ gitleaks detect -v
|
||
|
```
|
||
|
|
||
|
Looks good! Let's move on to support the adopted repo's deploy strategy.
|
||
|
|
||
|
## Supporting Deploys
|
||
|
|
||
|
While deploying services when someone pushes to a given branch is a common
|
||
|
deployment strategy, branch-based deployment don't make a whole lot of sense in
|
||
|
a monorepo.
|
||
|
|
||
|
When adopting another repo, you'll typically encounter a Github Action
|
||
|
configuration that contains a section like this:
|
||
|
|
||
|
```yaml
|
||
|
on:
|
||
|
push:
|
||
|
- staging
|
||
|
- production
|
||
|
```
|
||
|
|
||
|
In our monorepo, `staging` and `production` don't exist. And I don't think we
|
||
|
want to support them either. `staging` and `production` are ambiguous in a
|
||
|
monorepo that hosts multiple services each of which likely having its own notion
|
||
|
of `staging` and `production`.
|
||
|
|
||
|
Doing "pinned releases" where a service is deployed from a `git` revision from
|
||
|
the mainline branch works well in these scenarios. In order to support this we
|
||
|
need to make sure the adopted repo has references to
|
||
|
|
||
|
`git subtree add` asks us to define which branch it should use when grafting the
|
||
|
repository onto our monorepo. We'll use `main` (or whatever the mainline branch
|
||
|
is).
|
||
|
|
||
|
In order to support the *current* deployments while migrating to a pinned
|
||
|
release strategy, we have to ensure that `main` has a commit containing the same
|
||
|
tree state as `staging` *and* another commit containing the same tree state as
|
||
|
`production`. Let's do that!
|
||
|
|
||
|
```shell
|
||
|
λ git checkout main # ensure you're on the main branch
|
||
|
λ git diff main staging >/tmp/main-to-staging.patch
|
||
|
λ git diff main production >/tmp/main-to-production.patch
|
||
|
```
|
||
|
|
||
|
### staging
|
||
|
|
||
|
```shell
|
||
|
λ git apply /tmp/main-to-staging.patch
|
||
|
λ git add . && git commit # chore: main -> staging
|
||
|
λ git revert HEAD
|
||
|
λ git commit --amend # revert: staging -> main
|
||
|
```
|
||
|
|
||
|
### production
|
||
|
|
||
|
```shell
|
||
|
λ git apply /tmp/main-to-production.patch
|
||
|
λ git add . && git commit # chore: main -> production
|
||
|
λ git revert HEAD
|
||
|
λ git commit --amend # revert: production -> main
|
||
|
```
|
||
|
|
||
|
Now let's check our work:
|
||
|
|
||
|
```shell
|
||
|
λ git log --oneline
|
||
|
38f4422 revert: production -> main
|
||
|
f071a9f chore: main -> production
|
||
|
02ea731 revert: staging -> main
|
||
|
308ed90 chore: main -> staging
|
||
|
```
|
||
|
|
||
|
When we go to support pinned releases we can do something like so:
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"staging": "308ed90",
|
||
|
"production": "f071a9f"
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Subtree Merge
|
||
|
|
||
|
Now the repo is ready to be merged.
|
||
|
|
||
|
```shell
|
||
|
λ git subtree add --prefix=foo/bar/baz path/to/baz main
|
||
|
λ git commit --amend # subtree: Dock baz into monorepo!
|
||
|
```
|
||
|
|
||
|
## Formatting
|
||
|
|
||
|
Some CI enforces code formatting standards, so you may need to run that:
|
||
|
|
||
|
```shell
|
||
|
λ repofmt
|
||
|
λ git add . && git commit # chore(fmt): Format the codes
|
||
|
```
|
||
|
|
||
|
Lastly, if you need the latest monorepo code from `origin/main` before opening a
|
||
|
pull request, the following should work:
|
||
|
|
||
|
```shell
|
||
|
λ git fetch origin main && git rebase origin/main --rebase-merges --strategy=subtree
|
||
|
```
|