• Stars
    star
    434
  • Rank 100,274 (Top 2 %)
  • Language
    Go
  • License
    Apache License 2.0
  • Created over 3 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A simple tool to help apply changes across many GitHub repositories simultaneously

turbolift

A simple tool to help apply changes across many GitHub repositories simultaneously.

Philosophy

Anyone who has had to manually make changes to many GitHub repositories knows that it's hard to beat the simplicity of just cloning the repositories and updating them locally. You can use any tools necessary to make the change, and there's a degree of immediacy in having local files to inspect, tweak or run validation.

It's dumb but it works. It doesn't scale well, though. Manually cloning and raising PRs against tens/hundreds of repositories is painful and boring.

Turbolift essentially automates the boring parts and stays out of the way when it comes to actually making the changes. It automates cloning, committing, and raising PRs en-masse, so that you can focus on the substance of the change.

Historical note: Turbolift supersedes an internal system at Skyscanner named Codelift. Codelift was a centralised batch system, requiring changes to be scripted upfront and run overnight. While Codelift was useful, we have found that a decentralised, interactive tool is far easier and quicker for people to use in practice.

This blog post gives a longer background for the thinking behind Turbolift.

Demo

This demo shows Turbolift in action, creating a simple PR in two repositories:

Screencast demo of turbolift in use

Installation

Using brew (recommended) Install turbolift using brew from Skyscanner's tap, as follows:
brew install skyscanner/tools/turbolift

Note that the GitHub CLI, gh is a dependency of Turbolift and will be installed automatically.

Downloading binaries

Pre-built binary archives can be downloaded from the Releases page.

  • Download, extract the archive, and move it onto your PATH.
  • Note that the binaries are not currently notarized for MacOS Gatekeeper. If errors are displayed, use xattr -c PATH_TO_TURBOLIFT_BINARY to un-quarantine the binary, or right-click on the binary in Finder and choose 'Open' once to allow future execution. Distribution will be improved under #43.

You must also have the GitHub CLI, gh, installed:

  • Install using brew install gh

Before using Turbolift, run gh auth login once and follow the prompts, to authenticate against github.com and/or your GitHub Enterprise server.

Basic usage:

Making changes with turbolift is split into six main phases:

  1. init - getting set up
  2. Identifying the repos to operate upon
  3. Running a mass clone of the repos (by default, it will create a fork in your user space)
  4. Making changes to every repo
  5. Committing changes to every repo
  6. Creating a PR for every repo

It is expected that you'll go through these phases in series.

The turbolift tool automates as much of the Git/GitHub heavy lifting as possible, but leaves you to use whichever tools are appropriate for making the actual changes.

Caveats

With great power comes great responsibility. We encourage Turbolift users to consider the following guidelines:

  • Don't use Turbolift to raise pointless PRs. If a reviewer might think the change is trivial or unimportant, think about whether it's actually needed.
  • If you need to make a change to a large number of repositories, we've found that it's generally better to raise PRs to a small subset at first and collect feedback. Simply comment out repositories in repos.txt to make Turbolift temporarily ignore them.
  • For complicated or potentially contentious changes, think about ways to validate them before raising PRs. This could range from working in a pair, through writing a peer-reviewed script, all the way to preparing a design document for the planned changes.
  • If you can run automated tests locally, then do (e.g. turbolift foreach to run linting and tests for each repository).
  • Raising draft PRs can be a good way to collect feedback, especially CI test results, with less pressure on reviewers. Use turbolift create-prs --draft
  • In an organisation with shared infrastructure (e.g. CI), raising many PRs in a short timeframe can cause a lot of load. Consider spacing out PR creation using the --sleep option or by commenting out chunks of repositories in repos.txt.

Detailed usage

init - getting set up

As per the installation instructions above, make sure gh is installed and authenticated before starting.

If working with repositories on a GitHub Enterprise server, ensure that you have the environment variable GH_HOST set to point to that server.

To begin working with Turbolift and create a 'campaign' to hold settings and working copies of repositories:

turbolift init --name CAMPAIGN_NAME

This creates a new turbolift 'campaign' directory ready for you to work in. Note that CAMPAIGN_NAME will be used as the branch name for any changes that are created

Next, please run:

