{"id":51260145,"url":"https://github.com/fverse/berrypick","last_synced_at":"2026-06-29T11:32:16.988Z","repository":{"id":365303007,"uuid":"1271296142","full_name":"fverse/berrypick","owner":"fverse","description":"CLI to cherry-pick a commit, a PR, or whatever last changed a file:line onto your target branch.","archived":false,"fork":false,"pushed_at":"2026-06-26T02:44:42.000Z","size":6139,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T03:12:09.102Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","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/fverse.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":"2026-06-16T14:20:33.000Z","updated_at":"2026-06-26T02:44:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/fverse/berrypick","commit_stats":null,"previous_names":["fverse/berrypick"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/fverse/berrypick","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fverse%2Fberrypick","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fverse%2Fberrypick/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fverse%2Fberrypick/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fverse%2Fberrypick/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fverse","download_url":"https://codeload.github.com/fverse/berrypick/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fverse%2Fberrypick/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34925718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"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":"2026-06-29T11:32:16.865Z","updated_at":"2026-06-29T11:32:16.976Z","avatar_url":"https://github.com/fverse.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# berrypick\n\n**When the same change has to land on several long-lived branches, `berrypick`\nmoves the work over and tracks where it still needs to go.** Teams that maintain\nmore than one branch in parallel constantly re-apply the same commits by hand and\nlose track of which branches got which change. `berrypick` does the move for you\nand keeps a **shared, committed record** of what's been ported and what's still\npending: queue the picks a branch needs, see an at-a-glance matrix of done versus\ntodo across every branch, and have each pick record itself automatically — so\nnothing slips through and the whole team works from the same picture.\n[Jump to tracking →](#tracking-cherry-picks-across-branches)\n\nIt does the cherry-picking. Point it at a **commit hash**, a\n**`\u003cfile\u003e:\u003cline\u003e`** (the commit that last changed that line), or a **GitHub Pull\nRequest**, and it cherry-picks the work onto a fresh branch off your target —\nrecording the result as it goes.\n\n## Install\n\n### macOS / Linux\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/fverse/berrypick/main/install.sh | sh\n```\n\nThis downloads the **latest** release for your OS and architecture and installs\nit to `/usr/local/bin`.\n\n### Windows (PowerShell)\n\n```powershell\nirm https://raw.githubusercontent.com/fverse/berrypick/main/install.ps1 | iex\n```\n\n### With Go\n\n```sh\ngo install github.com/fverse/berrypick@latest\n```\n\n### Manual download\n\nGrab a prebuilt archive for your platform from the\n[latest release](https://github.com/fverse/berrypick/releases/latest) — builds\nare provided for **macOS**, **Linux**, and **Windows** (`amd64` and `arm64`).\n\n## Usage\n\n```\nberrypick \u003ccommit-hash | file:line | PR-url\u003e \u003ctarget-branch\u003e [branch-name] [flags]\n```\n\nThe first argument can be:\n\n- a **commit hash** (full or abbreviated) — cherry-picks that one commit;\n  every commit in the PR, in chronological order;\n- a **`\u003cfile\u003e:\u003cline\u003e`** reference — uses `git blame` to find the commit that last\n  changed that line and cherry-picks it (see [Blame mode](#blame-mode)).\n- a **GitHub PR URL** (`https://github.com/\u003cowner\u003e/\u003crepo\u003e/pull/\u003cn\u003e`) — cherry-picks\n\nBy default the new branch is named `cherry-pick/\u003cfirst 8 chars of the SHA\u003e`. Pass\nan optional third argument to name the branch yourself instead.\n\n### Examples\n\n```sh\n# Cherry-pick a single commit onto a new branch off release/1.2\nberrypick a1b2c3d4e5f6 release/1.2\n\n# Cherry-pick the commit that last changed line 42 of a file, onto main\nberrypick src/utils/util.js:42 main\n\n# Cherry-pick every commit in PR #123 onto a new branch off main, then push\nberrypick https://github.com/owner/repo/pull/123 main --push\n\n# Cherry-pick a commit onto a new branch off release/1.2 with a custom branch name\nberrypick a1b2c3d4e5f6 release/1.2 hotfix/login-bug\n\n```\n\n### Flags\n\n| Flag                 | Description                                                                             |\n| -------------------- | --------------------------------------------------------------------------------------- |\n| `--push`             | Push the new branch to `origin` after a successful cherry-pick.                         |\n| `--delete-local`     | Delete the local cherry-pick branch after a successful push. Requires `--push`.         |\n| `--force`            | Recreate the working branch if it already exists.                                       |\n| `-m`, `--mainline N` | Parent number (1-based) to follow when cherry-picking a **merge commit** (e.g. `-m 1`). |\n| `--rev \u003cref\u003e`        | For a `\u003cfile\u003e:\u003cline\u003e` source, blame this revision instead of the working tree.          |\n| `-h`, `--help`       | Show help.                                                                              |\n\n### Merge commits\n\nA merge commit has more than one parent, so git needs to know which side to\ntreat as the mainline. `berrypick` detects this and stops with guidance; re-run\nwith `-m 1` (usually the target-branch parent) to cherry-pick it:\n\n```sh\nberrypick -m 1 9f1e5d1b2622d3538a0589ac2d76758b551a1340 dev\n```\n\n### Blame mode\n\nPass a `\u003cfile\u003e:\u003cline\u003e` reference to cherry-pick the commit that last modified\nthat line, as reported by `git blame`:\n\n```sh\nberrypick internal/git/git.go:42 main\n\n# Blame a specific revision instead of the working tree\nberrypick internal/git/git.go:42 main --rev origin/main\n```\n\n\u003e **This cherry-picks the _entire_ commit that last touched the line, not just\n\u003e the line.** If that commit changed 10 files, all 10 come along.\n\nThe line is blamed in your working tree by default; use `--rev \u003cref\u003e` to blame a\nspecific commit or branch. It fails clearly when the line is not committed yet,\nthe line number is out of range, or the file is missing or untracked.\n\n## Tracking cherry-picks across branches\n\nFor teams that back-port the same fixes onto several branches, `berrypick`\ncan track what still needs picking and what's already done — in a **shared,\ncommitted** log so everyone sees the same picture.\n\n```sh\nberrypick init          # scaffold .berrypick/ (config.toml + log.jsonl)\n```\n\nThis creates a `.berrypick/` directory at the repo root:\n\n```\n.berrypick/\n  config.toml     # hand-edited, committed: your branch topology\n  log.jsonl       # append-only event log, committed\n```\n\n**Commit both files.** The log is append-only (one line per event) precisely so\ntwo teammates recording picks the same day produce two lines, not a merge\nconflict.\n\n### `config.toml`\n\nSimple form — one source, many targets:\n\n```toml\n[branches]\nsource  = \"main\"\ntargets = [\"release/2.0\", \"release/1.0\"]\n```\n\nAdvanced form — chained back-ports. If `[[flows]]` is present it wins over\n`[branches]`:\n\n```toml\n[[flows]]\nfrom = \"main\"\nto   = [\"release/2.0\"]\n\n[[flows]]\nfrom = \"release/2.0\"\nto   = [\"release/1.0\"]\n```\n\nOptional forge block (autodetected from the remote when omitted):\n\n```toml\n[forge]\nkind = \"github\"      # github | gitlab | ...\nhost = \"github.com\"\n```\n\nA configured branch that doesn't exist yet only warns — you can write the config\nahead of creating the branches.\n\n### `berrypick todo`\n\nTrack which picks still need to happen:\n\n```sh\n# Queue a change onto every configured target (or pass --to to pick branches)\nberrypick todo add a1b2c3d4\nberrypick todo add https://github.com/owner/repo/pull/123 --to release/2.0\nberrypick todo add internal/git/git.go:42\n\nberrypick todo list                     # grouped by target branch\nberrypick todo list --branch release/2.0\nberrypick todo list --json              # for scripting\n\nberrypick todo remove a1b2c3d4          # across all targets where it's queued\nberrypick todo remove a1b2c3d4 --to release/1.0\n```\n\nEach `(item, target)` pair is its own todo, so partial back-ports are trackable.\nSubject and author are resolved when you add, so the list reads well without\nre-fetching. Adding an already-queued or already-done pair is skipped, not\ndouble-added.\n\n### `berrypick status`\n\nFold the log into a matrix — rows are tracked changes, columns are target\nbranches:\n\n```sh\nberrypick status\n```\n\n```\nCherry-pick status (source: main)\n\n#   ID         SUBJECT                release/2.0   release/1.0\n─────────────────────────────────────────────────────────────────\n1   a1b2c3d4   Fix null deref         ✓ done        ⧗ todo\n2   e5f6a7b8   Patch CVE-2026-1234    ✓ done        ✓ done\n```\n\n| Cell     | Meaning                                     |\n| -------- | ------------------------------------------- |\n| `✓ done` | latest event for `(id, branch)` is **done** |\n| `⧗ todo` | latest event is **queued**                  |\n| `· -`    | not tracked for that branch                 |\n\nThe `#` column gives each row a number you can pick by (see below).\n\n```sh\nberrypick status --branch release/2.0   # one column\nberrypick status --json                 # full matrix as JSON\nberrypick status --reconcile            # see below\n```\n\n`--reconcile` scans each target branch's history for `cherry picked from commit`\n(`-x`) annotations and surfaces picks made **outside** berrypick, offering to\nbackfill `done` events for them. It's behind the flag so plain `status` stays\nfast.\n\n### Cherry-picking straight from a todo\n\nOnce a change is queued, you can pick it without retyping the SHA and target. The\nordinary `berrypick` command gains two tracking shortcuts:\n\n```sh\nberrypick :2            # pick row #2 from `berrypick status`\nberrypick a1b2c3d4      # pick a queued change onto the branch it's queued for\n```\n\n- A **`:N`** argument refers to row `N` in `berrypick status` (the leading colon\n  keeps it distinct from a commit hash). Row numbers are positional — they shift\n  as todos are added or completed, so they mean \"the list I'm looking at now,\"\n  not a stable id.\n- The **target branch is optional**: when the change is queued for exactly one\n  branch, that branch is used; when it's queued for several, you're asked which\n  (or pass the branch explicitly, e.g. `berrypick :2 release/1.0`).\n\nThe pick runs the normal cherry-pick flow, so it records the `done` event\nautomatically — the todo flips from `⧗ todo` to `✓ done`.\n\n### `berrypick compact`\n\n```sh\nberrypick compact\n```\n\nRewrites `log.jsonl` keeping only the latest event per `(id, target)` key, to\nbound growth. Derived state is unchanged. Because this rewrites the **shared**\ncommitted log, coordinate it like any shared-file rewrite (compact, commit, have\nteammates re-pull).\n\n## GitHub access\n\nFor PR sources, the tool talks to the GitHub REST API and discovers credentials\nautomatically, with **no `gh` binary required at runtime**:\n\n- A token from the `GH_TOKEN` / `GITHUB_TOKEN` environment variable, **or** the\n  token stored by a previous `gh auth login` — whichever is found first.\n- Enterprise hosts (`https://\u003chost\u003e/api/v3`) are handled automatically.\n- **Public repositories work with no credentials at all** (subject to GitHub's\n  low anonymous rate limit). A token is only required for **private** repos.\n\n```sh\nexport GITHUB_TOKEN=ghp_xxx   # for private repos, if you haven't run `gh auth login`\n```\n\n## Build\n\nFrom a clone:\n\n```sh\ngo build -o berrypick .   # or: make build\nmake install              # builds and installs into $(go env GOPATH)/bin\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffverse%2Fberrypick","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffverse%2Fberrypick","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffverse%2Fberrypick/lists"}