diff --git a/install.zsh b/install.zsh index 26e1dc6..7ba902f 100755 --- a/install.zsh +++ b/install.zsh @@ -572,8 +572,179 @@ curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $ https://${host}/xrpc/com.atproto.repo.putRecord } +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# Patch creation helpers +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +# Find existing patches that modify the same file +# Usage: at-patch-find-conflicts +function at-patch-find-conflicts() { + local filepath="$1" + local patch_dir="$2" + local conflicts=() + + if [ ! -d "$patch_dir" ]; then + return + fi + + for pf in "$patch_dir"/*.patch(N) "$patch_dir"/*.diff(N); do + [ -f "$pf" ] || continue + if grep -q "^--- a/$filepath" "$pf" 2>/dev/null || grep -q "^+++ b/$filepath" "$pf" 2>/dev/null; then + conflicts+=("$(basename "$pf")") + fi + done + + if [ ${#conflicts[@]} -gt 0 ]; then + echo "⚠️ This file is also modified by:" + for c in "${conflicts[@]}"; do + echo " - $c" + done + echo "" + echo " Ensure patches are applied in order before patch-begin." + echo " Baseline must reflect the post-earlier-patches state." + fi +} + +# Save current file state as baseline for patch creation +# Usage: ./install.zsh patch-begin [--ios] +# Example: ./install.zsh patch-begin social-app "src/screens/Profile/Header/ProfileHeaderStandard.tsx" --ios +function at-patch-begin() { + local repo="$1" + local filepath="$2" + local flag="$3" + + if [ -z "$repo" ] || [ -z "$filepath" ]; then + echo "Usage: ./install.zsh patch-begin [--ios]" + echo "Example: ./install.zsh patch-begin social-app \"src/screens/Profile/Header/ProfileHeaderStandard.tsx\" --ios" + return 1 + fi + + local full_path="$d/repos/$repo/$filepath" + if [ ! -f "$full_path" ]; then + echo "❌ File not found: $full_path" + return 1 + fi + + local tmp_file="/tmp/patch-base--$(echo "$filepath" | tr '/' '-')" + cp "$full_path" "$tmp_file" + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ Baseline saved: $tmp_file" + echo " File: $full_path" + echo "" + + # Check for conflicting patches + if [ "$flag" = "--ios" ]; then + at-patch-find-conflicts "$filepath" "$d/ios/patching" + else + at-patch-find-conflicts "$filepath" "$d/patching" + fi + + echo "Next steps:" + echo " 1. Edit: $full_path" + echo " 2. Save: ./install.zsh patch-save $repo \"$filepath\" ${flag:---ios}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +} + +# Generate patch from baseline diff +# Usage: ./install.zsh patch-save [--ios] +# Example: ./install.zsh patch-save 042-social-app-ios-feature.patch social-app "src/path/to/file.tsx" --ios +function at-patch-save() { + local patch_filename="$1" + local repo="$2" + local filepath="$3" + local flag="$4" + + if [ -z "$patch_filename" ] || [ -z "$repo" ] || [ -z "$filepath" ]; then + echo "Usage: ./install.zsh patch-save [--ios]" + echo "Example: ./install.zsh patch-save 042-social-app-ios-feature.patch social-app \"src/file.tsx\" --ios" + return 1 + fi + + local full_path="$d/repos/$repo/$filepath" + local tmp_file="/tmp/patch-base--$(echo "$filepath" | tr '/' '-')" + + if [ ! -f "$tmp_file" ]; then + echo "❌ No baseline found. Run 'patch-begin' first." + return 1 + fi + if [ ! -f "$full_path" ]; then + echo "❌ File not found: $full_path" + return 1 + fi + + # Determine output directory + local patch_dir="$d/patching" + if [ "$flag" = "--ios" ]; then + patch_dir="$d/ios/patching" + fi + + # Generate diff with proper a/b paths + diff -u "$tmp_file" "$full_path" \ + | sed "1s|--- .*|--- a/$filepath|" \ + | sed "2s|+++ .*|+++ b/$filepath|" \ + > "$patch_dir/$patch_filename" + + local line_count + line_count=$(wc -l < "$patch_dir/$patch_filename" | tr -d ' ') + if [ "$line_count" -eq 0 ]; then + echo "⚠️ No differences found. Patch file is empty." + rm "$patch_dir/$patch_filename" + return 1 + fi + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📝 Patch: $patch_dir/$patch_filename ($line_count lines)" + + # Dry-run verify: restore baseline, test patch, restore edit + pushd "$d/repos/$repo" > /dev/null + cp "$full_path" /tmp/patch-edited-tmp + cp "$tmp_file" "$full_path" + + if patch --dry-run -p1 < "$patch_dir/$patch_filename" > /dev/null 2>&1; then + echo "✅ Dry-run: OK" + else + echo "❌ Dry-run: FAILED" + patch --dry-run -p1 < "$patch_dir/$patch_filename" 2>&1 | head -5 + fi + + cp /tmp/patch-edited-tmp "$full_path" + rm -f /tmp/patch-edited-tmp + popd > /dev/null + + rm -f "$tmp_file" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +} + +# Verify all patches can be applied (full dry-run) +# Usage: ./install.zsh patch-check [--ios] +function at-patch-check() { + local flag="$1" + + if [ "$flag" = "--ios" ]; then + echo "Checking iOS patches against: $d/repos/social-app" + pushd "$d/repos/social-app" > /dev/null + for pf in "$d/ios/patching"/*.patch; do + [ -f "$pf" ] || continue + local name="$(basename "$pf")" + if patch --dry-run -p1 < "$pf" > /dev/null 2>&1; then + echo " ✅ $name" + else + echo " ❌ $name" + fi + done + popd > /dev/null + else + echo "Checking server patches..." + for pf in "$d/patching"/*.patch "$d/patching"/*.diff; do + [ -f "$pf" ] || continue + echo " $(basename "$pf")" + done + fi +} + at-repos-env -case "$1" in +case "$1" in pull) at-repos-clone at-repos-pull @@ -587,6 +758,18 @@ case "$1" in show-failed-patches exit ;; + patch-begin) + at-patch-begin "$2" "$3" "$4" + exit + ;; + patch-save) + at-patch-save "$2" "$3" "$4" "$5" + exit + ;; + patch-check) + at-patch-check "$2" + exit + ;; build) at-repos-build-docker-atproto $2 exit