cd CAMPAIGN_NAME

Identifying the repos to operate upon

Update repos.txt with the names of the repos that need changing (either manually or using a tool to identify the repos).

gh-search is an excellent tool for performing GitHub code searches, and can output a list of repositories in a format that turbolift understands:

e.g.

$ gh-search --repos-with-matches YOUR_GITHUB_CODE_SEARCH_QUERY > repos.txt

Working on multiple repo files

Occasionally you may need to work on different repo files. For instance the repos can be divided in sub categories and the same change don't apply to them the same way. The default repo file is called repos.txt but you can override this with the --repos flag.

turbolift foreach --repos repoFile1.txt sed 's/pattern1/replacement1/g'
turbolift foreach --repos repoFile2.txt sed 's/pattern2/replacement2/g'

Running a mass clone

turbolift clone

This creates a fork and clones all repositories listed in the repos.txt file into the work directory. You may wish to skip the fork and work on the upstream repository branch directly with the flag --no-fork.

NTLD: if one of the repositories in the list requires a fork to create a PR, omit the --no-fork flag and let all the repositories be forked. For now it's a all-or-nothing scenario.

Making changes

Now, make changes to the checked-out repos under the work directory. You can do this manually using an editor, using sed and similar commands, or using codemod/comby, etc.

You are free to use any tools that help get the job done.

If you wish to, you can run the same command against every repo using turbolift foreach ... (where ... is the shell command you want to run).

For example, you might choose to:

  • turbolift foreach rm somefile - to delete a particular file
  • turbolift foreach sed -i '' 's/foo/bar/g' somefile - to find/replace in a common file
  • turbolift foreach make test - for example, to run tests (using any appropriate command to invoke the tests)
  • turbolift foreach git add somefile - to stage a file that you have created

At any time, if you need to update your working copy branches from the upstream, you can run turbolift foreach git pull upstream master.

It is highly recommended that you run tests against affected repos, if it will help validate the changes you have made.

Committing changes

When ready to commit changes across all repos, run:

turbolift commit --message "Your commit message"

This command is a no-op on any repos that do not have any changes. Note that the commit will be run with the --all flag set, meaning that it is not necessary to stage changes using git add/rm for changed files. Newly created files will still need to be staged using git add.

Repeat if you want to make multiple commits.

Creating PRs

Edit the PR title and description in README.md.

Next, to push and raise PRs against changed repos, run:

turbolift create-prs

Use turbolift create-prs --sleep 30s to, for example, force a 30s pause between creation of each PR. This can be helpful in reducing load on shared infrastructure.

Important: if raising many PRs, you may generate load on shared infrastucture such as CI. It is highly recommended that you:

  • slow the rate of PR creation by making Turbolift sleep in between PRs
  • create PRs in batches, for example by commenting out repositories in repos.txt
  • Use the --draft flag to create the PRs as Draft

If you need to mass-close PRs, it is easy to do using turbolift foreach and the gh GitHub CLI (docs):

For example:

turbolift foreach gh pr close --delete-branch YOUR_USERNAME:CAMPAIGN_NAME

After creating PRs

Viewing status

While it's simple to search for PRs in GitHub search, turbolift pr-status can be used to view PR status in the terminal. For example:

Viewing a summary of PRs:

$ turbolift pr-status
...
State        Count
Merged       139
Open         53
Closed       29
Skipped      0
No PR Found  1

Viewing a detailed list of status per repo:

$ turbolift pr-status --list
...
Repository                                                State   Reviews            URL
redacted/redacted                                         OPEN    REVIEW_REQUIRED    https://github.redacted/redacted/redacted/pull/262
redacted/redacted                                         OPEN    REVIEW_REQUIRED    https://github.redacted/redacted/redacted/pull/515
redacted/redacted                                         OPEN    REVIEW_REQUIRED    https://github.redacted/redacted/redacted/pull/342
redacted/redacted                                         MERGED  APPROVED           https://github.redacted/redacted/redacted/pull/407
redacted/redacted                                         MERGED  REVIEW_REQUIRED    https://github.redacted/redacted/redacted/pull/220
redacted/redacted                                         OPEN    REVIEW_REQUIRED    https://github.redacted/redacted/redacted/pull/105
redacted/redacted                                         MERGED  APPROVED           https://github.redacted/redacted/redacted/pull/532
redacted/redacted                                         MERGED  APPROVED           https://github.redacted/redacted/redacted/pull/268
redacted/redacted                                         OPEN    REVIEW_REQUIRED    https://github.redacted/redacted/redacted/pull/438
...

