Save and restore per-branch Xcode breakpoints automatically.
When you switch git branches, Xcode breakpoints stay anchored to line numbers from the wrong version of your code. xmark fixes this by hooking into git checkout to save your breakpoints before you leave a branch and restore them when you return.
Xcode stores breakpoints in a file called Breakpoints_v2.xcbkptlist inside your project's xcuserdata directory. xmark copies that file in and out of ~/.xmark/ keyed by repo and branch name. No Xcode plugin, no LLDB scripting — just file copies triggered by a git hook.
The breakpoint file location depends on your Xcode version:
| Xcode version | Breakpoint file path |
|---|---|
| Xcode 26+ | MyApp.xcodeproj/xcuserdata/<user>.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist |
| Xcode 15 and earlier | MyApp.xcodeproj/xcuserdata/<user>.xcuserdatad/Breakpoints_v2.xcbkptlist |
xmark detects which path is in use automatically — it checks for the xcdebugger/ path first and falls back to the legacy path if it doesn't exist.
Because Xcode loads breakpoints at project-open time and does not watch the file for external changes, xmark uses AppleScript to close and reopen your project after each restore. This makes the new breakpoints appear immediately without any manual steps.
- macOS 13 or later
- Xcode with Command Line Tools
- Git
First, make sure xcode-select points at your Xcode installation, not the standalone Command Line Tools:
xcode-select -pIf the output is /Library/Developer/CommandLineTools, you need to switch it to Xcode (this is a one-time step):
sudo xcode-select --switch /Applications/Xcode.appReplace Xcode.app with the name of your installed Xcode if it differs (e.g. Xcode_26.3.app). You can see what's in /Applications with:
ls /Applications | grep XcodeOnce xcode-select points at Xcode, build normally:
git clone https://github.com/coveloper/xmark.git
cd xmark
swift build -c release
cp .build/release/xmark /usr/local/bin/xmarkVerify it worked:
xmark --version
# 0.1.0There are two parts to getting xmark running: a one-time global step, and a per-repo setup step.
Run this from the root of your git repo (the same directory that contains your .xcworkspace or .xcodeproj):
cd ~/Developer/MyApp
xmark config --set project=MyApp.xcworkspaceIf your repo contains exactly one .xcworkspace or .xcodeproj at the root, you can skip this step — xmark will find it automatically.
xmark setupThat's it. From this point on, every git checkout automatically saves your current branch's breakpoints and restores the new branch's breakpoints.
This section walks through a complete example so you can verify everything is working before you rely on it.
Set a few breakpoints in your code on your current branch (main or whatever you're on). Make them distinctive — for example, put one on a specific line in your AppDelegate or main view.
xmark save
# xmark: Breakpoints saved for branch 'main'.git checkout feature/my-featureIf the hook is installed, xmark runs automatically at this point — it saves your main breakpoints, restores any saved breakpoints for feature/my-feature, and reloads the Xcode project so the change takes effect immediately. Xcode will close and reopen your project; this is normal.
If feature/my-feature has no saved breakpoints yet, xmark clears the breakpoint file (the default behavior) or leaves it alone, depending on your onEmptyBranch setting.
Your breakpoints from main should be gone. Add some new breakpoints that make sense for this feature branch.
git checkout mainxmark automatically saves feature/my-feature's breakpoints, restores main's breakpoints, and reloads Xcode. Your original breakpoints should be back exactly where you left them.
xmark list
# Saved breakpoints for MyApp (origin: github.com/you/MyApp):
#
# main (2 minutes ago)
# feature/my-feature (just now)Installs a post-checkout git hook in the current repo and updates .gitignore with targeted entries for Breakpoints_v2.xcbkptlist and .xmark.
xmark setupThe gitignore entries are version-aware: xmark detects your installed Xcode and adds the appropriate path pattern (Xcode 16+ uses xcdebugger/Breakpoints_v2.xcbkptlist; earlier versions use the legacy path). If xcodebuild is unavailable, both patterns are added. Re-running xmark setup is safe — it won't add duplicate entries.
If a post-checkout hook already exists (from Lefthook, Husky, etc.), xmark will not overwrite it. Instead, it prints the line you need to add manually:
xmark setup: A post-checkout hook already exists at .git/hooks/post-checkout.
Add the following line to your existing hook to enable xmark:
xmark _hook post-checkout "$1" "$2" "$3"
Saves the current breakpoint file as a snapshot for the current branch.
xmark saveSave as a specific branch name:
xmark save --branch feature/my-featureRestores the saved breakpoint snapshot for the current branch.
xmark restoreRestore from a specific branch's snapshot:
xmark restore --branch mainIf no snapshot exists for the branch, xmark applies your onEmptyBranch policy (see Configuration below).
If Xcode is open, xmark automatically closes and reopens your project so the restored breakpoints take effect immediately. Any active debug session will be terminated — this is expected when switching branches.
Shows all saved breakpoint snapshots for the current repo.
xmark list
# Saved breakpoints for MyApp (origin: github.com/you/MyApp):
#
# main (3 days ago)
# feature/login (2 hours ago)
# bugfix/crash-on-launch (yesterday)Removes the saved snapshot for a branch.
xmark delete feature/old-branchDisplays or sets per-repo configuration.
Show current config:
xmark config
# Config at /path/to/repo/.xmark:
#
# {
# "onEmptyBranch" : "clear",
# "project" : "MyApp.xcworkspace"
# }Set a value:
xmark config --set project=MyApp.xcworkspace
xmark config --set onEmptyBranch=preserveThe .xmark file at your repo root controls per-repo behaviour. It is created by xmark config --set and contains machine-specific settings. xmark setup adds it to .gitignore automatically.
| Key | Values | Default | Description |
|---|---|---|---|
project |
filename | auto-detect | The .xcworkspace or .xcodeproj to use. Required if your repo root contains more than one. |
onEmptyBranch |
clear / preserve |
clear |
What to do when switching to a branch with no saved breakpoints. clear writes an empty breakpoint file. preserve leaves the previous branch's breakpoints in place. |
clear (default) — Recommended for most workflows. When you switch to a fresh branch, you start with a clean slate. This prevents stale breakpoints from a different branch cluttering your new context.
preserve — Useful if you want breakpoints to carry forward when starting work on a new branch from an existing one. For example, if you branch off main and want to keep debugging in the same place you were.
xmark stores snapshots in ~/.xmark/, organized by repo and branch:
~/.xmark/
<repo-identifier>/ # SHA-256 of the git remote URL (or repo path if no remote)
main.xcbkptlist
feature__login.xcbkptlist # '/' in branch names is stored as '__'
bugfix__crash.xcbkptlist
Snapshots are plain XML plist files — the same format Xcode uses. You can inspect them with any text editor.
To remove all stored snapshots for a repo, delete its directory from ~/.xmark/. To wipe everything:
rm -rf ~/.xmark/If you already have a post-checkout hook (common with Lefthook, Husky, or custom scripts), xmark setup will detect it and print the snippet to add manually rather than overwriting your hook:
xmark setup
# xmark setup: A post-checkout hook already exists at .git/hooks/post-checkout.
# Add the following line to your existing hook to enable xmark:
#
# xmark _hook post-checkout "$1" "$2" "$3"Open .git/hooks/post-checkout in your editor and add that line.
You can call xmark directly from the LLDB console using the ! shell escape prefix — no need to leave the debugger:
(lldb) !xmark save
(lldb) !xmark list
This is handy for capturing a precise breakpoint state before a risky rebase or experiment.
"No .xcworkspace or .xcodeproj found in the repo root"
Your repo root has either no Xcode project, or more than one. Tell xmark which to use:
xmark config --set project=MyApp.xcworkspace"No breakpoint file found"
Xcode hasn't created the breakpoint file yet. Open Xcode, set at least one breakpoint, then run xmark save.
Breakpoints didn't restore after switching branches
- Confirm the hook is installed:
cat .git/hooks/post-checkout - Confirm
xmarkis in your PATH:which xmark - Try a manual restore:
xmark restore— this also reloads Xcode automatically if it's open - If Xcode didn't reopen automatically, close and reopen it manually
Branch switched but nothing happened
The post-checkout hook only fires on branch switches (flag $3 == 1), not on individual file checkouts. Confirm you're doing a full git checkout <branch>, not git checkout -- <file>.
"A post-checkout hook already exists"
See Working with existing git hooks above.
If your repo contains multiple .xcworkspace or .xcodeproj files, xmark requires explicit configuration:
xmark config --set project=MyApp.xcworkspaceWithout this, xmark exits with an error listing the candidates it found.
Remove the binary:
rm /usr/local/bin/xmarkRemove the git hook from any repo where you installed it:
rm .git/hooks/post-checkoutRemove all stored snapshots:
rm -rf ~/.xmark/MIT. See LICENSE.