Simple App to help @jaspermayone make it through COMP1050 with a professor who won't use version control.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add automated release pipeline and docs

+407
+133
.github/workflows/release.yml
··· 1 + name: Release 2 + 3 + on: 4 + push: 5 + tags: 6 + - 'v*' 7 + 8 + jobs: 9 + build-and-release: 10 + runs-on: macos-latest 11 + 12 + steps: 13 + - name: Checkout code 14 + uses: actions/checkout@v4 15 + 16 + - name: Set up Xcode 17 + uses: maxim-lobanov/setup-xcode@v1 18 + with: 19 + xcode-version: latest-stable 20 + 21 + - name: Import Code Signing Certificate 22 + env: 23 + CERTIFICATE_BASE64: ${{ secrets.CERTIFICATE_BASE64 }} 24 + CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }} 25 + run: | 26 + # Create temporary keychain 27 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db 28 + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) 29 + 30 + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" 31 + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" 32 + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" 33 + 34 + # Import certificate 35 + echo "$CERTIFICATE_BASE64" | base64 --decode > certificate.p12 36 + security import certificate.p12 \ 37 + -k "$KEYCHAIN_PATH" \ 38 + -P "$CERTIFICATE_PASSWORD" \ 39 + -T /usr/bin/codesign \ 40 + -T /usr/bin/productsign 41 + 42 + # Set keychain for codesigning 43 + security list-keychain -d user -s "$KEYCHAIN_PATH" 44 + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" 45 + 46 + # Clean up certificate file 47 + rm certificate.p12 48 + 49 + - name: Build app 50 + run: | 51 + xcodebuild -project ZipMerge.xcodeproj \ 52 + -scheme ZipMerge \ 53 + -configuration Release \ 54 + -derivedDataPath ./build \ 55 + CODE_SIGN_IDENTITY="Developer ID Application" \ 56 + CODE_SIGN_STYLE=Manual \ 57 + DEVELOPMENT_TEAM=M67B42LX8D \ 58 + clean build 59 + 60 + - name: Sign app bundle 61 + run: | 62 + codesign --force --deep --sign "Developer ID Application" \ 63 + --options runtime \ 64 + --timestamp \ 65 + ./build/Build/Products/Release/ZipMerge.app 66 + 67 + - name: Create DMG (optional, or use zip) 68 + run: | 69 + # Create a zip file for distribution 70 + cd ./build/Build/Products/Release 71 + zip -r ZipMerge.zip ZipMerge.app 72 + mv ZipMerge.zip $GITHUB_WORKSPACE/ 73 + 74 + - name: Notarize app 75 + env: 76 + APPLE_ID: ${{ secrets.APPLE_ID }} 77 + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} 78 + APPLE_TEAM_ID: M67B42LX8D 79 + run: | 80 + # Submit for notarization 81 + xcrun notarytool submit ZipMerge.zip \ 82 + --apple-id "$APPLE_ID" \ 83 + --team-id "$APPLE_TEAM_ID" \ 84 + --password "$APPLE_ID_PASSWORD" \ 85 + --wait 86 + 87 + # Unzip, staple, and re-zip 88 + unzip ZipMerge.zip 89 + xcrun stapler staple ZipMerge.app 90 + rm ZipMerge.zip 91 + zip -r ZipMerge.zip ZipMerge.app 92 + 93 + - name: Calculate SHA256 94 + id: sha 95 + run: | 96 + SHA256=$(shasum -a 256 ZipMerge.zip | awk '{print $1}') 97 + echo "sha256=$SHA256" >> $GITHUB_OUTPUT 98 + echo "SHA256: $SHA256" 99 + 100 + - name: Create Release 101 + uses: softprops/action-gh-release@v1 102 + with: 103 + files: ZipMerge.zip 104 + body: | 105 + ## Installation 106 + 107 + ### Via Homebrew 108 + ```bash 109 + brew tap jaspermayone/tap 110 + brew install --cask zipmerge 111 + ``` 112 + 113 + ### Manual Installation 114 + 1. Download ZipMerge.zip 115 + 2. Unzip and move ZipMerge.app to /Applications 116 + 117 + ## SHA256 Checksum 118 + ``` 119 + ${{ steps.sha.outputs.sha256 }} 120 + ``` 121 + 122 + ## Update Homebrew Cask 123 + Update the SHA256 in `Casks/zipmerge.rb`: 124 + ```ruby 125 + sha256 "${{ steps.sha.outputs.sha256 }}" 126 + ``` 127 + env: 128 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 129 + 130 + - name: Clean up keychain 131 + if: always() 132 + run: | 133 + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
+154
CLAUDE.md
··· 1 + # CLAUDE.md 2 + 3 + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 + 5 + ## Project Overview 6 + 7 + ZipMerge is a macOS SwiftUI application that compares a local project directory with a teacher's submitted zip file. It's designed to help students merge changes from assignments when version control isn't used by instructors. 8 + 9 + ## Building and Running 10 + 11 + ```bash 12 + # Build the project 13 + xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge build 14 + 15 + # Run the app (after building in Xcode) 16 + open ZipMerge.xcodeproj 17 + # Then press Cmd+R in Xcode to run 18 + ``` 19 + 20 + The project uses Swift 5.0 and requires macOS with SwiftUI support. 21 + 22 + ## Code Signing and Installation 23 + 24 + The project is configured with: 25 + - **Bundle Identifier**: com.singlefeather.ZipMerge 26 + - **Development Team**: M67B42LX8D 27 + - **Code Sign Style**: Automatic 28 + - **Code Sign Identity**: Currently ad-hoc (for local development) 29 + 30 + ### Building for Local Development 31 + 32 + ```bash 33 + # Build the project 34 + xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge -configuration Release clean build 35 + 36 + # The built app will be in: 37 + # ./build/Release/ZipMerge.app 38 + 39 + # Copy to Applications folder (optional) 40 + cp -r build/Release/ZipMerge.app /Applications/ 41 + ``` 42 + 43 + ### Automated Release Pipeline 44 + 45 + **See RELEASING.md for complete setup instructions.** 46 + 47 + The project uses GitHub Actions for automated releases. When you push a version tag, it automatically: 48 + 1. Builds with Developer ID signing 49 + 2. Notarizes with Apple 50 + 3. Creates GitHub release with signed .zip 51 + 4. Outputs SHA256 for Homebrew cask 52 + 53 + **Quick release process:** 54 + ```bash 55 + git tag v1.0.0 56 + git push origin v1.0.0 57 + ``` 58 + 59 + Then update the SHA256 in `/Users/jsp/dev/projects/homebrew-tap/Casks/zipmerge.rb` from the release notes. 60 + 61 + **Required GitHub Secrets** (one-time setup - see RELEASING.md): 62 + - `CERTIFICATE_BASE64` - Developer ID certificate 63 + - `CERTIFICATE_PASSWORD` - Certificate password 64 + - `APPLE_ID` - Apple ID email 65 + - `APPLE_ID_PASSWORD` - App-specific password for notarization 66 + 67 + ### Distributing via Homebrew 68 + 69 + The Homebrew cask is located at `/Users/jsp/dev/projects/homebrew-tap/Casks/zipmerge.rb`. 70 + 71 + After each release, update the SHA256 hash in the cask file with the value from the release notes. 72 + 73 + **Users can install via:** 74 + ```bash 75 + brew tap jaspermayone/tap 76 + brew install --cask zipmerge 77 + ``` 78 + 79 + ### Manual Distribution (Without CI) 80 + 81 + If you need to build manually: 82 + 83 + ```bash 84 + # Build with Developer ID signing 85 + xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge -configuration Release \ 86 + CODE_SIGN_IDENTITY="Developer ID Application" clean build 87 + 88 + # Notarize 89 + xcrun notarytool submit build/Release/ZipMerge.app.zip \ 90 + --apple-id your-email@example.com \ 91 + --team-id M67B42LX8D \ 92 + --password app-specific-password \ 93 + --wait 94 + 95 + # Staple and package 96 + xcrun stapler staple build/Release/ZipMerge.app 97 + cd build/Release && zip -r ZipMerge.zip ZipMerge.app 98 + ``` 99 + 100 + ## Architecture 101 + 102 + ### Core Components 103 + 104 + **Models.swift** - Defines the data model: 105 + - `FileChangeType`: Enum for tracking file states (added, modified, deleted, unchanged) 106 + - `MergeDecision`: User decision per file (keepMine, takeTheirs, pending) 107 + - `ComparedFile`: Represents a single file comparison with diff hunks 108 + - `DiffHunk` and `DiffLine`: Structures for granular diff display (not yet fully implemented) 109 + - `ComparisonResult`: Contains all compared files and directories 110 + 111 + **FileComparer.swift** - File operations and comparison logic: 112 + - `extractZip()`: Uses system `unzip` command to extract archives 113 + - `findRootDirectory()`: Handles zips with wrapper folders 114 + - `compare()`: Main comparison engine that walks both directory trees and categorizes changes 115 + - `applyChanges()`: Applies user merge decisions to the local directory 116 + - `computeHunks()`: Placeholder for future hunk-level merging (currently returns empty array) 117 + 118 + **ContentView.swift** - Main UI orchestration: 119 + - Left panel: Directory picker, zip drop zone, and file list 120 + - Right panel: Diff view for selected file 121 + - State management for comparison results and merge decisions 122 + - Git integration: Detects git repos and offers commit creation after merge 123 + - File cleanup: Removes temp directories and zip files after successful merge 124 + 125 + **DiffView.swift** - File diff visualization: 126 + - Side-by-side view for modified files 127 + - Single pane for added/deleted files with color-coded backgrounds 128 + - Line-by-line display with monospaced font 129 + 130 + ### Data Flow 131 + 132 + 1. User selects project directory and drops teacher's zip file 133 + 2. `FileComparer.extractZip()` extracts to temp directory 134 + 3. `FileComparer.compare()` walks both trees and generates `ComparisonResult` 135 + 4. UI displays changed files with color-coded icons 136 + 5. User reviews diffs and makes keepMine/takeTheirs decisions per file 137 + 6. `FileComparer.applyChanges()` applies decisions 138 + 7. If git repo detected, optionally creates commit 139 + 8. Cleanup removes temp files and zip 140 + 141 + ### Important Implementation Details 142 + 143 + - Zip extraction uses system `/usr/bin/unzip` command via Process 144 + - Comparison is byte-level for binaries, line-level diffs for text (when viewing) 145 + - Temp directories are created in system temp folder with UUID names 146 + - Git commits use `/usr/bin/git` directly (not libgit2 or similar) 147 + - All file operations happen on background queue, UI updates on main thread 148 + 149 + ## Known Limitations 150 + 151 + - Hunk-level merging (`computeHunks()` and `applySelectedHunks()`) is stubbed but not implemented 152 + - Currently only supports whole-file merge decisions 153 + - No conflict resolution UI for line-level merging 154 + - Binary files show as "(binary or unreadable)" in diff view
+120
RELEASING.md
··· 1 + # Release Process 2 + 3 + This document describes the automated release pipeline for ZipMerge. 4 + 5 + ## Automated Release Pipeline 6 + 7 + Releases are automated via GitHub Actions. When you push a version tag, the workflow will: 8 + 9 + 1. Build the app with Developer ID signing 10 + 2. Notarize the app with Apple 11 + 3. Create a GitHub release with the signed .zip 12 + 4. Output the SHA256 hash for the Homebrew cask 13 + 14 + ## Required GitHub Secrets 15 + 16 + Before creating your first release, set up these secrets in GitHub repository settings (Settings → Secrets and variables → Actions): 17 + 18 + ### 1. CERTIFICATE_BASE64 19 + 20 + Your Developer ID Application certificate exported as base64. 21 + 22 + **How to create:** 23 + 24 + ```bash 25 + # Export your Developer ID certificate from Keychain Access 26 + # 1. Open Keychain Access 27 + # 2. Find "Developer ID Application: <Your Name> (M67B42LX8D)" 28 + # 3. Right-click → Export "Developer ID Application..." 29 + # 4. Save as certificate.p12 with a password 30 + 31 + # Convert to base64 32 + base64 -i certificate.p12 | pbcopy 33 + 34 + # Paste the output as CERTIFICATE_BASE64 secret in GitHub 35 + # Then delete the certificate.p12 file 36 + rm certificate.p12 37 + ``` 38 + 39 + ### 2. CERTIFICATE_PASSWORD 40 + 41 + The password you used when exporting the certificate.p12 file. 42 + 43 + ### 3. APPLE_ID 44 + 45 + Your Apple ID email address (the one associated with your developer account). 46 + 47 + ### 4. APPLE_ID_PASSWORD 48 + 49 + An app-specific password for notarization (NOT your Apple ID password). 50 + 51 + **How to create:** 52 + 53 + 1. Go to https://appleid.apple.com/account/manage 54 + 2. Sign in with your Apple ID 55 + 3. In the Security section, under "App-Specific Passwords", click "Generate Password" 56 + 4. Enter "GitHub Actions Notarization" as the name 57 + 5. Copy the generated password and save it as APPLE_ID_PASSWORD in GitHub 58 + 59 + ## Creating a Release 60 + 61 + Once secrets are configured: 62 + 63 + ```bash 64 + # Ensure you're on main branch with latest changes 65 + git checkout main 66 + git pull 67 + 68 + # Create and push a version tag 69 + git tag v1.0.0 70 + git push origin v1.0.0 71 + ``` 72 + 73 + The GitHub Actions workflow will automatically: 74 + - Build and sign the app 75 + - Notarize with Apple (takes 5-10 minutes) 76 + - Create a GitHub release with ZipMerge.zip 77 + - Include SHA256 hash in release notes 78 + 79 + ## Updating the Homebrew Cask 80 + 81 + After the release completes: 82 + 83 + 1. Copy the SHA256 from the release notes 84 + 2. Update `/Users/jsp/dev/projects/homebrew-tap/Casks/zipmerge.rb`: 85 + ```ruby 86 + sha256 "THE_SHA256_FROM_RELEASE_NOTES" 87 + ``` 88 + 3. Update version if needed 89 + 4. Commit and push to homebrew-tap: 90 + ```bash 91 + cd /Users/jsp/dev/projects/homebrew-tap 92 + git add Casks/zipmerge.rb 93 + git commit -m "Update zipmerge to v1.0.0" 94 + git push 95 + ``` 96 + 97 + Users can then install with: 98 + ```bash 99 + brew tap jaspermayone/tap 100 + brew install --cask zipmerge 101 + ``` 102 + 103 + ## Troubleshooting 104 + 105 + ### Notarization fails 106 + 107 + - Verify APPLE_ID and APPLE_ID_PASSWORD are correct 108 + - Ensure app-specific password hasn't expired 109 + - Check notarization logs in the GitHub Actions output 110 + 111 + ### Code signing fails 112 + 113 + - Verify CERTIFICATE_BASE64 is correctly encoded 114 + - Ensure CERTIFICATE_PASSWORD matches the export password 115 + - Certificate must be "Developer ID Application" (not "Apple Development") 116 + 117 + ### Build fails 118 + 119 + - Check that Xcode version on GitHub Actions supports your Swift version 120 + - Verify project builds locally first with `xcodebuild`