Cherry-pick applies the change introduced by a selected commit to the current line of development. It is useful for a targeted backport when an older release needs one fix without the rest of a development branch. It does not move or delete the source commit, and it does not establish a merge relationship with the source branch. The target result must be reviewed as a change in its own environment.

Select a change relative to its parent

For an ordinary single-parent commit, the relevant change is the difference from that parent. Git uses the available history and merge machinery to apply that change to the target. It is not simply replacing the target files with the source’s complete snapshot. Other changes already present on the target should remain unless the selected change or its resolution deliberately affects them.

Usually, cherry-pick records a new commit after the target tip. Its parent and potentially its tree differ from the source, so its identity differs too. The original author identity and author date are normally retained, while the committer identifies whoever records the backport. The commit message need not change. Do not describe cherry-picking as automatically assigning a new author or inventing a new message.

The default creates commits, but --no-commit instead leaves changes in the index and working tree for review or combination. With --ff, an eligible operation can fast-forward to the existing source commit. These explicit modes are reasons not to assert that every possible cherry-pick invocation always creates a different object.

A backport needs dependency review

A fix can depend on an API, schema, configuration, or earlier refactoring absent from the target branch. Clean textual application does not establish behavioral correctness. Read the source change and its prerequisites, then test the affected behavior on the actual maintenance branch. Backport only the dependencies that the intended fix requires, with an explicit explanation of any adaptation.

For a clean pick between shared branches, -x records the source commit ID in the new message. That makes later tracing easier, but is not cryptographic proof of equivalence or approval. Review the final message after manual conflict resolution too. The original commit’s signature does not become a valid signature for a newly created object; any required target signature belongs to the target’s release process.

When it breaks

On a conflict, Git keeps HEAD at the last successfully created commit and records the problematic source in CHERRY_PICK_HEAD. Cleanly applied paths can already be updated, while conflicted paths have unmerged index entries. Conflict markers are useful for text files, but binary conflicts and other conflict types require inspecting status and the index rather than searching only for marker strings.

Resolve the intended target behavior, stage the specific resolved paths, and run the relevant tests before git cherry-pick --continue. A staged file means the conflict was marked resolved, not that the code is correct. --abort returns to the pre-sequence state; --quit forgets operation state without performing the same rollback. Start from a clean, understood working tree so aborting does not become a substitute for preserving unrelated work.

A change already present can make the pick empty. Determine whether the target really contains the intended behavior before skipping; a superficially similar patch may have different effects. Selecting a merge commit introduces another choice: -m identifies the numbered parent used as the mainline. Choosing a parent is a semantic decision about the desired difference, not a generic conflict bypass, and the result does not reproduce the original merge topology.

Check it quickly

Run this with Python 3 and Git 2.28 or newer. The isolated temporary repository excludes personal Git configuration and inherited Git routing variables. It uses example identities, disables hooks and signing, and has no remote. One target accepts the fix; another deliberately edits the same line differently and exercises abort.

import os
import subprocess
import tempfile
from pathlib import Path

env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")}
env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull)
with tempfile.TemporaryDirectory(prefix="git-pick-demo-") as directory:
    def git(*args, check=True):
        return subprocess.run(
            ["git", "-c", "user.name=Backport Example",
             "-c", "user.email=backport@example.invalid",
             "-c", "commit.gpgsign=false", "-c", "core.hooksPath=/dev/null", *args],
            cwd=directory, env=env, text=True, capture_output=True, check=check,
        )
    def commit(text, message):
        (Path(directory) / "value.txt").write_text(text)
        git("add", "value.txt")
        git("commit", "-qm", message)
    git("init", "-q", "-b", "main")
    commit("old\n", "base")
    base = git("rev-parse", "HEAD").stdout.strip()
    git("switch", "-qc", "source")
    (Path(directory) / "value.txt").write_text("fixed\n")
    git("add", "value.txt")
    git("-c", "user.name=Original Example", "-c", "user.email=original@example.invalid",
        "commit", "-qm", "fix value")
    source = git("rev-parse", "HEAD").stdout.strip()
    git("switch", "-q", "main")
    git("commit", "--allow-empty", "-qm", "target progress")
    target = git("rev-parse", "HEAD").stdout.strip()
    git("cherry-pick", "-x", source)
    print("change-applied:", (Path(directory) / "value.txt").read_text() == "fixed\n")
    print("new-object-on-target:", git("rev-parse", "HEAD").stdout.strip() != source
          and git("rev-parse", "HEAD^").stdout.strip() == target)
    print("author-preserved:", git("show", "-s", "--format=%ae", "HEAD").stdout.strip()
          == "original@example.invalid")
    print("committer-is-backporter:", git("show", "-s", "--format=%ce", "HEAD").stdout.strip()
          == "backport@example.invalid")
    print("provenance-recorded:", source in git("show", "-s", "--format=%B", "HEAD").stdout)
    git("switch", "-qc", "conflict", base)
    commit("other\n", "target alternative")
    before = git("rev-parse", "HEAD").stdout.strip()
    conflict = git("cherry-pick", source, check=False)
    print("conflict-stopped:", conflict.returncode != 0
          and bool(git("ls-files", "-u").stdout)
          and git("rev-parse", "CHERRY_PICK_HEAD").stdout.strip() == source
          and git("rev-parse", "HEAD").stdout.strip() == before)
    git("cherry-pick", "--abort")
    print("abort-restored:", git("rev-parse", "HEAD").stdout.strip() == before
          and (Path(directory) / "value.txt").read_text() == "other\n"
          and not git("status", "--porcelain").stdout)

All seven checks should print True. The clean target contains the fixed value in a new commit whose parent is the target’s former tip. The source author remains, the backporter is the committer, and the message records provenance. The conflicting target stops with unmerged entries, then abort restores its prior text, tip, and clean status. This demonstrates transfer and recovery mechanics, not application-level correctness of a real backport.

Sources