Cross repo cherry pick
Skill OmarEltak/legacy-prod-survival-kit/skills/cross-repo-cherry-pick
Use when you need to apply commits from one git repository to another whose history is unrelated. Common scenario; an old "dev" repo with N commits of undeployed work, and a fresh "prod-mirror" repo created from the live server. You want N-3 of those commits applied to the mirror without merging the histories. Handles the line-ending chaos (CRLF/LF) that occurs when source was on Windows and destination came from a Linux server. Includes a CRLF/LF detective sub-protocol.From its SKILL.md
npx -y skills add OmarEltak/legacy-prod-survival-kit --skill cross-repo-cherry-pickAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.8 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it
Cross-repo cherry-pick (with line-ending intelligence)
When to use this
You have two git repositories with unrelated histories:
- Source repo: an old development repository with commits you want to ship.
- Target repo: a newer repository that was initialised from a different starting point (often: a live production server's current state).
You want to bring specific commits — or a contiguous range, or "everything since commit X" — from source into target. Crucially:
- You do not want to merge the histories. They're unrelated; merging would be nonsense.
- You do not want a single giant "initial sync" commit; you want the actual content of those specific commits.
- You want the target's git history to make sense as "mirror of prod, plus these intentional changes."
This is harder than it sounds because:
git cherry-pickworks only when commits live in the same DAG.git format-patch+git amfails across repos because patches reference parent blob SHAs that don't exist in the target.- Working-tree files often have different line endings between the two repos (Windows checkout = CRLF; production pull = mixed CRLF/LF). Naive copy produces a "huge diff" that's 95% line-ending noise.
The right approach (chosen after trial-and-error)
- Identify the file set. What files were touched in the commit range you want?
- Detect line-ending convention in the target repo for those files (per-file).
- Extract content from source git blobs, not from the source working tree. Source git stores LF (typically); working tree may have CRLF after checkout.
- Write content to target with the target's line-ending convention (per-file match).
- Verify diff with
--ignore-all-spaceto see the actual content delta vs the line-ending noise. - Commit on a non-deployed branch with a message preserving the source commit summaries.
Concrete protocol
Step 1 — identify files in the commit range
In the source repo:
# range: from <start>^ (exclusive) to <end> (inclusive)
git diff --name-status <start>^..<end>
Output is M for modified, A for added, D for deleted, R for renamed. List of files is your work scope.
For a single commit:
git diff --name-status <commit>^..<commit>
Step 2 — line-ending detective
Don't trust assumptions. Check actual bytes per file in both repos.
# In source repo — what's in the git blob (typically LF)
git show HEAD:<file> | head -1 | od -c | head -1
# In source repo working tree — what's on disk (often CRLF after Windows checkout)
head -1 <file> | od -c | head -1
# In target repo — what's in HEAD
git -C <target> show HEAD:<file> | head -1 | od -c | head -1
You're looking for \r \n (CRLF) vs just \n (LF) at the end of the first line.
Common findings:
| Source git blob | Source working tree | Target git blob | Implication |
|---|---|---|---|
| LF | CRLF (autocrlf checkout) | LF | Easy — extract from blob, write to target |
| LF | CRLF | CRLF | Extract from blob, convert LF→CRLF, write |
| LF | CRLF | Mixed (some files LF, some CRLF) | Per-file detection required |
A 17-year-old PHP codebase that has been touched by both Windows and Linux editors will be inconsistent — some files LF, some CRLF, in the same repo.
Step 3 — extract from blobs (not working tree)
# Get the LF-correct content of the source file at HEAD (or any commit)
git -C <source> show HEAD:<file> > /tmp/blob-content
This gives you exactly what's in git, with no checkout-time conversion.
Step 4 — match target's convention
Per file, check target's line ending and convert if needed:
# Detect target's convention for a file
target_ending=$(git -C <target> show HEAD:<file> 2>/dev/null | head -c 4096 | grep -c $'\r')
# If $target_ending > 0, target uses CRLF for this file
if [ "$target_ending" -gt 0 ]; then
sed 's/$/\r/' /tmp/blob-content > /tmp/final-content
else
cp /tmp/blob-content /tmp/final-content
fi
cp /tmp/final-content <target>/<file>
Step 5 — verify the actual diff
cd <target>
git status -s # files changed
git diff --stat # raw diff (will look huge if lines differ)
git diff --stat --ignore-all-space # real content delta
Expect a discrepancy: if a file had CRLF in source-git's blob (rare) but you wrote LF to target, every line will show as changed in the raw diff. The --ignore-all-space view is the source of truth for "what content actually changed."
If the --ignore-all-space numbers match what the source commits' git log --stat showed, you applied the changes correctly.
Step 6 — commit on a safe branch
cd <target>
git checkout -b dev # if not already on a non-deployed branch
git add -A
git commit -m "$(cat <<'EOF'
feat: apply <feature-name> from <source-repo> to prod-mirror
Cherry-picks commits <range> from <source-repo>:
<commit-sha-1> <message-1>
<commit-sha-2> <message-2>
...
Files:
- <file-1>
- <file-2>
Note: line endings normalized to <LF|CRLF|mixed> to match target convention.
Real content delta (ignoring whitespace): +X / -Y lines.
EOF
)"
Push to a branch that does not auto-deploy. If your CI deploys on main push, push to dev or any other branch.
A more robust automated version (Bash one-liner)
For when you trust the protocol:
#!/usr/bin/env bash
# cross-repo-cherrypick.sh <source-repo-dir> <target-repo-dir> <range>
# e.g.: ./cross-repo-cherrypick.sh ~/old-dev ~/prod-mirror 6548e50^..HEAD
set -euo pipefail
SRC="$1"
DST="$2"
RANGE="$3"
# 1. List files
files=$(git -C "$SRC" diff --name-only "$RANGE")
# 2. For each file, extract from source blob, match target convention, write
while IFS= read -r f; do
[ -z "$f" ] && continue
mkdir -p "$DST/$(dirname "$f")"
# Extract source content (blob — LF normalized)
src_content=$(git -C "$SRC" show "HEAD:$f" 2>/dev/null) || {
echo "MISSING in source HEAD: $f"
continue
}
# Detect target convention (default to LF if file doesn't exist yet)
target_uses_crlf="no"
if [ -f "$DST/$f" ]; then
if head -c 4096 "$DST/$f" | grep -q $'\r'; then
target_uses_crlf="yes"
fi
fi
# Write with appropriate line ending
if [ "$target_uses_crlf" = "yes" ]; then
printf '%s' "$src_content" | sed 's/$/\r/' > "$DST/$f"
else
printf '%s' "$src_content" > "$DST/$f"
fi
echo "OK $f"
done <<< "$files"
echo "Done. Run \`cd $DST && git diff --stat --ignore-all-space\` to verify."
CRLF / LF detective protocol (sub-skill)
When you see a "huge git diff that doesn't make sense":
- Check the file's blob in HEAD on both sides. Use
git show <ref>:<file> | head -1 | od -c | head -1. - Check the file on disk in both working trees. Use
head -1 <file> | od -c | head -1. - Compare. Find the mismatch.
- Decide which convention you want to standardize on for the target repo.
- Convert before committing. Use
sed 's/\r$//'(CRLF→LF) orsed 's/$/\r/'(LF→CRLF). - Re-check
git diff --stat --ignore-all-spaceto verify actual content matches expectations.
The pattern: git diff output is very rarely lying about content. It IS often lying about how much you actually changed, because line endings inflate the apparent diff. Always cross-check with --ignore-all-space.
Why this skill exists
In one engagement, this exact problem ate ~45 minutes:
- Copied working-tree files from source to target. ❌ Broken — Windows checkout had CRLF, target had LF for some files.
- Converted all to LF. ❌ Broken — some target files were CRLF; converting introduced new diffs.
- Tried
git amwith patch files. ❌ Broken — blob SHAs don't exist across repos. - Tried
git apply --3way. ❌ Broken — same SHA problem. - Finally: extracted blobs from source git (always LF), detected target convention per file, wrote with matching convention. ✅ Works.
The trial-and-error was costly because every attempt produced a "huge diff" that looked plausible at first glance — you have to dig in to realize it's line-ending noise. Skip the chain by using this protocol from step 1.
Common mistakes to avoid
- Trusting
cpto preserve line endings. It does (binary copy). But the source working tree itself may have CRLF that the source's git blob doesn't have. The blob is the truth. - Running
dos2unixblindly across all files. Some prod files genuinely have CRLF (legacy Windows uploads). Don't normalize without checking the target's existing convention. - Using
git amfor cross-repo work. It needs SHAs. They don't exist. It will fail. Don't waste time. - Trusting the "huge diff" number. Always cross-check with
--ignore-all-space. The real number is usually 5–10% of the displayed number. - Committing the result to
maindirectly. If your target has auto-deploy, this ships untested code. Always use a branch that doesn't auto-deploy.
Output format
When you complete a cross-repo cherry-pick, produce a summary:
Source: <repo> <range>
Target: <repo> <branch>
Files: <count> (M: ..., A: ..., D: ...)
Raw diff: +<X> / -<Y>
Content diff: +<X'> / -<Y'> (--ignore-all-space)
Line-ending noise: <X-X'> + <Y-Y'>
Commit: <sha>
Branch: <branch> (push manually when ready)
This makes the actual scope of the change reviewable.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.