├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── help-request.md └── workflows │ ├── bot.yml │ ├── docs.yml │ ├── infrastructure.yml │ ├── web_api.yml │ └── web_ui.yml ├── .gitignore ├── .kodiak.toml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── design.dot ├── design.svg └── logo.png ├── bot ├── .coveragerc ├── .dockerignore ├── .pylintrc ├── .python-version ├── .vscode │ ├── extensions.json │ └── settings.json ├── Dockerfile ├── README.md ├── docker-compose.yml ├── example.env ├── kodiak │ ├── __init__.py │ ├── app_config.py │ ├── assertions.py │ ├── cli.py │ ├── config.py │ ├── conftest.py │ ├── dependencies.py │ ├── entrypoints │ │ ├── __init__.py │ │ ├── ingest.py │ │ └── worker.py │ ├── errors.py │ ├── evaluation.py │ ├── events │ │ ├── README.md │ │ ├── __init__.py │ │ ├── base.py │ │ ├── check_run.py │ │ ├── pull_request.py │ │ ├── pull_request_review.py │ │ ├── pull_request_review_thread.py │ │ ├── push.py │ │ └── status.py │ ├── http.py │ ├── logging.py │ ├── messages.py │ ├── pull_request.py │ ├── queries │ │ ├── __init__.py │ │ └── commits.py │ ├── queue.py │ ├── redis_client.py │ ├── refresh_pull_requests.py │ ├── schemas.py │ ├── test │ │ └── fixtures │ │ │ ├── api │ │ │ └── get_event │ │ │ │ ├── behind.json │ │ │ │ ├── no_author.json │ │ │ │ └── no_latest_sha.json │ │ │ ├── config │ │ │ ├── config-schema.json │ │ │ ├── v1-default.toml │ │ │ ├── v1-opposite.1.toml │ │ │ └── v1-opposite.2.toml │ │ │ ├── config_utils │ │ │ ├── pydantic-error.md │ │ │ └── toml-error.md │ │ │ └── events │ │ │ ├── check_run │ │ │ ├── check_run_completed.json │ │ │ ├── check_run_created.json │ │ │ ├── check_run_created_incomplete.json │ │ │ ├── check_run_event.json │ │ │ └── check_run_event_pull_requests.json │ │ │ ├── pull_request │ │ │ ├── assigned.json │ │ │ ├── closed.json │ │ │ ├── edited.json │ │ │ ├── labeled.json │ │ │ ├── labeled_full.json │ │ │ ├── opened_draft.json │ │ │ ├── opened_reviewers_assignees_milestone_project.json │ │ │ ├── pr_opened.json │ │ │ ├── pull_request_event.json │ │ │ ├── review_requested.json │ │ │ └── synchronize.json │ │ │ ├── pull_request_review │ │ │ ├── approved.json │ │ │ ├── changes_requested.json │ │ │ ├── commented.json │ │ │ ├── pull_request_review_event.json │ │ │ └── pull_request_review_thread_event.json │ │ │ ├── push │ │ │ ├── create_patch.json │ │ │ ├── master.json │ │ │ └── push_event.json │ │ │ └── status │ │ │ ├── missing_target_url.json │ │ │ ├── pending.json │ │ │ ├── short.json │ │ │ ├── status_event.json │ │ │ └── success.json │ ├── test_config.py │ ├── test_config_utils.py │ ├── test_evaluation.py │ ├── test_event_handlers.py │ ├── test_events.py │ ├── test_logging.py │ ├── test_main.py │ ├── test_pull_request.py │ ├── test_queries.py │ ├── test_queue.py │ ├── test_text.py │ ├── test_utils.py │ ├── tests │ │ ├── __init__.py │ │ ├── dependencies │ │ │ ├── __init__.py │ │ │ ├── pull_requests │ │ │ │ ├── update-major-github_action.txt │ │ │ │ ├── update-major-two_packages.txt │ │ │ │ ├── update-major-two_packages_2.txt │ │ │ │ ├── update-minor-single_package.txt │ │ │ │ ├── update-patch-dependabot_batch.txt │ │ │ │ ├── update-patch-dependabot_single.txt │ │ │ │ └── update-patch-lock_file_maintenance.txt │ │ │ ├── test_dependabot.py │ │ │ ├── test_dependencies.py │ │ │ └── test_renovate.py │ │ ├── evaluation │ │ │ ├── test_automerge_dependencies.py │ │ │ ├── test_branch_protection.py │ │ │ ├── test_check_runs.py │ │ │ ├── test_merge_message.py │ │ │ ├── test_merge_message_cut_body.py │ │ │ ├── test_merge_message_trailers.py │ │ │ └── test_merge_method.py │ │ ├── event_handlers │ │ │ ├── __init__.py │ │ │ └── test_check_run.py │ │ ├── fixtures.py │ │ ├── test_fixtures.py │ │ └── test_messages.py │ ├── text.py │ └── throttle.py ├── poetry.lock ├── pyproject.toml ├── s │ ├── dev-ingest │ ├── dev-workers │ ├── fmt │ ├── lint │ ├── test │ ├── typecheck │ └── upload-code-cov ├── supervisord.conf ├── tox.ini └── typings │ ├── jwt.pyi │ ├── markdown_html_finder.pyi │ ├── markupsafe.pyi │ ├── rure │ ├── __init__.pyi │ └── exceptions.pyi │ ├── structlog │ ├── __init__.pyi │ ├── _base.pyi │ ├── _config.pyi │ ├── _generic.pyi │ ├── processors.pyi │ └── stdlib.pyi │ ├── uvicorn │ ├── __init__.pyi │ └── main.pyi │ └── zstandard.pyi ├── codecov.yml ├── docs ├── .prettierrc.js ├── README.md ├── core │ └── Footer.js ├── docs │ ├── billing.md │ ├── config-reference.md │ ├── contributing.md │ ├── dashboard.md │ ├── features.md │ ├── permissions.md │ ├── prior-art-and-alternatives.md │ ├── quickstart.md │ ├── recipes.md │ ├── self-hosting.md │ ├── sponsoring.md │ ├── troubleshooting.md │ └── why-and-how.md ├── netlify.toml ├── package.json ├── pages │ └── en │ │ ├── help.js │ │ └── index.js ├── s │ ├── build │ ├── dev │ ├── fmt │ ├── fmt-ci │ └── typecheck ├── sidebars.json ├── siteConfig.js ├── static │ ├── css │ │ └── custom.css │ └── img │ │ ├── billing-modify-card-step-1.png │ │ ├── billing-modify-card-step-2.png │ │ ├── billing-modify-card-step-3.png │ │ ├── branch-protection-require-branches-up-to-date.png │ │ ├── branch-protection-require-signed-commits.png │ │ ├── coauthors-example.png │ │ ├── dashboard │ │ ├── billing-trial-signup.png │ │ ├── kodiak-activity.png │ │ ├── merge-queue.png │ │ ├── overview.png │ │ ├── pull-request-activity.png │ │ ├── trial-signup.png │ │ └── usage.png │ │ ├── favicon.ico │ │ ├── kodiak-pr-flow.svg │ │ ├── logo_complexgmbh.png │ │ ├── restrict-who-can-push-to-matching-branches.png │ │ ├── undraw_code_review.svg │ │ ├── undraw_uploading.svg │ │ └── wordmark.png ├── tsconfig.json └── yarn.lock ├── infrastructure ├── README.md ├── kodiak-daily-restart.service ├── kodiak-daily-restart.timer ├── kodiak_restart.sh ├── playbooks │ └── dashboard-deploy.yml └── systemd │ ├── kodiak-aggregate_pull_request_activity.service.j2 │ ├── kodiak-aggregate_pull_request_activity.timer │ ├── kodiak-aggregate_user_pull_request_activity.service.j2 │ └── kodiak-aggregate_user_pull_request_activity.timer ├── kodiak.code-workspace ├── s └── shellcheck ├── web_api ├── .coveragerc ├── .dockerignore ├── .pylintrc ├── .python-version ├── .vscode │ └── settings.json ├── Dockerfile ├── README.md ├── example.env ├── manage.py ├── poetry.lock ├── pyproject.toml ├── s │ ├── dev │ ├── fmt │ ├── lint │ ├── squawk.py │ ├── test │ └── upload-code-cov ├── tox.ini ├── typings │ └── zstandard.pyi └── web_api │ ├── __init__.py │ ├── asgi.py │ ├── auth.py │ ├── conftest.py │ ├── event_ingestion.py │ ├── exceptions.py │ ├── http.py │ ├── management │ └── commands │ │ ├── aggregate_pull_request_activity.py │ │ ├── aggregate_user_pull_request_activity.py │ │ └── ingest_events.py │ ├── merge_queues.py │ ├── middleware.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_githubevent.py │ ├── 0003_auto_20200215_1733.py │ ├── 0004_auto_20200215_2015.py │ ├── 0005_auto_20200215_2156.py │ ├── 0006_remove_account_payload.py │ ├── 0007_auto_20200217_0345.py │ ├── 0008_payload_installation_id_idx.py │ ├── 0009_pullrequestactivityprogress.py │ ├── 0010_auto_20200220_0116.py │ ├── 0011_accountmembership_role.py │ ├── 0012_auto_20200308_2254.py │ ├── 0013_auto_20200310_0412.py │ ├── 0014_auto_20200323_0159.py │ ├── 0015_remove_stripecustomerinformation_customer_delinquent.py │ ├── 0016_auto_20200405_1511.py │ ├── 0017_account_trial_email.py │ ├── 0018_auto_20200502_1849.py │ ├── 0019_auto_20200610_0006.py │ ├── 0020_auto_20200613_2012.py │ ├── 0021_auto_20200617_1246.py │ ├── 0022_remove_account_stripe_plan_id.py │ ├── 0023_account_limit_billing_access_to_owners.py │ ├── 0024_auto_20200726_0316.py │ ├── 0025_auto_20200902_0052.py │ ├── 0026_auto_20220322_0036.py │ └── __init__.py │ ├── models.py │ ├── patches.py │ ├── settings.py │ ├── test_account.py │ ├── test_analytics_aggregator.py │ ├── test_merge_queues.py │ ├── test_middleware.py │ ├── test_pull_request_activity.py │ ├── test_stripe_customer_info.py │ ├── test_stripe_views.py │ ├── test_user.py │ ├── test_user_pull_request_activity.py │ ├── test_views.py │ ├── tests │ └── fixtures │ │ ├── pull_request_kodiak_merged.json │ │ ├── pull_request_kodiak_updated.json │ │ ├── pull_request_kodiak_updated_different_institution.json │ │ ├── pull_request_review_kodiak_approved.json │ │ ├── pull_request_total_closed.json │ │ ├── pull_request_total_merged.json │ │ ├── pull_request_total_opened.json │ │ └── pull_request_total_opened_different_institution.json │ ├── testutils.py │ ├── urls.py │ ├── user_activity_aggregator.py │ ├── utils.py │ ├── views.py │ └── wsgi.py └── web_ui ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .vscode └── settings.json ├── Dockerfile ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── modules.js ├── paths.js ├── pnpTs.js ├── webpack.config.js └── webpackDevServer.config.js ├── general_headers.conf ├── netlify.toml ├── nginx.conf ├── package.json ├── public ├── favicon.ico └── index.html ├── s ├── dev ├── fmt ├── lint ├── test └── typecheck ├── scripts ├── build.js ├── start.js └── test.js ├── src ├── api.ts ├── auth.ts ├── components │ ├── AccountsPage.tsx │ ├── ActivityChart.tsx │ ├── ActivityPage.tsx │ ├── App.tsx │ ├── DebugSentryPage.tsx │ ├── ErrorBoundary.tsx │ ├── Image.tsx │ ├── LoginPage.test.tsx │ ├── LoginPage.tsx │ ├── NotFoundPage.tsx │ ├── OAuthPage.tsx │ ├── Page.tsx │ ├── SideBarNav.tsx │ ├── Spinner.tsx │ ├── SubscriptionAlert.tsx │ ├── ToolTip.tsx │ ├── UsageBillingPage.tsx │ └── __snapshots__ │ │ └── LoginPage.test.tsx.snap ├── custom.scss ├── index.tsx ├── settings.ts ├── setupTests.ts ├── useApi.ts ├── webdata.ts └── world.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: chdsbd # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help request 3 | about: Ask for help configuring and using Kodiak 4 | title: '' 5 | labels: help-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/bot.yml: -------------------------------------------------------------------------------- 1 | name: bot 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | pre_job: 12 | runs-on: ubuntu-latest 13 | outputs: 14 | should_skip: ${{ steps.skip_check.outputs.should_skip }} 15 | paths_result: ${{ steps.skip_check.outputs.paths_result }} 16 | steps: 17 | - id: skip_check 18 | uses: fkirc/skip-duplicate-actions@c449d86cf33a2a6c7a4193264cc2578e2c3266d4 # pin@v4 19 | with: 20 | paths: '["bot/**", ".github/workflows/**"]' 21 | 22 | test: 23 | needs: pre_job 24 | if: needs.pre_job.outputs.should_skip != 'true' 25 | runs-on: ubuntu-latest 26 | services: 27 | redis: 28 | image: redis:5 29 | # Set health checks to wait until redis has started 30 | options: >- 31 | --health-cmd "redis-cli ping" 32 | --health-interval 10s 33 | --health-timeout 5s 34 | --health-retries 5 35 | ports: 36 | # Maps port 6379 on service container to the host 37 | - 6379:6379 38 | steps: 39 | - uses: actions/checkout@v3 40 | - name: Install poetry 41 | run: | 42 | pipx install poetry==1.1.13 43 | poetry config virtualenvs.in-project true 44 | - uses: actions/setup-python@v4 45 | with: 46 | python-version-file: "./bot/.python-version" 47 | cache: poetry 48 | cache-dependency-path: "./bot/poetry.lock" 49 | - name: Install dependencies 50 | working-directory: "./bot" 51 | run: poetry install 52 | - name: Run tests 53 | working-directory: "bot" 54 | run: ./s/test 55 | - name: upload code coverage 56 | working-directory: bot 57 | run: ./s/upload-code-cov 58 | lint: 59 | needs: pre_job 60 | if: needs.pre_job.outputs.should_skip != 'true' 61 | runs-on: ubuntu-latest 62 | steps: 63 | - uses: actions/checkout@v3 64 | - name: Install poetry 65 | run: | 66 | pipx install poetry==1.1.13 67 | poetry config virtualenvs.in-project true 68 | - uses: actions/setup-python@v4 69 | with: 70 | python-version-file: "./bot/.python-version" 71 | cache: poetry 72 | cache-dependency-path: "./bot/poetry.lock" 73 | - name: Install dependencies 74 | working-directory: "./bot" 75 | run: poetry install 76 | - name: Run lints 77 | working-directory: "bot" 78 | run: ./s/lint 79 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | pre_job: 12 | runs-on: ubuntu-latest 13 | outputs: 14 | should_skip: ${{ steps.skip_check.outputs.should_skip }} 15 | paths_result: ${{ steps.skip_check.outputs.paths_result }} 16 | steps: 17 | - id: skip_check 18 | uses: fkirc/skip-duplicate-actions@c449d86cf33a2a6c7a4193264cc2578e2c3266d4 # pin@v4 19 | with: 20 | paths: '["docs/**", ".github/workflows/**"]' 21 | typecheck: 22 | needs: pre_job 23 | if: needs.pre_job.outputs.should_skip != 'true' 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: actions/setup-node@v3 28 | with: 29 | node-version-file: "docs/package.json" 30 | cache-dependency-path: "docs/yarn.lock" 31 | cache: "yarn" 32 | - name: Install dependencies 33 | working-directory: "docs" 34 | run: yarn install --frozen-lockfile 35 | - name: run typechecker 36 | working-directory: "docs" 37 | run: ./s/typecheck 38 | 39 | fmt: 40 | needs: pre_job 41 | if: needs.pre_job.outputs.should_skip != 'true' 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v3 45 | - uses: actions/setup-node@v3 46 | with: 47 | node-version-file: "docs/package.json" 48 | cache-dependency-path: "docs/yarn.lock" 49 | cache: "yarn" 50 | - name: Install dependencies 51 | working-directory: "docs" 52 | run: yarn install --frozen-lockfile 53 | - name: Run tests 54 | working-directory: "docs" 55 | run: ./s/fmt-ci 56 | 57 | verify_build: 58 | needs: pre_job 59 | if: needs.pre_job.outputs.should_skip != 'true' 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v3 63 | - uses: actions/setup-node@v3 64 | with: 65 | node-version-file: "docs/package.json" 66 | cache-dependency-path: "docs/yarn.lock" 67 | cache: "yarn" 68 | - name: Install dependencies 69 | working-directory: "docs" 70 | run: yarn install --frozen-lockfile 71 | - name: Run tests 72 | working-directory: "docs" 73 | run: ./s/build 74 | -------------------------------------------------------------------------------- /.github/workflows/web_ui.yml: -------------------------------------------------------------------------------- 1 | name: web_ui 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | pre_job: 12 | runs-on: ubuntu-latest 13 | outputs: 14 | should_skip: ${{ steps.skip_check.outputs.should_skip }} 15 | paths_result: ${{ steps.skip_check.outputs.paths_result }} 16 | steps: 17 | - id: skip_check 18 | uses: fkirc/skip-duplicate-actions@c449d86cf33a2a6c7a4193264cc2578e2c3266d4 # pin@v4 19 | with: 20 | paths: '["web_ui/**", ".github/workflows/**"]' 21 | test: 22 | needs: pre_job 23 | if: needs.pre_job.outputs.should_skip != 'true' 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: actions/setup-node@v3 28 | with: 29 | node-version-file: "web_ui/package.json" 30 | cache-dependency-path: "web_ui/yarn.lock" 31 | cache: "yarn" 32 | - name: Install dependencies 33 | working-directory: "web_ui" 34 | run: yarn install --frozen-lockfile 35 | - name: Run tests 36 | working-directory: "web_ui" 37 | run: ./s/test 38 | 39 | lint: 40 | needs: pre_job 41 | if: needs.pre_job.outputs.should_skip != 'true' 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v3 45 | - uses: actions/setup-node@v3 46 | with: 47 | node-version-file: "web_ui/package.json" 48 | cache-dependency-path: "web_ui/yarn.lock" 49 | cache: "yarn" 50 | - name: Install dependencies 51 | working-directory: "web_ui" 52 | run: yarn install --frozen-lockfile 53 | - name: Run tests 54 | working-directory: "web_ui" 55 | run: ./s/lint 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | # .python-version 84 | 85 | # pipenv 86 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 87 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 88 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 89 | # install all needed dependencies. 90 | #Pipfile.lock 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | 125 | *.pem 126 | i18n 127 | -------------------------------------------------------------------------------- /.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [merge.message] 4 | title = "pull_request_title" 5 | body = "pull_request_body" 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
40 | {config_escaped} 41 |42 | 43 | ## configuration error message 44 |
45 | {error_escaped} 46 |47 | 48 | ## notes 49 | - Check the Kodiak docs for setup information at https://kodiakhq.com/docs/quickstart. 50 | - A configuration reference is available at https://kodiakhq.com/docs/config-reference. 51 | - Full examples are available at https://kodiakhq.com/docs/recipes 52 | """ 53 | ) 54 | 55 | 56 | def get_markdown_for_paywall() -> str: 57 | return format( 58 | """\ 59 | You can start a 30 day trial or update your subscription on the Kodiak dashboard at https://app.kodiakhq.com. 60 | 61 | Kodiak is free to use on public repositories, but requires a subscription to use with private repositories. 62 | 63 | See the [Kodiak docs](https://kodiakhq.com/docs/billing) for more information about free trials and subscriptions. 64 | """ 65 | ) 66 | 67 | 68 | def get_markdown_for_push_allowance_error(*, branch_name: str) -> str: 69 | return format( 70 | f"""\ 71 | Your branch protection setting for `{branch_name}` has "Restrict who can push to matching branches" enabled. You must allow Kodiak to push to this branch for Kodiak to merge pull requests. 72 | 73 | See the Kodiak troubleshooting docs for more information: https://kodiakhq.com/docs/troubleshooting#restricting-pushes 74 | """ 75 | ) 76 | 77 | 78 | class APICallRetry(Protocol): 79 | @property 80 | def api_name(self) -> str: 81 | ... 82 | 83 | @property 84 | def http_status(self) -> str: 85 | ... 86 | 87 | @property 88 | def response_body(self) -> str: 89 | ... 90 | 91 | 92 | def get_markdown_for_api_call_errors(*, errors: Sequence[APICallRetry]) -> str: 93 | formatted_errors = "\n".join( 94 | f"- API call {error.api_name!r} failed with HTTP status {error.http_status!r} and response: {error.response_body!r}" 95 | for error in errors 96 | ) 97 | return format( 98 | f"""\ 99 | Errors encountered when contacting GitHub API. 100 | 101 | {formatted_errors} 102 | """ 103 | ) 104 | -------------------------------------------------------------------------------- /bot/kodiak/queries/commits.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List, Optional 2 | 3 | import pydantic 4 | import structlog 5 | 6 | logger = structlog.get_logger() 7 | 8 | 9 | class CommitConnection(pydantic.BaseModel): 10 | totalCount: int 11 | 12 | 13 | class User(pydantic.BaseModel): 14 | databaseId: Optional[int] 15 | login: str 16 | name: Optional[str] 17 | type: str 18 | 19 | def __hash__(self) -> int: 20 | # defining a hash method allows us to deduplicate CommitAuthors easily. 21 | return hash(self.databaseId) + hash(self.login) + hash(self.name) 22 | 23 | 24 | class GitActor(pydantic.BaseModel): 25 | user: Optional[User] 26 | 27 | 28 | class Commit(pydantic.BaseModel): 29 | parents: CommitConnection 30 | author: Optional[GitActor] 31 | 32 | 33 | class PullRequestCommit(pydantic.BaseModel): 34 | commit: Commit 35 | 36 | 37 | class PullRequestCommitConnection(pydantic.BaseModel): 38 | nodes: Optional[List[PullRequestCommit]] 39 | 40 | 41 | class PullRequest(pydantic.BaseModel): 42 | commitHistory: PullRequestCommitConnection 43 | 44 | 45 | def get_commits(*, pr: Dict[str, Any]) -> List[Commit]: 46 | """ 47 | Extract the commit authors from the pull request commits. 48 | """ 49 | # we use a dict as an ordered set. 50 | try: 51 | pull_request = PullRequest.parse_obj(pr) 52 | except pydantic.ValidationError: 53 | logger.exception("problem parsing commit authors") 54 | return [] 55 | nodes = pull_request.commitHistory.nodes 56 | if not nodes: 57 | return [] 58 | return [node.commit for node in nodes] 59 | -------------------------------------------------------------------------------- /bot/kodiak/redis_client.py: -------------------------------------------------------------------------------- 1 | import redis.asyncio as redis 2 | 3 | import kodiak.app_config as conf 4 | 5 | 6 | def create_connection() -> "redis.Redis[bytes]": 7 | redis_db = 0 8 | try: 9 | redis_db = int(conf.REDIS_URL.database) 10 | except ValueError: 11 | pass 12 | 13 | return redis.Redis( 14 | host=conf.REDIS_URL.hostname or "localhost", 15 | port=conf.REDIS_URL.port or 6379, 16 | username=conf.REDIS_URL.username, 17 | password=conf.REDIS_URL.password, 18 | ssl=conf.REDIS_URL.scheme == "rediss", 19 | db=redis_db, 20 | socket_keepalive=True, 21 | socket_timeout=conf.REDIS_SOCKET_TIMEOUT_SEC, 22 | socket_connect_timeout=conf.REDIS_SOCKET_CONNECT_TIMEOUT_SEC, 23 | health_check_interval=True, 24 | ) 25 | 26 | 27 | redis_bot = create_connection() 28 | redis_web_api = create_connection() 29 | -------------------------------------------------------------------------------- /bot/kodiak/schemas.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Dict 4 | 5 | import pydantic 6 | 7 | 8 | class RawWebhookEvent(pydantic.BaseModel): 9 | event_name: str 10 | payload: Dict[str, Any] 11 | -------------------------------------------------------------------------------- /bot/kodiak/test/fixtures/config/v1-default.toml: -------------------------------------------------------------------------------- 1 | # configuration with all defaults set 2 | version = 1 # required 3 | 4 | [merge] 5 | automerge_label = "automerge" 6 | require_automerge_label = true 7 | # this setting is for snapshot testing purposes. This gets replaced with the 8 | # `WIP:.*` default regex in evaluation.py 9 | blacklist_title_regex = ":::|||kodiak|||internal|||reserved|||:::" 10 | blacklist_labels = [] 11 | delete_branch_on_merge = false 12 | block_on_reviews_requested = false 13 | notify_on_conflict = true 14 | optimistic_updates = true 15 | dont_wait_on_status_checks = [] 16 | update_branch_immediately = false 17 | prioritize_ready_to_merge = false 18 | do_not_merge = false 19 | 20 | [merge.message] 21 | title = "github_default" 22 | body = "github_default" 23 | include_pr_number = true 24 | body_type = "markdown" 25 | strip_html_comments = false 26 | 27 | [update] 28 | always = false 29 | require_automerge_label = true 30 | 31 | [approve] 32 | auto_approve_usernames = [] 33 | auto_approve_labels = [] 34 | -------------------------------------------------------------------------------- /bot/kodiak/test/fixtures/config/v1-opposite.1.toml: -------------------------------------------------------------------------------- 1 | # configuration with all settings changed from defaults 2 | version = 1 # required 3 | app_id = 12345 # default: None 4 | 5 | [merge] 6 | automerge_label = "mergeit!" # default: "automerge" 7 | require_automerge_label = false # default: true 8 | blacklist_title_regex = "" # default: "^WIP:.*" 9 | blacklist_labels = ["wip", "block-merge"] # default: [] 10 | method = "squash" # default: first valid merge method in list `"merge"`, `"squash"`, `"rebase"` 11 | delete_branch_on_merge = true # default: false 12 | block_on_reviews_requested = true # default: false 13 | notify_on_conflict = false # default: true 14 | optimistic_updates = false # default: true 15 | dont_wait_on_status_checks = ["ci/circleci: deploy"] # default: [] 16 | update_branch_immediately = true 17 | prioritize_ready_to_merge = true 18 | do_not_merge = true 19 | 20 | [merge.message] 21 | title = "pull_request_title" # default: "github_default" 22 | body = "pull_request_body" # default: "github_default" 23 | include_pr_number = false # default: true 24 | body_type = "plain_text" # default: "markdown" 25 | strip_html_comments = true # default: false 26 | 27 | [update] 28 | always = true 29 | require_automerge_label = false 30 | 31 | [approve] 32 | auto_approve_usernames = ["dependabot"] # default: [] 33 | auto_approve_labels = ["autoapprove"] # default: [] 34 | -------------------------------------------------------------------------------- /bot/kodiak/test/fixtures/config/v1-opposite.2.toml: -------------------------------------------------------------------------------- 1 | # configuration with all settings changed from defaults 2 | version = 1 # required 3 | app_id = 12345 # default: None 4 | 5 | [merge] 6 | automerge_label = "mergeit!" # default: "automerge" 7 | require_automerge_label = false # default: true 8 | blacklist_title_regex = "" # default: "^WIP:.*" 9 | blacklist_labels = ["wip", "block-merge"] # default: [] 10 | method = "squash" # default: first valid merge method in list `"merge"`, `"squash"`, `"rebase"` 11 | delete_branch_on_merge = true # default: false 12 | block_on_reviews_requested = true # default: false 13 | notify_on_conflict = false # default: true 14 | optimistic_updates = false # default: true 15 | dont_wait_on_status_checks = ["ci/circleci: deploy"] # default: [] 16 | update_branch_immediately = true 17 | prioritize_ready_to_merge = true 18 | do_not_merge = true 19 | 20 | [merge.message] 21 | title = "pull_request_title" # default: "github_default" 22 | body = "empty" # default: "github_default" 23 | include_pr_number = false # default: true 24 | body_type = "plain_text" # default: "markdown" 25 | strip_html_comments = true # default: false 26 | 27 | [update] 28 | always = true 29 | require_automerge_label = false 30 | 31 | [approve] 32 | auto_approve_usernames = ["dependabot"] # default: [] 33 | auto_approve_labels = ["autoapprove"] # default: [] 34 | -------------------------------------------------------------------------------- /bot/kodiak/test/fixtures/config_utils/pydantic-error.md: -------------------------------------------------------------------------------- 1 | You have an invalid Kodiak configuration file. 2 | 3 | ## configuration file 4 | > config_file_expression: master:.kodiak.toml 5 | > line count: 1 6 | 7 |
8 | version = 12 9 |10 | 11 | ## configuration error message 12 |
13 | # pretty 14 | 1 validation error for V1 15 | version 16 | Version must be `1` (type=value_error.invalidversion) 17 | 18 | 19 | # json 20 | [ 21 | { 22 | "loc": [ 23 | "version" 24 | ], 25 | "msg": "Version must be `1`", 26 | "type": "value_error.invalidversion" 27 | } 28 | ] 29 |30 | 31 | ## notes 32 | - Check the Kodiak docs for setup information at https://kodiakhq.com/docs/quickstart. 33 | - A configuration reference is available at https://kodiakhq.com/docs/config-reference. 34 | - Full examples are available at https://kodiakhq.com/docs/recipes 35 | 36 | 37 | If you need help, you can open a GitHub issue, check the docs, or reach us privately at support@kodiakhq.com. 38 | 39 | [docs](https://kodiakhq.com/docs/troubleshooting) | [dashboard](https://app.kodiakhq.com) | [support](https://kodiakhq.com/help) 40 | 41 | -------------------------------------------------------------------------------- /bot/kodiak/test/fixtures/config_utils/toml-error.md: -------------------------------------------------------------------------------- 1 | You have an invalid Kodiak configuration file. 2 | 3 | ## configuration file 4 | > config_file_expression: master:.kodiak.toml 5 | > line count: 1 6 | 7 |
8 | [[[ version = 12 9 |10 | 11 | ## configuration error message 12 |
13 | TomlDecodeError('Key group not on a line by itself. (line 1 column 1 char 0)') 14 |15 | 16 | ## notes 17 | - Check the Kodiak docs for setup information at https://kodiakhq.com/docs/quickstart. 18 | - A configuration reference is available at https://kodiakhq.com/docs/config-reference. 19 | - Full examples are available at https://kodiakhq.com/docs/recipes 20 | 21 | 22 | If you need help, you can open a GitHub issue, check the docs, or reach us privately at support@kodiakhq.com. 23 | 24 | [docs](https://kodiakhq.com/docs/troubleshooting) | [dashboard](https://app.kodiakhq.com) | [support](https://kodiakhq.com/help) 25 | 26 | -------------------------------------------------------------------------------- /bot/kodiak/test_config_utils.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from kodiak.config import V1 4 | from kodiak.messages import get_markdown_for_config 5 | 6 | 7 | def load_config_fixture(fixture_name: str) -> Path: 8 | return Path(__file__).parent / "test" / "fixtures" / "config_utils" / fixture_name 9 | 10 | 11 | def test_get_markdown_for_config_pydantic_error() -> None: 12 | config = "version = 12" 13 | error = V1.parse_toml(config) 14 | assert not isinstance(error, V1) 15 | markdown = get_markdown_for_config( 16 | error, config_str=config, git_path="master:.kodiak.toml" 17 | ) 18 | assert markdown == load_config_fixture("pydantic-error.md").read_text() 19 | 20 | 21 | def test_get_markdown_for_config_toml_error() -> None: 22 | config = "[[[ version = 12" 23 | error = V1.parse_toml(config) 24 | assert not isinstance(error, V1) 25 | markdown = get_markdown_for_config( 26 | error, config_str=config, git_path="master:.kodiak.toml" 27 | ) 28 | assert markdown == load_config_fixture("toml-error.md").read_text() 29 | -------------------------------------------------------------------------------- /bot/kodiak/test_event_handlers.py: -------------------------------------------------------------------------------- 1 | from kodiak.queue import get_branch_name 2 | 3 | 4 | def test_get_branch_name() -> None: 5 | assert get_branch_name("refs/heads/master") == "master" 6 | assert ( 7 | get_branch_name("refs/heads/master/refs/heads/123") == "master/refs/heads/123" 8 | ) 9 | assert get_branch_name("refs/tags/v0.1.0") is None 10 | -------------------------------------------------------------------------------- /bot/kodiak/test_events.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | from pydantic import BaseModel 5 | 6 | from kodiak import events 7 | 8 | # A mapping of all events to their corresponding fixtures. Any new event must 9 | # register themselves here for testing. 10 | MAPPING = ( 11 | ("check_run", events.CheckRunEvent), 12 | ("pull_request", events.PullRequestEvent), 13 | ("pull_request_review", events.PullRequestReviewEvent), 14 | ("pull_request_review_thread", events.PullRequestReviewThreadEvent), 15 | ("status", events.StatusEvent), 16 | ("push", events.PushEvent), 17 | ) 18 | 19 | 20 | @pytest.mark.parametrize("event_name, schema", MAPPING) 21 | def test_event_parsing(event_name: str, schema: BaseModel) -> None: 22 | for fixture_path in ( 23 | Path(__file__).parent / "test" / "fixtures" / "events" / event_name 24 | ).rglob("*.json"): 25 | schema.parse_file(fixture_path) 26 | -------------------------------------------------------------------------------- /bot/kodiak/test_queue.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from kodiak.queue import installation_id_from_queue 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "queue_name, expected_installation_id", 8 | ( 9 | ("merge_queue:11256551.sbdchd/squawk/main.test.foo", "11256551"), 10 | ("merge_queue:11256551.sbdchd/squawk", "11256551"), 11 | ("merge_queue:11256551.sbdchd/squawk:repo/main:test.branch", "11256551"), 12 | ("webhook:11256551", "11256551"), 13 | ("", ""), 14 | ), 15 | ) 16 | def test_installation_id_from_queue( 17 | queue_name: str, expected_installation_id: str 18 | ) -> None: 19 | """ 20 | We should gracefully parse an installation id from the queue name 21 | """ 22 | assert installation_id_from_queue(queue_name) == expected_installation_id 23 | -------------------------------------------------------------------------------- /bot/kodiak/test_text.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from kodiak.text import strip_html_comments_from_markdown 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "original,stripped", 8 | [ 9 | ( 10 | """\ 11 | Non dolor velit vel quia mollitia. Placeat cumque a deleniti possimus. 12 | 13 | Totam dolor [exercitationem laborum](https://numquam.com) 14 | 15 | 22 | """, 23 | """\ 24 | Non dolor velit vel quia mollitia. Placeat cumque a deleniti possimus. 25 | 26 | Totam dolor [exercitationem laborum](https://numquam.com) 27 | 28 | 29 | """, 30 | ), 31 | ( 32 | 'Non dolor velit vel quia mollitia.\r\n\r\nVoluptates nulla tempora.\r\n\r\n', 33 | "Non dolor velit vel quia mollitia.\n\nVoluptates nulla tempora.\n\n", 34 | ), 35 | ("hello world", "hello world"), 36 | ( 37 | "hello
hello
world", 38 | "hellohello
world", 39 | ), 40 | ( 41 | "hellohello
world", 42 | "hellohello
world", 43 | ), 44 | ( 45 | """\ 46 | this is an example comment message with a comment from a PR template 47 | 48 | 55 | """, 56 | """\ 57 | this is an example comment message with a comment from a PR template 58 | 59 | 60 | """, 61 | ), 62 | ("🏷️", "🏷️"), 63 | ], 64 | ) 65 | def test_strip_html_comments_from_markdown(original: str, stripped: str) -> None: 66 | assert strip_html_comments_from_markdown(original) == stripped 67 | -------------------------------------------------------------------------------- /bot/kodiak/test_utils.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | from typing import TypeVar 4 | 5 | T = TypeVar("T") 6 | 7 | # some change happened in Python 3.8 to eliminate the need for wrapping mock 8 | # results. 9 | if sys.version_info < (3, 8): 10 | 11 | def wrap_future(x: T) -> "asyncio.Future[T]": 12 | fut: "asyncio.Future[T]" = asyncio.Future() 13 | fut.set_result(x) 14 | return fut 15 | 16 | 17 | else: 18 | 19 | def wrap_future(x: T) -> "T": 20 | return x 21 | -------------------------------------------------------------------------------- /bot/kodiak/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chdsbd/kodiak/0065e640d6febdb08ea236f459c125990fd9d9cf/bot/kodiak/tests/__init__.py -------------------------------------------------------------------------------- /bot/kodiak/tests/dependencies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chdsbd/kodiak/0065e640d6febdb08ea236f459c125990fd9d9cf/bot/kodiak/tests/dependencies/__init__.py -------------------------------------------------------------------------------- /bot/kodiak/tests/dependencies/pull_requests/update-major-github_action.txt: -------------------------------------------------------------------------------- 1 | chore(deps): update codecov/codecov-action action to v2 2 | [](https://renovatebot.com) 3 | 4 | This PR contains the following updates: 5 | 6 | | Package | Type | Update | Change | 7 | |---|---|---|---| 8 | | [codecov/codecov-action](https://togithub.com/codecov/codecov-action) | action | major | `v1` -> `v2` | 9 | 10 | --- 11 | 12 | ### Release Notes 13 | 14 |Logging in...
46 | ) : ( 47 |
48 |
49 | Login failure
50 |
51 | {" "}
52 | {error}
53 |
57 | {error && ( 58 | 62 | )} 63 | Return to Login 64 |
65 |38 | 41 | Install 42 | 43 | | 44 | 47 | Docs 48 | 49 | | 50 | 51 | 54 | Help 55 | 56 |
57 |