{"id":51052377,"url":"https://github.com/erbilnas/git-persona","last_synced_at":"2026-06-22T18:02:57.842Z","repository":{"id":354862096,"uuid":"1225581708","full_name":"erbilnas/git-persona","owner":"erbilnas","description":"GitPersona is a minimal macOS menu bar utility for switching Git commit identities per repository or globally. ","archived":false,"fork":false,"pushed_at":"2026-06-17T08:21:21.000Z","size":2959,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T10:15:25.752Z","etag":null,"topics":["developer-tools","dmg","git","gitconfig","macos","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erbilnas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-30T12:31:50.000Z","updated_at":"2026-06-17T08:19:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/erbilnas/git-persona","commit_stats":null,"previous_names":["erbilnas/git-persona"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/erbilnas/git-persona","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erbilnas%2Fgit-persona","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erbilnas%2Fgit-persona/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erbilnas%2Fgit-persona/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erbilnas%2Fgit-persona/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erbilnas","download_url":"https://codeload.github.com/erbilnas/git-persona/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erbilnas%2Fgit-persona/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34659898,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["developer-tools","dmg","git","gitconfig","macos","swift"],"created_at":"2026-06-22T18:02:57.066Z","updated_at":"2026-06-22T18:02:57.836Z","avatar_url":"https://github.com/erbilnas.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GitPersona\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"docs/logo.svg\" alt=\"GitPersona logo\" width=\"128\" height=\"128\"\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/erbilnas/git-persona/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/erbilnas/git-persona?label=release\" alt=\"Latest GitHub release\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/erbilnas/git-persona/actions/workflows/build-dmg.yml\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/erbilnas/git-persona/build-dmg.yml?branch=main\u0026label=build\" alt=\"Build DMG workflow status\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nGitPersona is a macOS menu bar app that switches Git author identity—`user.name`, `user.email`, and optional `user.signingkey`—per repository (local config) or globally (`~/.gitconfig`). Personas are named presets you pick from the menu bar window.\n\n## Requirements\n\n| Requirement | Notes |\n|-------------|--------|\n| macOS | **26.0** or later (matches deployment target and Liquid Glass APIs) |\n| Xcode | **26+** with Swift 6 and macOS 26 SDK, to build from source |\n| Git | On your `PATH` (e.g. `/usr/bin/git` or Command Line Tools) |\n\n## Install\n\nGitPersona is **not** on the Mac App Store. Use GitHub releases, CI artifacts, or a local build.\n\n### Releases (recommended)\n\n1. Open [Releases](https://github.com/erbilnas/git-persona/releases).\n2. Download `GitPersona-x.y.z.dmg` from a tagged release (e.g. `v1.0.0`). Versioned releases attach the DMG.\n3. Open the DMG and drag **GitPersona** into **Applications**.\n4. Launch from Applications. The app lives in the **menu bar** only (`LSUIElement`: no Dock icon).\n\n### CI artifacts\n\nPushes to `main` / `master` and pull requests run [Build DMG](.github/workflows/build-dmg.yml) and upload a `GitPersona-macos` workflow artifact (DMG name still includes the project version). These are **not** GitHub Releases—use them for unreleased builds.\n\n### Build DMG locally\n\nOn a Mac with Xcode:\n\n```bash\n./scripts/build-dmg.sh\n```\n\nOutput: `dist/GitPersona-\u003cversion\u003e.dmg` plus `dist/GitPersona.dmg` → symlink to that file.\n\nGatekeeper may prompt on first open for unsigned builds. For distribution, use Developer ID signing and notarization (see [Building and releasing](#building-and-releasing)).\n\n## Usage\n\n1. Click the menu bar icon to open the GitPersona window.\n2. Open **Settings** (gear) to create **personas**—label, `user.name`, `user.email`, optional signing key and notes. You can enable **Launch at login** there (macOS Login Items / SMAppService as applicable).\n3. Under **Repository**, use **Choose…** to pick any folder inside a clone, or pick a path from **Recents** (last used repo roots are stored).\n4. Select a persona, then **Apply to repo** (writes `\u003crepo\u003e/.git/config`) or **Apply globally** (runs `git config --global`).\n\nThe main window shows read-only previews of **local** and **global** identity from `git config`.\n\n### Limitations\n\n- There is no shell “current directory”; you always pick the repo (or a recent).\n- Only Git config identity fields are changed—not SSH keys, remotes, or hosting accounts.\n\n## How it fits together\n\n```mermaid\nflowchart LR\n  subgraph ui [UI]\n    MB[MenuBarExtra]\n    Pop[Main window]\n    Set[Settings]\n  end\n  subgraph domain [App]\n    PS[PersonaStore]\n    GA[GitConfigApplier]\n    RM[RepoResolver]\n  end\n  subgraph io [IO]\n    Git[git process]\n    Store[(Encrypted store)]\n  end\n  MB --\u003e Pop\n  MB --\u003e Set\n  Pop --\u003e PS\n  Set --\u003e PS\n  Pop --\u003e RM\n  Pop --\u003e GA\n  GA --\u003e Git\n  RM --\u003e Git\n  PS --\u003e Store\n```\n\n| Component | Role |\n|-----------|------|\n| **PersonaStore** | Loads/saves encrypted `personas.store` (AES-GCM via **PersonaVault**; key in Keychain) under `~/Library/Application Support/dev.gitpersona.app/`. Migrates legacy plaintext `personas.json` once, then removes it. |\n| **RepoResolver** | Runs `git rev-parse --show-toplevel` on the chosen directory to resolve the repo root. |\n| **GitConfigApplier** | Reads/writes identity with `git config` (`--local` / `--global`); finds `git` via `/usr/bin/git` or `PATH`. |\n\n### Encrypted store shape\n\nDecrypted JSON looks like:\n\n```json\n{\n  \"version\": 1,\n  \"personas\": [\n    {\n      \"id\": \"UUID\",\n      \"displayName\": \"Work\",\n      \"gitUserName\": \"Ada Lovelace\",\n      \"gitUserEmail\": \"ada@company.example\",\n      \"signingKey\": null,\n      \"notes\": null\n    }\n  ],\n  \"lastRepoPaths\": [\"/path/to/repo\"]\n}\n```\n\nIf decryption fails, the app moves the blob to `personas.store.corrupt-\u003ctimestamp\u003e` and may fall back to `personas.json` when present.\n\n### UI notes (Liquid Glass)\n\nOn macOS 26+, chrome uses `.glassEffect` where appropriate (e.g. popover header, primary action bar via `GlassChrome`). Older behavior uses material fallbacks. Dense lists and forms stay on standard grouped styling.\n\n## System design\n\n```mermaid\nflowchart TB\n  GP[GitPersona.app]\n  FS[(Application Support / personas.store)]\n  GC[\"~/.gitconfig\"]\n  LC[\"repo/.git/config\"]\n  GIT[git]\n  GP --\u003e FS\n  GP --\u003e|\"Apply globally\"| GIT\n  GIT --\u003e GC\n  GP --\u003e|\"Apply to repo\"| GIT\n  GIT --\u003e LC\n```\n\nThe app does not open network connections; data stays on your Mac.\n\n## Building and releasing\n\n### Versioning with Changesets\n\nReleases follow semver using [Changesets](https://github.com/changesets/changesets). `package.json` holds the version; `npm run version-packages` syncs into [GitPersona/Version.xcconfig](GitPersona/Version.xcconfig) (`MARKETING_VERSION` and bumped `CURRENT_PROJECT_VERSION`).\n\n1. `npm install`\n2. After user-visible changes: `npm run changeset`, choose bump level, commit the new file under `.changeset/` with your PR.\n3. Merge to `main`. [changesets.yml](.github/workflows/changesets.yml) opens a **Version packages** PR (changelog + version + `Version.xcconfig`).\n4. Merge **Version packages**. The workflow runs `npm run release`, creating and pushing tag `v*.*.*`.\n5. [versioned-release-after-changesets.yml](.github/workflows/versioned-release-after-changesets.yml) builds the DMG and publishes the GitHub Release (tags from `GITHUB_TOKEN` may not trigger `on.push.tags`, so this follow-up attaches the asset).\n\n**Manual tag:** You can still `git tag v1.2.3 \u0026\u0026 git push origin v1.2.3` if `MARKETING_VERSION` already matches; Changesets keeps `CHANGELOG.md` aligned.\n\n`.changeset/config.json` uses `\"baseBranch\": \"main\"`. If your default branch is only `master`, set `baseBranch` to `master`.\n\n### CI\n\n[build-dmg.yml](.github/workflows/build-dmg.yml) uses the `macos-26` runner (Xcode 26 / macOS 26 SDK), matching the app target. Optional secrets for signed DMGs:\n\n| Secret | Purpose |\n|--------|---------|\n| `SIGN_IDENTITY` | Developer ID Application string for `codesign` |\n| `NOTARY_PROFILE` | `notarytool` keychain profile name |\n\nPass them as workflow `env` only when configured.\n\n### Xcode build\n\n```bash\ncd git-persona\nxcodebuild -scheme GitPersona -configuration Release \\\n  -derivedDataPath ./build/DerivedDataRelease build\n```\n\nApp bundle: `build/DerivedDataRelease/Build/Products/Release/GitPersona.app`\n\n### DMG script and notarization\n\n```bash\n./scripts/build-dmg.sh\n```\n\nOptional env: `SIGN_IDENTITY`, `NOTARY_PROFILE` (same meaning as in CI). Typical distribution: Release build with hardened runtime → codesign app → DMG → sign DMG → `notarytool submit --wait` → `stapler staple`. See Apple’s [Notarizing macOS software before distribution](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution).\n\n## Privacy and security\n\n- No analytics or outbound network from the app.\n- Writes occur when you apply: `~/.git/config` or `~/.gitconfig`, plus the Application Support store; encryption key in Keychain.\n- **App Sandbox is off** so `git` can update configs without the friction common to sandboxed file access.\n\n## Repository layout\n\n```\ngit-persona/\n├── .changeset/\n├── .github/workflows/          # build-dmg, changesets, versioned-release-after-changesets\n├── GitPersona.xcodeproj/\n├── GitPersona/                 # Swift sources (app, settings, store, git helpers, glass UI)\n├── docs/logo.svg\n├── scripts/                    # build-dmg.sh, version sync, release tag helpers\n├── package.json\n└── README.md\n```\n\n## Troubleshooting\n\n| Symptom | What to try |\n|---------|-------------|\n| Git executable not found | `xcode-select --install` or ensure `git` is on `PATH`. |\n| Repo / apply error | Pick a folder inside a real Git work tree (`git rev-parse` works there). |\n| Changesets “Version packages” PR never opens | Ensure a `.changeset/*.md` exists on `main` and the workflow has `contents: write` and `pull-requests: write`. |\n\n## License\n\nNo license file is included by default; add one if you publish the repository publicly.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferbilnas%2Fgit-persona","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferbilnas%2Fgit-persona","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferbilnas%2Fgit-persona/lists"}