The problem: two Macs, one brain
I run a Mac Studio at the desk and a MacBook Air everywhere else. I ship web stuff from both. I used to think that was luxurious. Mostly it was chaos.
I had branches that only existed on one machine. "Final-final" folders. Half-finished experiments. A random zip file on the Desktop that was probably important. Or maybe a screenshot. I did not dare delete it.
Sync tools made it worse. Dropbox, iCloud, Syncthing. They all tried to be smart. I do not want smart. I want explicit.
So I moved everything through Git and put Tower on top of it. That changed the workflow from "hope this syncs" to a boring, predictable routine.
The core rule: Git is the source of truth
The whole setup hangs on one rule.
Nothing is real until it is committed and pushed.
No local-only work. No "I will push later". No half-baked thing silently living on the MacBook but not the Studio. If I cannot see it in Tower, it does not exist.
That sounds strict. It is. That is why it works.
The hardware split: Studio vs Air
I treat the machines differently.
- Mac Studio: Deep work, heavy tools, long sessions. Multiple monitors, proper keyboard, the whole cliché.
- MacBook Air: Short bursts. Trains, sofa, kitchen table, client visits.
They share the same repos. Same code. Same CLIs. Same dotfiles. I want to be able to close the lid on one machine, walk to the other, open Tower, and just continue.
The only way that works is if the workflow makes conflicts boring and rare.
Step zero: identical project layout
I keep the same project layout on both machines. No creativity here.
/Users/richard/Code/
client-a-project
client-b-project
personal-site
random-lab
On a new machine I clone everything into ~/Code. No custom locations, no smart folders. I want muscle memory.
Dotfiles and global config live in one repo too. That repo is also managed with Tower. New Mac, one clone, one script, I am home.
Why Tower, not just CLI?
I like Git on the command line. Still, I use Tower every day.
Here is why.
- Visual sanity check. Before I switch machines I glance at Tower. No uncommitted changes. Nothing stashed and forgotten. No mystery branches.
- History as a timeline. I can see exactly where the MacBook edits stopped and the Studio edits begin. That matters when I come back to a project after a week.
- Safe, boring operations. Rebasing, squashing, cherry-picking. I can do that in the CLI, but in Tower I am less likely to fat-finger a branch name and ruin my evening.
On both machines, Tower is almost always open. It sits next to my editor and the browser.
The golden routine: before I leave a machine
This is the core of the workflow. I repeat the same sequence every time I stop coding on a machine, even if I think I will be back in ten minutes.
- Make sure tests build or run, or at least the app runs. No broken state.
- Commit everything meaningful. Small, focused commits. No giant "stuff" commit.
- Push the branch.
That is it. No exceptions. If something is truly experimental, I still commit it. Worst case I delete it later.
The important part is the push. I treat it like locking the door when I leave the house. I might come back in one minute. I still lock the door.
Branch strategy that keeps things boring
I do not use a complicated branching model. Complexity is where conflicts breed.
- One main branch.
mainormaster, depending on the repo history. - Short-lived feature branches.
feature/navbar-refactor,bugfix/modal-escape, that kind of thing.
Here is the key detail.
I never edit the same feature branch on both machines at the same time without pushing and pulling in between.
If I start feature/navbar-refactor on the Studio, I commit and push before I touch that branch on the Air. If I forget, the Air simply does not get to work on that branch.
Sometimes I make a second branch instead, from remote main, on the other machine. For example:
feature/navbar-refactor
feature/navbar-refactor-air
Later I clean this up with a rebase or a squash merge in Tower. That might sound messy, but it keeps conflicts small and local. I would rather reconcile two branches deliberately than have two machines silently fight over a shared one.
Daily flow: starting on machine A, continuing on B
Here is what an actual day looks like.
Morning on the Mac Studio
- Open Tower and my editor.
- Pull
mainand any active feature branches. - Create or continue a feature branch.
- Commit throughout the morning. Small chunks, meaningful messages.
Before lunch, when I know I might switch to the MacBook, I do a small ritual.
- Final commit for the morning.
- Run the app, quick smoke test.
- Push the branch in Tower.
- Check Tower: no local changes pending, no unpushed commits.
Then I walk away from the desk.
Afternoon on the MacBook Air
On the Air, first thing:
- Open Tower.
- Pull all remotes.
- Checkout the same feature branch I pushed from the Studio.
Now I am exactly where I left off, minus the extra monitor. No file sync needed, no guessing which copy is newer, no "conflicted copy" files lurking in Finder.
When I am done on the Air, the same rule applies. Commit. Push. Verify in Tower that everything is clean.
Handling uncommitted experiments
Sometimes I play with something that I do not want in history yet. A throwaway refactor. A console.log storm. A nasty debug branch with broken types.
There are three options I use.
- WIP commit on a dedicated branch. I create
wip/login-flow, commit the ugly stuff, and push. I delete the branch later. - Stash in Tower, but only if I stay on the same machine. Stashes do not sync by default. I treat them as local-only scratchpads.
- Commit and immediately mark as TODO. Sometimes I leave a loud
// TODO RICHARDand a clear commit message like "WIP: broken login validation". That keeps future me honest.
If I might need that code on the other machine, I do not stash it. I make a branch and push. Stashes and two machines do not mix well.
Conflicts: how often and how bad
Since I tightened this routine, I see maybe one real merge conflict every few weeks. Usually on a busy client repo with multiple people pushing.
On my own stuff, conflicts are almost always my fault for breaking my own rules. Same branch, both machines, not pushing in between. Tower at least makes the fix visible and relatively calm.
When conflicts do happen, I resolve them on the machine with the bigger screen. That is usually the Studio. Tower's diff view is better when you are not squinting.
The Tower views I live in
I probably use 10 percent of Tower's feature list on most days. But that 10 percent, I use constantly.
- Working Copy view. I can see exactly what changed before any commit. No accidental garbage sneaks in.
- History graph. The little branch graph tells me straight away whether I forgot to push something from one machine.
- Branches sidebar. I keep this tidy. No month-old branches sitting there unloved.
- Pull with rebase. My default. I like a clean linear history more than I like huge merge commits.
If the graph looks weird, I stop and investigate instead of plowing through. That habit alone has saved me plenty of time.
What I do not sync with Git
Not everything lives in Git, obviously. But I am very picky about what crosses machines and how.
- Dotfiles and shell config: Git repo, plus a bootstrap script. Never iCloud.
- Editor config: Mostly in the dotfiles repo, or synced via the editor account (Neovim config in Git, VS Code settings sync if I use it).
- Big assets: Design exports, large videos. These go into a separate storage setup, not the main code repos. I do not want 5 GB screenshots pulled on a train hotspot.
I treat every sync tool as untrusted until proven otherwise. Git with Tower is the only path that matters for code.
Edge cases: offline work and bad habits
Sometimes I am coding on the MacBook in a place with no internet. Plane, train dead zone, rural field watching baseball practice.
The rule adjusts slightly:
- Commit locally as usual.
- Once I get a connection again, I open Tower and push before I open the same repo on the other machine.
The trap is thinking "I will push later" while already editing the same branch on the other Mac. That is how you get conflicts.
Bad habit number two is editing main directly on both machines. I still slip sometimes. So I configured a guard.
On some repos I add a pre-push hook that complains if I try to push commits directly to main from my local user. Tower respects that hook. It forces me back to feature branches.
Why this beats shared folders and magic sync
I tried putting projects in iCloud Drive and letting macOS handle it. It mostly worked, right until it did not.
Files would be in a "waiting to upload" limbo. Some folders would be local on the Studio, but offloaded to the cloud on the Air. Git would choke on partially downloaded objects. I spent more time babysitting than coding.
With Git plus Tower, the sync surface is narrow and predictable.
- I know exactly when something syncs: on push and pull.
- I know exactly what conflicts look like: Git conflicts in files, not mystery duplicates.
- I know exactly where history lives: on the remote, not inside a proprietary sync state.
I prefer boring tools that do one thing in broad daylight instead of five things in the background.
The mental payoff
Most of this post is mechanics. The real benefit is cognitive.
I no longer burn brain cycles on "is this the right version of the project" or "did I leave something only on the laptop". The workflow answers that for me.
If I am on the Studio and I do not see the work in Tower, I know I never pushed it. So I grab the MacBook, push from there, then pull. No detective work.
Conflicts are rare. When they happen, they are small and expected, not a shock from some sync daemon that tried to be clever.
Adapting this to your setup
You do not need my exact stack. Replace Tower with your Git client of choice if you want. The important parts are the rules.
- Identical project paths on both machines.
- Git as the only source of truth for code.
- Always commit and push before leaving a machine.
- Always pull before starting on the other machine.
- A clear branch strategy that does not assume you remember what you did yesterday.
Once that becomes habit, switching between a desktop and a laptop for the same codebase stops being a gamble. It just becomes part of the rhythm.
Two Macs. One workflow. No drama.
Subscribe to my newsletter to get the latest updates and news
Member discussion