Thanks for your interest in GitRelay. This document covers how to build, test, and send changes.
- Be kind. Bug reports and PRs are welcome from everyone.
- Discuss large changes in an issue before starting so we can align on scope and design.
- GitRelay intentionally does not modify the user's dotfiles or global git config — no change should break that guarantee.
- macOS 14 (Sonoma) or later
- Xcode 16 or later
- Apple Silicon or Intel
git clone https://github.com/yangflow/gitrelay.git
cd gitrelay
open gitrelay.xcodeprojSelect the gitrelay scheme, press ⌘R.
xcodebuild test \
-project gitrelay.xcodeproj \
-scheme gitrelay \
-destination 'platform=macOS' \
-only-testing:gitrelayTests \
CODE_SIGNING_ALLOWED=NOBefore opening a PR, make sure this command exits 0.
If you add a bug fix, add a regression test that fails on the old code. New services need tests covering at least the happy path and one error case.
- Swift 6,
@MainActor/actor/Sendableas appropriate. Keep diffs concurrency-safe. - SwiftUI for all views. No AppKit unless unavoidable at the menu bar or window layer.
- Prefer plain structs and enums with associated values over class hierarchies.
- No force unwraps on user input. Throw typed errors.
- No unrelated refactors in bug-fix PRs.
Short imperative subject (under ~70 chars), optional body explaining the why:
<type>: <short subject, imperative>
<optional body explaining the why, not the what>
Type prefixes: feat, fix, refactor, docs, test, chore.
- Fork + branch off
main. - Implement the change. Add or update tests.
- Run the test command above. Confirm it exits 0.
- Push, open a PR against
main. - In the PR body: describe the why, not the what.
SyncEngine— errors must never leak credentials into logs.redactCredentialsis the gate; if you add a new log line that might contain a URL, pass it through.GitRunner— all git subprocess calls. If you add a new command, make sure it handles the cancelled signal path (GitError.cancelled) and cleans up any partially-written state.KeychainService— tokens must stay in the Keychain. Never store a token inRepoConfigorrepos.json.SyncScheduler—Timerfires on the main run loop. All callbacks must beMainActor-safe. UseMainActor.assumeIsolatedin the timer closure if needed.- Concurrency —
AppViewModel.inProgressSyncIDsis the guard against double-syncing. Do not bypass it.
If you discover a security issue, please email the maintainer instead of opening a public issue.
By contributing to GitRelay you agree your contributions are licensed under the MIT License.