Integrations
Consumer apps should depend on git-pont contracts and adapters, not copy provider-specific OAuth or REST behavior.
App Guides
Shared References
Lezin Integration
Lezin should use git-pont only for remote document operations. It should not depend on GitFolder and should not shell out to Git.
User Flows
Open From Git URL
Menu:
File > Open from Git URL...
Flow:
- User pastes a GitHub/GitLab/Forgejo/Gitea/Codeberg file URL.
- Lezin calls
gitPont.openFile(from:). URL ambiguity (slashed branch names) is resolved inside git-pont; Lezin never sees it. - If public read works, open immediately.
- If
.authenticationRequiredor.missingConnection, show the connection flow, then retry. - Store the loaded
GitRemoteFile.referenceandversionwith the document. - Also fetch
gitPont.repository(file.reference.repository)and keeppermissions— the save UI depends on it.
Browse a Connected Repository (optional but recommended)
Paste-a-URL is a power-user flow. A picker is friendlier:
- User picks a connection.
gitPont.repositories(connectionID:)→ repo list (truncatedflag shown as "showing first 3000").gitPont.branches(of:)→ branch choice, default branch preselected.gitPont.listDirectory(_:)→ file tree; opening a file callsreadFile.
Save Remote Document
For a remote document, normal save becomes commit behavior. What the dialog offers depends on GitRepository.permissions:
canPush == true: offer Commit to {branch} and Commit to new branch + open {PR/MR}.canPush == false: offer only Propose change (fork + {PR/MR}) — this is the common case for files the user does not own. Never show a direct-commit option that is guaranteed to fail with 403.
Use provider.changeRequestTerm for the PR/MR label.
Flow:
- User saves.
- If document source is remote, show commit dialog.
- Lezin builds a
GitChangeSubmissionand callsgitPont.submitChange(_:):- direct commit →
.directCommit - new branch + PR →
.branchAndPullRequest(...) - no push access →
.forkAndPullRequest(...) - or simply
.automatic(...)and let git-pont decide
- direct commit →
- On success, update the stored remote version from
GitChangeResult.commit.newVersion, update the stored reference tousedRepository/usedBranch(it may now point at a fork branch), clear dirty state, and offer to openpullRequest.webURLwhen one was created. - On
.conflict, show conflict UI and do not overwrite. - On
.partialSubmission, tell the user what succeeded (for example "committed, but opening the PR failed") and offer retry of only the missing step.
Document Source
Lezin should introduce a source enum:
enum DocumentSource {
case local(URL)
case remote(GitFileReference, version: GitRemoteVersion?, permissions: GitRepositoryPermissions?)
case unsaved
}
Commit Dialog
Fields:
- commit message
- target branch (hidden when forking; git-pont picks the fork branch name from the submission)
- optional create new branch (name defaulted to
lezin/{slugified-filename}) - optional open PR/MR after commit (with title/body fields; term from
changeRequestTerm)
Default message:
Update {filename}
Staleness Check
Before the user starts a long edit, or on window focus, Lezin may call gitPont.checkForRemoteChange(_:). If it returns true, show a non-blocking "remote has changed" banner so the user can reload before investing more work. This is advisory; the commit-time version check remains the real protection.
Conflict Handling
If git-pont returns .conflict:
- Do not silently overwrite.
- Offer reload remote, copy local changes, or save to new branch (a
.branchAndPullRequestsubmission sidesteps the conflict). - Keep local editor content intact.
Settings
Settings should show provider connections:
Settings > Integrations
├─ GitHub
├─ GitLab.com
├─ Self-hosted GitLab
├─ Codeberg
├─ Forgejo
└─ Gitea
Multiple connections per provider are allowed (personal + work). When a URL matches an instance with several connections, git-pont throws .ambiguousConnection; Lezin shows a picker and stores the chosen connectionID with the document.
Lezin should let users enable/disable integrations, but disabled integrations should not delete stored credentials unless the user explicitly disconnects.
GitFolder Integration
GitFolder is an end-user menu bar app that syncs local folders to remote repositories. It should use git-pont for platform connections and credential resolution, not for the sync engine itself.
Current GitFolder Shape
GitFolder currently:
- stores selected folders with security-scoped bookmarks
- runs system
git - initializes repos
- configures remotes
- creates snapshot commits
- pulls with rebase
- pushes to remote
- has GitHub device auth
- stores a GitHub token in Keychain
- injects GitHub token through a Git credential helper
Target Shape
Replace GitHub-specific auth and language with provider-neutral connections.
Current concepts:
githubToken
hasGitHubToken
GitHubOAuthService
AuthMode.githubToken
GitSyncError.missingGitHubToken
testGitHubAccess
Target concepts:
GitConnection
hasProviderConnection
GitPont
AuthMode.providerToken
GitSyncError.missingProviderConnection
testRepositoryAccess
Folder Model
Evolve SyncedFolder:
struct SyncedFolder {
var repoUrl: String
var provider: String // GitProviderKind rawValue
var connectionID: String?
var branch: String
}
Avoid storing tokens in folder config. Always store connectionID on new folders so multi-account users never hit .ambiguousConnection during background sync.
Folder Setup Flow
- User picks a local folder.
- User picks a connection (or creates one).
- User picks an existing repository via
gitPont.repositories(connectionID:)— or creates a new one viagitPont.createRepository(_:connectionID:)(name defaulted from the folder name, private by default). This removes the "go create a repo in the browser first" step. - User picks a branch via
gitPont.branches(of:), defaulting to the repository default branch. - GitFolder stores
repoUrlfromGitRepository.reference.cloneHTTPSURL, plusconnectionIDandbranch.
Repository access check (testRepositoryAccess) uses gitPont.repository(_:) and verifies permissions.canPush.
Sync Engine Integration
Before running remote Git commands, resolve a fresh credential context (contexts are single-use; OAuth tokens may have been refreshed since the last run):
let context = try await gitPont.gitCredentialContext(
forRemoteURL: URL(string: folder.repoUrl)!,
preferredConnectionID: folder.connectionID
)
Then use:
git(context.argumentsPrefix + ["pull", "--rebase", "origin", folder.branch], environment: context.environment)
git(context.argumentsPrefix + ["push", "-u", "origin", folder.branch], environment: context.environment)
Error handling in the sync loop:
.missingConnection/.authenticationFailed→ mark the folder as needing reconnect; do not retry until the user acts..ambiguousConnection→ prompt once, store the chosenconnectionIDon the folder..rateLimited(retryAfter:)→ skip this cycle and delay the next sync by at leastretryAfter.
Settings UI
Replace "GitHub" settings with "Connections":
Connections
├─ GitHub
├─ GitLab.com
├─ Self-hosted GitLab
├─ Codeberg
├─ Forgejo
└─ Gitea
Folder setup:
Local folder
Connection
Repository (pick existing or create new)
Branch
Sync interval
Migration
Existing GitFolder installs may have:
- provider:
github - authMode:
github_token - GitHub token in Keychain account
github-token
Migration should:
- Detect existing GitHub token.
- Validate it by loading the account (
gitPont.addConnectiondoes this) and create a GitPont GitHub connection. - Move or duplicate the token into GitPont credential storage.
- Update folders to reference the new
connectionID. - Keep the old token until migration succeeds; delete it only after a successful sync through the new path.
If validation fails (revoked/expired token), keep the old state, mark affected folders as needing reconnect, and show the connection flow.
SSH
Do not route SSH through GitPont v1.
GitFolder may keep its existing advanced SSH mode separately, but the default flow should be provider account connections over HTTPS.