{"id":31796969,"url":"https://github.com/yang/pyspr","last_synced_at":"2025-10-10T20:54:14.835Z","repository":{"id":276595806,"uuid":"928126233","full_name":"yang/pyspr","owner":"yang","description":null,"archived":false,"fork":false,"pushed_at":"2025-09-24T04:10:16.000Z","size":575,"stargazers_count":1,"open_issues_count":20,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-24T05:34:49.658Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-06T05:21:21.000Z","updated_at":"2025-09-24T04:10:18.000Z","dependencies_parsed_at":"2025-02-09T10:19:50.864Z","dependency_job_id":"f6105339-55a7-4db7-b81c-7d949c61dcf2","html_url":"https://github.com/yang/pyspr","commit_stats":null,"previous_names":["yang/pyspr"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/yang/pyspr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang%2Fpyspr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang%2Fpyspr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang%2Fpyspr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang%2Fpyspr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yang","download_url":"https://codeload.github.com/yang/pyspr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang%2Fpyspr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279005221,"owners_count":26083864,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-10T20:53:33.440Z","updated_at":"2025-10-10T20:54:14.826Z","avatar_url":"https://github.com/yang.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- @format --\u003e\n\n# pyspr: stacked pull requests for github\n\nI found spr to be buggy and crashy, and I wanted something I could keep hacking in Python.\n\nWritten mostly by Claude!\n\n```\nalias pr=.../bin/pyspr\n\npr up\npr st\n```\n\nMinimal Python port of [spr](https://github.com/ejoffe/spr).\nSee its docs on the usage model.\n\n## How to install\n\n1. Install [rye](https://rye.astral.sh/). See that website for most up-to-date install instructions.\n\n```\nwhich rye || curl -sSf https://rye.astral.sh/get | bash\n```\n\n2. Clone and setup this repo.\n\n```\ngit clone https://github.com/yang/pyspr.git\ncd pyspr\nrye sync\nrye run pyspr --help  # try running it\n```\n\n3. Create this alias for your shell, so that you can run from anywhere - remember to add in you bashrc / zshrc.\n\n```\nalias pr=/PATH/TO/THE/REPO/.venv/bin/pyspr\n```\n\n4. Make sure you are able to use the `gh` command already, since the auth info is read from its auth file.\n\n5. Create a `.spr.yaml` in the repo you care about. Here's an example that for a repo that uses merge queue and `master` default branch, and where you want to auto-label all PRs with `run-monorepo-tests-on-push`:\n\n```\n---\nrepo:\n    github_repo_owner: anthropics\n    github_repo_name: anthropic\n    github_host: github.com\n    github_remote: origin\n    github_branch: master\n    require_checks: true\n    require_approval: true\n    merge_queue: true\n    branch_push_individually: true\n    show_pr_titles_in_stack: false\n    merge_method: rebase\n    force_fetch_tags: false\n    labels: [ 'run-monorepo-tests-on-push' ]\ngithub_repo_owner: anthropics\ngithub_repo_name: anthropic\ngithub_host: github.com\ngithub_remote: origin\ngithub_branch: master\nrequire_checks: true\nrequire_approval: true\nmerge_queue: true\nbranch_push_individually: true\nshow_pr_titles_in_stack: false\nmerge_method: rebase\nforce_fetch_tags: false\n```\n\n## How to use\n\nSee the spr docs for better docs, but a tldr:\n\n- PRs and commits are 1:1.\n- Amend commits, rebase, etc. rather than just creating new ones or merging.\n- Use `pr up` to update the PR stack to reflect your local commit stack.\n- Use `pr merge` to merge a bunch of PRs (chosen based on your current local commit stack).\n- Use `pr analyze` to analyze which commits can be submitted independently.\n\n### The `analyze` command\n\nThe `pr analyze` command helps you understand dependencies between commits in your stack. It identifies which commits can be submitted as independent PRs versus which ones depend on earlier commits.\n\n**How dependencies are determined:**\n- Dependencies are based on actual git conflicts when cherry-picking\n- The analyze command attempts to cherry-pick each commit onto the base branch\n- If a commit cannot be cherry-picked cleanly, it checks which earlier commits are needed\n- A commit is marked as dependent only if cherry-picking specific earlier commits allows it to succeed\n- The goal is to maximize the number of independent PRs that can be submitted in parallel\n\n**Output includes:**\n1. **Independent commits**: Can be submitted directly to the base branch without conflicts\n2. **Dependent commits**: Require earlier commits to be merged first\n3. **Alternative stacking scenarios**:\n   - **Strongly Connected Components**: Groups commits with mutual dependencies\n   - **Single-Parent Trees**: Attempts to create a forest where each commit has at most one parent\n\n**Example usage:**\n```bash\n$ pr analyze\n🎯 Commit Stack Analysis\n\nAnalyzing 17 commits for independent submission...\n\n✅ Independent commits (12):\n   These can be submitted directly to the base branch without conflicts:\n   - f8ceef6d Add retries with forced retries for debugging\n   - 06cb532f Improve logging\n   ...\n\n❌ Dependent commits (5):\n   These require earlier commits or have conflicts:\n   - b634b789 Remove long lived connection\n     Reason: Depends on: f8ceef6d\n   ...\n\nTip: You can use 'pyspr breakup' to create independent PRs for the 12 independent commits.\n```\n\nUse this command when you want to:\n- Understand which commits can be parallelized as separate PRs\n- Find the optimal way to break up a large stack\n- Identify file conflicts between commits\n\n## Tips\n\nUse `-r` to tag reviewers.\n\n## Running tests\n\nTo run tests, use the provided script:\n\n```bash\n# Run all tests with auto-detected parallelization\n./run_tests.sh\n\n# Run with verbose output\n./run_tests.sh -vsx\n\n# Run specific tests\n./run_tests.sh -k \"test_analyze\"\n\n# Run with specific number of workers\n./run_tests.sh -n 4\n```\n\nThe `run_tests.sh` script uses pytest-xdist for parallel test execution:\n\n```bash\n# What run_tests.sh does:\nrye run pytest -p xdist -n auto \"$@\"\n```\n\nAdditional useful options:\n- `--dist loadscope`: Groups tests by module/class for better test isolation\n- `--dist worksteal`: Dynamic scheduling (better for uneven test durations)\n- `-x/--maxfail=1`: Stop after first failure\n- `--tb=short`: Shorter tracebacks\n\n## Gotchas\n\n(The fix for this is not implemented so for now please avoid reordering....)\n\nWhen reordering PRs/commits, we had a bug.\n\nSay you have 4 PRs:\n\n\u003cimg width=\"1244\" alt=\"Screenshot 2025-03-19 at 11 36 09 PM\" src=\"https://github.com/user-attachments/assets/7c74c460-c5cf-4691-826a-a052bbdfd8b9\" /\u003e\n\nNow you reorder C2 and C3, force-pushing them.\nWe push the commits in order, so first C3'.\n\n\u003cimg width=\"1291\" alt=\"Screenshot 2025-03-19 at 11 36 16 PM\" src=\"https://github.com/user-attachments/assets/c77f8c91-cb32-41af-82af-7641aca5706c\" /\u003e\n\nNow we push C2' next.\n\n\u003cimg width=\"1203\" alt=\"Screenshot 2025-03-19 at 11 36 20 PM\" src=\"https://github.com/user-attachments/assets/8b3c24f2-ea6a-40e5-8b35-b34a75787184\" /\u003e\n\nUh-oh! Now P3 is considered merged into P2, since its commit is in the history of P2!\nSo P3 just got closed as merged.\n\nWe also can't first point P2 to base on P3, then update their commits, since\nthat would result in P2 getting closed as merged,\nsince its commits C2 is in the history of P3.\n\nLet's rewind.\n\n\u003cimg width=\"1291\" alt=\"Screenshot 2025-03-19 at 11 36 16 PM\" src=\"https://github.com/user-attachments/assets/c77f8c91-cb32-41af-82af-7641aca5706c\" /\u003e\n\nThe solution is to first update P3's commits, then point P3 to base on P2.\nPrevent P3's commit from falling into P2's history, and prevent P2's commit from falling into P3's history.\n\n\u003cimg width=\"671\" alt=\"Screenshot 2025-03-19 at 11 36 25 PM\" src=\"https://github.com/user-attachments/assets/cd28a67f-c958-4e6f-bc1b-5d175e84d6ba\" /\u003e\n\n\u003cimg width=\"874\" alt=\"Screenshot 2025-03-19 at 11 36 28 PM\" src=\"https://github.com/user-attachments/assets/a9b32528-7bf6-43c6-bba0-a0bf02119c26\" /\u003e\n\nFinally wrap it up with P4/C4'.\n\n\u003cimg width=\"1182\" alt=\"Screenshot 2025-03-19 at 11 36 32 PM\" src=\"https://github.com/user-attachments/assets/9298395e-c22d-4d86-9d23-e33929fd3bc7\" /\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyang%2Fpyspr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyang%2Fpyspr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyang%2Fpyspr/lists"}