Closing all PRs

To close all PRs currently opened under the campaign, there is a --close flag:

turbolift update-prs --close [--yes]

If the flag --yes is not present, a confirmation prompt will be presented to the user.

Status: Preview

This tool is fully functional, but we have improvements that we'd like to make, and would appreciate feedback.

Contributing

See CONTRIBUTING.md

Local development

To build locally:

make build

To run tests locally:

make test

More Repositories

1

SkyFloatingLabelTextField

A beautiful and flexible text field control implementation of "Float Label Pattern". Written in Swift.
Swift
4,094
star
2

backpack

Backpack Design System
TypeScript
492
star
3

whispers

Identify hardcoded secrets in static structured text
Python
480
star
4

LambdaGuard

AWS Serverless Security
Python
399
star
5

cfripper

Library and CLI tool for analysing CloudFormation templates and check them for security compliance.
Python
396
star
6

Dixie

Dixie, turning chaos to your advantage.
Objective-C
192
star
7

aiotask-context

Contextual information for asyncio tasks
Python
161
star
8

engineering-principles

Skyscanner's Engineering Principles
133
star
9

backpack-android

Backpack Design System
Kotlin
127
star
10

applicationset-progressive-sync

Progressive sync controller for Argo ApplicationSet
Go
124
star
11

skyscanner-python-sdk

Skyscanner Python SDK
Python
121
star
12

full-stack-recruitment-test

JavaScript
110
star
13

backpack-ios

Backpack Design System
Swift
108
star
14

sonar-secrets

SonarQube plugin for identifying hardcoded secrets, such as passwords, API keys, AWS credentials, etc..
Java
99
star
15

pyfailsafe

Simple failure handling. Failsafe implementation in Python
Python
89
star
16

kms-issuer

KMS issuer is a cert-manager Certificate Request controller that uses AWS KMS to sign the certificate request.
Go
61
star
17

backpack-react-native

Backpack Design System
JavaScript
49
star
18

pycfmodel

A python model for Cloud Formation scripts
Python
27
star
19

ensure-node-env

A script that helps ensure you have the correct node & npm versions installed in your environment.
JavaScript
19
star
20

historical-bank-ruby

A Ruby Bank that serves historical exchange rates
Ruby
15
star
21

gradle-time-logger-plugin

Gradle plugin to log your build time
Kotlin
12
star
22

stylelint-config-skyscanner

Skyscanner's very own stylelint config.
JavaScript
10
star
23

gha-aws-oidc-sample

Enforcing per-repo privileges on AWS with GitHub Actions OIDC, without session tags
9
star
24

backpack-foundations

The foundational repo for Backpack platforms
JavaScript
9
star
25

eslint-config-skyscanner

Skyscanner's ESLint configuration
JavaScript
7
star
26

skyscanner-contentful-management

CLI tool for performing bulk actions with content managed using Contentful
Python
7
star
27

backpack-docs

Backpack design system's docs
JavaScript
7
star
28

skyscanner.github.io

Homepage of Skyscanner Open Source Software https://skyscanner.github.io
JavaScript
5
star
29

cra-template-backpack

The Backpack template used for Backpack React Scripts app creation.
HTML
4
star
30

backpack-node-sass

Parallelised node-sass with Backpack support.
JavaScript
3
star
31

OpenTSDB-rollup

Spark job generating rollup data points from a snapshot of an OpenTSDB raw data table
Java
3
star
32

backpack-usage-analyser

A tool to analyse the usage of Backpack resources in codebases.
JavaScript
2
star
33

grpc-asyncio

Python
2
star
34

eslint-plugin-backpack

JavaScript
1
star
35

eslint-config-skyscanner-with-prettier

Skyscanner's eslint config with additional support for Prettier
JavaScript
1
star
36

helm-argocd-appofapps

A helm chart that provides a relatively simple app of apps pattern
Smarty
1
star