Skip to content

feat: implement single commit cherry pick#2282

Merged
jcubic merged 5 commits intoisomorphic-git:mainfrom
IAmSSH:implement-cherry-pick
Feb 11, 2026
Merged

feat: implement single commit cherry pick#2282
jcubic merged 5 commits intoisomorphic-git:mainfrom
IAmSSH:implement-cherry-pick

Conversation

@IAmSSH
Copy link
Contributor

@IAmSSH IAmSSH commented Feb 8, 2026

I'm fixing a bug or typo

  • if this is your first time contributing, run npm run add-contributor and follow the prompts to add yourself to the README
  • squash merge the PR with commit message "fix: [Description of fix]"

I'm adding a parameter to an existing command X:

  • add parameter to the function in src/api/X.js (and src/commands/X.js if necessary)
  • document the parameter in the JSDoc comment above the function
  • add a test case in __tests__/test-X.js if possible
  • if this is your first time contributing, run npm run add-contributor and follow the prompts to add yourself to the README
  • read "Appendix A submodules" in the CONTRIBUTING.md document. Be sure to include submodule tests.

I'm adding a new command:

  • add as a new file in src/api (and src/commands if necessary)
  • add command to src/index.js
  • update __tests__/test-exports.js
  • create a test in src/__tests__
  • document the command with a JSDoc comment
  • add page to the Docs Sidebar website/sidebars.json
  • add page to the v1 Docs Sidebar website/versioned_sidebars/version-1.x-sidebars.json
  • if this is your first time contributing, run npm run add-contributor and follow the prompts to add yourself to the README
  • squash merge the PR with commit message "feat: Added 'X' command"
  • read "Appendix A submodules" in the CONTRIBUTING.md document. Be sure to include submodule tests.

Should close: #1736

Summary by CodeRabbit

  • New Features
    • Added a cherry-pick operation with conflict handling, dry-run, no-update option, custom committer support, and explicit error cases for invalid cherry-picks.
  • Tests
    • Added comprehensive test suites covering cherry-pick behavior, conflicts, dry-run, timestamp/committer semantics, and submodule scenarios.
  • Chores
    • Added a new contributor entry and updated the Contributors section in the README.

@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds cherry-pick support: new public cherryPick API and internal _cherryPick command implementing three‑way merge, new CherryPickMergeCommitError and CherryPickRootCommitError, updated exports, extensive tests (including submodule cases), and contributor/README updates.

Changes

Cohort / File(s) Summary
Public API & Entry
src/api/cherryPick.js, src/index.js
Add cherryPick wrapper: validate params, normalize committer, delegate to _cherryPick, and export from package entry.
Command Implementation
src/commands/cherryPick.js
New _cherryPick implementation: validate target, compute base, perform three‑way merge via mergeTree, handle conflicts (respecting abortOnConflict), create commit preserving author and applying committer, and optionally update index/worktree; supports dryRun, noUpdateBranch, and mergeDriver.
Errors
src/errors/CherryPickMergeCommitError.js, src/errors/CherryPickRootCommitError.js, src/errors/index.js
Add CherryPickMergeCommitError and CherryPickRootCommitError classes and re-export them from errors index.
Exports / Snapshots
__tests__/test-exports.js, __tests__/test-exports-in-submodule.js
Add cherryPick to public exports snapshots/tests.
Tests (core & submodule)
__tests__/test-cherryPick.js, __tests__/test-cherryPick-in-submodule.js
Add extensive cherry‑pick tests covering simple picks, dry‑run, merge‑commit rejection, conflicts (abort vs continue), custom committer, timestamps, noUpdateBranch, and submodule scenarios.
Docs / Contributors
.all-contributorsrc, README.md
Add new contributor entry for IAmSSH / iamssh.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API as cherryPick API
    participant Cmd as _cherryPick Command
    participant Git as Git Ops
    participant Merger as mergeTree
    participant Index as Index/Worktree Manager

    User->>API: cherryPick({ oid, options })
    API->>API: validate params / normalize committer
    API->>Cmd: _cherryPick({ oid, options })

    Cmd->>Git: readCommit(oid)
    Git-->>Cmd: commitObj
    Cmd->>Git: readCommit(HEAD)
    Git-->>Cmd: headCommit
    Cmd->>Git: readCommit(parent of oid)
    Git-->>Cmd: baseCommit

    Cmd->>Merger: mergeTree(our=HEAD.tree, base=base.tree, their=oid.tree, mergeDriver)
    Merger-->>Cmd: mergedTree or MergeConflictError

    alt MergeConflictError & abortOnConflict
        Cmd-->>API: throw MergeConflictError
    else Conflicts handled / merged
        Cmd->>Git: commit(tree=mergedTree, parent=HEAD, author=orig, committer=new)
        Git-->>Cmd: newOid
        Cmd->>Index: applyTreeChanges(newOid) (unless dryRun/noUpdateBranch)
        Index-->>Cmd: index/worktree updated
        Cmd-->>API: return newOid
    end
    API-->>User: newOid or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 I hopped through trees to pick a patch,
