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:

  1. User pastes a GitHub/GitLab/Forgejo/Gitea/Codeberg file URL.
  2. Lezin calls gitPont.openFile(from:). URL ambiguity (slashed branch names) is resolved inside git-pont; Lezin never sees it.
  3. If public read works, open immediately.
  4. If .authenticationRequired or .missingConnection, show the connection flow, then retry.
  5. Store the loaded GitRemoteFile.reference and version with the document.
  6. Also fetch gitPont.repository(file.reference.repository) and keep permissions — the save UI depends on it.

Paste-a-URL is a power-user flow. A picker is friendlier:

  1. User picks a connection.
  2. gitPont.repositories(connectionID:) → repo list (truncated flag shown as "showing first 3000").
  3. gitPont.branches(of:) → branch choice, default branch preselected.
  4. gitPont.listDirectory(_:) → file tree; opening a file calls readFile.

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:

  1. User saves.
  2. If document source is remote, show commit dialog.
  3. Lezin builds a GitChangeSubmission and calls gitPont.submitChange(_:):
    • direct commit → .directCommit
    • new branch + PR → .branchAndPullRequest(...)
    • no push access → .forkAndPullRequest(...)
    • or simply .automatic(...) and let git-pont decide
  4. On success, update the stored remote version from GitChangeResult.commit.newVersion, update the stored reference to usedRepository/usedBranch (it may now point at a fork branch), clear dirty state, and offer to open pullRequest.webURL when one was created.
  5. On .conflict, show conflict UI and do not overwrite.
  6. 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 .branchAndPullRequest submission 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

  1. User picks a local folder.
  2. User picks a connection (or creates one).
  3. User picks an existing repository via gitPont.repositories(connectionID:)or creates a new one via gitPont.createRepository(_:connectionID:) (name defaulted from the folder name, private by default). This removes the "go create a repo in the browser first" step.
  4. User picks a branch via gitPont.branches(of:), defaulting to the repository default branch.
  5. GitFolder stores repoUrl from GitRepository.reference.cloneHTTPSURL, plus connectionID and branch.

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 chosen connectionID on the folder.
  • .rateLimited(retryAfter:) → skip this cycle and delay the next sync by at least retryAfter.

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:

  1. Detect existing GitHub token.
  2. Validate it by loading the account (gitPont.addConnection does this) and create a GitPont GitHub connection.
  3. Move or duplicate the token into GitPont credential storage.
  4. Update folders to reference the new connectionID.
  5. 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.