Pulldozer is a simple CLI tool for batch editing multiple GitHub repos.
You give Pulldozer a transformation script and it spits out pull requests. Duolingo has used Pulldozer to create well over 9000 PRs to date!
Clone this repo onto any Unix machine that has curl and jq. Set your GITHUB_TOKEN environment variable to an access token with repo scope and SSO enabled. To perform a batch edit (a.k.a. codemod):
-
Create a script file that defines a
COMMIT_MESSAGEstring,transformfunction, andREPOSlist.Click here to see a Shell example
COMMIT_MESSAGE='Fix "langauge" typos' transform() { # Write your transformation logic inside this function. GitHub org name # and repo name are passed into this `transform` function as vars $1 and # $2, respectively. echo "Hello world from $1/$2" > README.md # Pulldozer provides a `replace_all` helper function for replacing text # across all repo files. It's basically glorified sed. replace_all 'langauge' 'language' # Advanced `replace_all` example: regex, capture grouping, multi-line # matching, and file path filtering replace_all '(\nprotobuf==)\S+' '\13.19.4' 'requirements\.(in|txt)$' } REPOS=' artnc/dotfiles duolingo/halflife-regression duolingo/rtl-viewpager ' # Optional but recommended: Markdown to include in pull request descriptions DESCRIPTION='[Correct spelling](https://en.wiktionary.org/wiki/language)' # Optional: Markdown that will be posted as a comment on PRs COMMENT='This is a pull request comment' # Optional: Declare this variable to enable GitHub auto-merge on PRs AUTOMERGE=1
By default, Pulldozer will interpret your script as POSIX shell. If you want to use Bash instead, just run
bash ./pulldozerinstead of./pulldozerduring step 2 below.Click here to see a JavaScript example
const COMMIT_MESSAGE = 'Fix "langauge" typos'; // Write your transformation logic inside this function. GitHub org name and // repo name are passed in as parameters. const transform = async (org, repo) => { const { writeFile } = require("fs").promises; await writeFile("README.md", `Hello world from ${org}/${repo}`); // Pulldozer provides a `replace_all` helper function (no need to import) // for replacing text across all repo files. This helper is easier than // traversing the repo yourself, and it also respects .gitignore. await replace_all("langauge", "language"); // Advanced `replace_all` example: regex, capture grouping, multiline // matching, editing only a subset of repo files (optional third param) await replace_all( /(\nprotobuf==)\S+/, "$13.19.4", /requirements\.(in|txt)$/, ); }; const REPOS = [ "artnc/dotfiles", "duolingo/halflife-regression", "duolingo/rtl-viewpager", ]; // Optional but recommended: Markdown to include in pull request descriptions const DESCRIPTION = "[Correct spelling](https://en.wiktionary.org/wiki/language)"; // Optional: Markdown that will be posted as a comment on PRs const COMMENT = "This is a pull request comment"; // Optional: Declare this variable to enable GitHub auto-merge on PRs const AUTOMERGE = 1;
Click here to see a Python example
COMMIT_MESSAGE = 'Fix "langauge" typos' # Write your transformation logic inside this function. GitHub org name and # repo name are passed in as parameters. def transform(org, repo): with open("README.md", "w") as f: f.write(f"Hello world from {org}/{repo}") # Pulldozer provides a `replace_all` helper function (no need to import) # for replacing text across all repo files. This helper is easier than # using `os.walk` and `re.sub`, and it also respects .gitignore. replace_all("langauge", "language") # Advanced `replace_all` example: regex, capture grouping, multiline # matching, editing only a subset of repo files (optional third param) replace_all(r"(\nprotobuf==)\S+", r"\13.19.4", r"requirements\.(in|txt)$") REPOS = [ "artnc/dotfiles", "duolingo/halflife-regression", "duolingo/rtl-viewpager", ] # Optional but recommended: Markdown to include in pull request descriptions DESCRIPTION = "[Correct spelling](https://en.wiktionary.org/wiki/language)" # Optional: Markdown that will be posted as a comment on PRs COMMENT = "This is a pull request comment" # Optional: Declare this variable to enable GitHub auto-merge on PRs AUTOMERGE = 1
-
Run
./pulldozer /path/to/your/script. Pulldozer will generate a preview diff and ask for confirmation before creating PRs.
Duolingo is hiring! Apply at https://www.duolingo.com/careers