Three branches met and made a match,
Commits replayed with careful cheer,
Conflicts checked — no need to fear,
Backports bloom, a carrot batch! 🍒

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement single commit cherry pick' directly and accurately describes the main change in the pull request - implementation of cherry-pick functionality for single commits.
Linked Issues check ✅ Passed The PR implements cherry-pick functionality as required by issue #1736, including parameter validation, three-way merge logic, error handling for merge/root commits, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are within scope: cherry-pick implementation (API and commands), error classes, exports, tests, and contributor documentation. Documentation sidebar updates noted as incomplete are not blocking issues.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
__tests__/test-cherryPick-in-submodule.js (2)

24-164: Extract repeated submodule setup into a helper function.

Every test case repeats ~30-40 lines of identical submodule scaffolding (create superproject fixture, _mkdir, _cp, write .git file, init, setConfig). This makes each test harder to read and maintain — a single change to the setup pattern requires editing all 8 tests.

Consider extracting a helper like:

async function setupSubmoduleRepo(fixtureName, moduleName, opts = {}) {
  const { fs: fssp, dir: dirsp } = await makeFixture('superproject-test')
  const { dir, gitdir } = await makeFixture(fixtureName)
  await fssp._mkdir(join(dirsp, '.git'))
  await fssp._mkdir(join(dirsp, '.git', 'modules'))
  const gitdirsmfullpath = join(dirsp, '.git', 'modules', moduleName)
  await fssp._cp(gitdir, gitdirsmfullpath, { recursive: true })
  const officialSubmoduleDir = join(dirsp, moduleName)
  await fssp._cp(dir, officialSubmoduleDir, { recursive: true })
  const submoduleGitFile = join(officialSubmoduleDir, '.git')
  await fssp._writeFile(
    submoduleGitFile,
    `gitdir: ../.git/modules/${moduleName}\n`
  )
  await init({ fs: fssp, dir: officialSubmoduleDir, gitdir: submoduleGitFile })
  if (opts.setCommitter !== false) {
    await setConfig({ fs: fssp, dir: officialSubmoduleDir, gitdir: submoduleGitFile, path: 'user.name', value: opts.committerName || 'Committer' })
    await setConfig({ fs: fssp, dir: officialSubmoduleDir, gitdir: submoduleGitFile, path: 'user.email', value: opts.committerEmail || '[email protected]' })
  }
  return { fs: fssp, dir: officialSubmoduleDir, gitdir: submoduleGitFile }
}

This would reduce each test to its unique logic and make the suite much more maintainable.


726-790: Root commit test: gitCommit relies on config rather than explicit author — verify this is intentional.

At Line 768, gitCommit is called without an explicit author parameter, unlike all other tests in this file which pass author explicitly. It relies on user.name/user.email from setConfig (Lines 746-758). This works, but the implicit behavior differs from the rest of the test suite.

If intentional (testing that cherry-pick works with config-derived authors), a brief comment would help. Otherwise, pass author explicitly for consistency.

__tests__/test-cherryPick.js (1)

199-202: Redundant checkout on Line 200.

At this point, the test is already on branch b (checked out at Line 194). The checkout at Line 200 is a no-op.

Proposed fix
     await gitCommit({ fs, dir, gitdir, message: 'b' })
 
     // Merge a into b to create a merge commit on b
-    await checkout({ fs, dir, gitdir, ref: 'b' })
-    // Use merge API to create a merge commit
     await merge({ fs, dir, gitdir, theirs: 'a' })

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@IAmSSH
Copy link
Contributor Author

IAmSSH commented Feb 8, 2026

@jcubic Requesting review 😄

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@__tests__/test-cherryPick.js`:
- Around line 19-20: Reorder the import statements to satisfy ESLint import
grouping: move the package import "isomorphic-git/internal-apis"
(GitIndexManager) above the relative import './__helpers__/FixtureFS.js'
(makeFixture), and add a blank line between the external package import and the
relative import so there is a clear separation.
- Around line 186-188: Replace the dynamic import of merge with a static import
and use that symbol directly: add merge to the existing static import list at
the top (alongside any existing import like mergeApi) and then remove the
dynamic line "const merge = await import('isomorphic-git').then(m => m.merge)"
and call the statically imported merge(...) directly in the test where you
currently call merge({ fs, dir, gitdir, theirs: 'a' }).
🧹 Nitpick comments (4)
src/errors/CherryPickMergeCommitError.js (1)

1-18: Error class follows established patterns — LGTM.

The structure mirrors other error classes in the codebase (code, name, data pattern).

One minor observation: the message says "require specifying which parent to use as the base" (Line 11), but the current cherryPick API doesn't expose a parameter to do so. Consider adjusting the message to avoid implying unsupported functionality, or add a note that this may be supported in the future.

src/commands/cherryPick.js (1)

118-130: No signingKey/onSign support — acceptable for initial implementation.

The _commit call doesn't forward signing-related parameters. Users who require GPG-signed cherry-pick commits won't be able to use this directly. Consider noting this as a known limitation or a future enhancement.

src/index.js (1)

87-88: cherryPick is listed before checkout in exports — reversed alphabetical order.

The imports (Lines 12–13) correctly place checkout before cherryPick, but the named exports have them swapped. Same issue in the default export (Lines 162–163).

Proposed fix

Named exports:

   branch,
-  cherryPick,
   checkout,
+  cherryPick,
   clone,

Default export:

   branch,
-  cherryPick,
   checkout,
+  cherryPick,
   clone,
__tests__/test-cherryPick-in-submodule.js (1)

22-137: Extract repeated submodule setup into a helper function.

The submodule wiring boilerplate (makeFixture, mkdir, cp, writeFile gitdir pointer, init) is duplicated verbatim across all 6 tests (~20 lines each). This makes the tests harder to maintain — any change to the setup pattern requires updating every test.

Suggested helper
async function setupSubmoduleRepo(fixtureName, submoduleName) {
  const { fs: fssp, dir: dirsp } = await makeFixture('superproject-test')
  const { dir, gitdir } = await makeFixture(fixtureName)
  await fssp._mkdir(join(dirsp, '.git'))
  await fssp._mkdir(join(dirsp, '.git', 'modules'))
  const gitdirsmfullpath = join(dirsp, '.git', 'modules', submoduleName)
  await fssp._cp(gitdir, gitdirsmfullpath, { recursive: true })
  const officialSubmoduleDir = join(dirsp, submoduleName)
  await fssp._cp(dir, officialSubmoduleDir, { recursive: true })
  const submoduleGitFile = join(officialSubmoduleDir, '.git')
  await fssp._writeFile(
    submoduleGitFile,
    `gitdir: ../.git/modules/${submoduleName}\n`
  )
  await init({ fs: fssp, dir: officialSubmoduleDir, gitdir: submoduleGitFile })
  return { fs: fssp, dir: officialSubmoduleDir, gitdir: submoduleGitFile }
}

Each test then becomes:

const { fs, dir, gitdir } = await setupSubmoduleRepo('tmp-cherry-sub', 'mysubmodule')

@jcubic
Copy link
Member

jcubic commented Feb 8, 2026

Thanks for the PR. Will try to check tomorrow.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new cherryPick public API to isomorphic-git to support single-commit cherry-picking onto the current branch, including conflict handling, dry-run, and no-update options (closing #1736).

Changes:

  • Exposes a new cherryPick API via src/index.js and export tests.
  • Implements cherry-pick logic via a three-way merge + commit creation, with optional workdir/index update.
  • Adds a new CherryPickMergeCommitError and comprehensive test coverage (including submodule scenarios).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/index.js Exports the new cherryPick API (named + default export).
src/api/cherryPick.js Public API wrapper: parameter validation, gitdir discovery, committer normalization, delegates to command.
src/commands/cherryPick.js Core cherry-pick implementation (read commits, merge trees, commit result, update workdir/index).
src/errors/CherryPickMergeCommitError.js New error type for rejecting merge commits.
src/errors/index.js Re-exports the new error from the Errors bundle.
tests/test-exports.js Ensures cherryPick is part of the public exports.
tests/test-exports-in-submodule.js Ensures cherryPick export works in submodule test harness.
tests/test-cherryPick.js New test suite for cherry-pick behavior and options.
tests/test-cherryPick-in-submodule.js Cherry-pick test suite in submodule scenario.
README.md Adds contributor entry.
.all-contributorsrc Adds contributor metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@IAmSSH
Copy link
Contributor Author

IAmSSH commented Feb 11, 2026

@jcubic I have updated the PRs after fixing the comments. Let me know if anything else needs to be fixed.

@jcubic
Copy link
Member

jcubic commented Feb 11, 2026

Looks good. Can you add one more check for the happy path? You only check the message of the commit. It would be a good idea to check if the work tree has content from the cherry-picked commit and if the staging area is empty.

You can use the git.status on the file that was changed or git.statusMatrix.

The command just uses mergeTree, but it will not hurt if we check if the state of the Git repo is correct.

@IAmSSH
Copy link
Contributor Author

IAmSSH commented Feb 11, 2026

Looks good. Can you add one more check for the happy path? You only check the message of the commit. It would be a good idea to check if the work tree has content from the cherry-picked commit and if the staging area is empty.

You can use the git.status on the file that was changed or git.statusMatrix.

The command just uses mergeTree, but it will not hurt if we check if the state of the Git repo is correct.

@jcubic Done

@jcubic jcubic merged commit ba6177f into isomorphic-git:main Feb 11, 2026
4 checks passed
@isomorphic-git-bot
Copy link
Member

🎉 This PR is included in version 1.37.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support cherry-pick

3 participants

Comments