Skip to content

fix(installer): preserve existing oh-my-zsh installation - #188

Open
eezzekl wants to merge 1 commit into
Gentleman-Programming:mainfrom
eezzekl:fix/omz-preserve-existing-install
Open

fix(installer): preserve existing oh-my-zsh installation#188
eezzekl wants to merge 1 commit into
Gentleman-Programming:mainfrom
eezzekl:fix/omz-preserve-existing-install

Conversation

@eezzekl

@eezzekl eezzekl commented Aug 14, 2026

Copy link
Copy Markdown

Closes #187

Summary

  • The installer copied the vendored GentlemanZsh/.oh-my-zsh snapshot over ~/.oh-my-zsh unconditionally, which dirties the tracked files of a real Oh My Zsh clone and makes omz update fail on the autostash pop.
  • Oh My Zsh is now installed through its official installer only when ~/.oh-my-zsh is missing, and an existing installation is never written into.
  • The vendored copy is removed, since it carried no project-owned code and Oh My Zsh manages its own checkout.

Changes

File Change
installer/internal/system/exec.go New PathExists helper next to EnsureDir. Resolves symlinks on purpose, so a symlinked ~/.oh-my-zsh counts as installed.
installer/internal/tui/installer.go New shouldInstallOhMyZsh; the system.CopyDir call in case "zsh" is replaced by the guard plus the official installer.
installer/internal/tui/installation_steps_test.go TestShouldInstallOhMyZsh covering missing, real-directory and symlinked-directory states.
docs/manual-installation.md cp -rf GentlemanZsh/.oh-my-zsh ~/ replaced with a guarded official-installer invocation.
GentlemanZsh/.oh-my-zsh Removed — 1021 files, ~11 MB.

The installer call is:

ZSH=<home>/.oh-my-zsh RUNZSH=no CHSH=no KEEP_ZSHRC=yes \
  sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

KEEP_ZSHRC=yes is what preserves the .zshrc copied a few lines above; RUNZSH/CHSH keep it non-interactive. All three are documented in Oh My Zsh's tools/install.sh. This introduces no new dependency — the installer already clones tpm (installer.go:935), Alacritty, and runs the Homebrew installer via curl.

Why the vendored copy is safe to remove

Every bundled file was hashed and checked against Oh My Zsh's own history:

h=$(git hash-object "GentlemanZsh/.oh-my-zsh/$f")
git -C ~/.oh-my-zsh cat-file -e "$h"   # exists => unmodified upstream file

1015 of 1019 files matched an identical upstream blob. The remaining 4 are plugins/fig/ and plugins/rbfu/, also upstream files, whose blobs are missing only because upstream deleted those plugins. rg -i gentleman over the tree returns nothing, and the directory has 3 commits in the entire repository history, all imports. There is nothing to migrate into $ZSH_CUSTOM.

Worth noting for anyone who resolved these conflicts by hand: the bundle_install() function in the bundled plugins/bundler/bundler.plugin.zsh is deprecated Oh My Zsh code, not a Gentleman.Dots function. Upstream replaced it with alias bi="bundle install".

Test plan

  • go test ./... -skip Golden -count=1 — system 5.7s, tui 71.8s, trainer OK
  • go build ./cmd/gentleman-installer and --help / --version smoke test
  • go vet ./... clean
  • New unit test covers both branches of the guard without requiring network access
  • No .golden snapshot references Oh My Zsh, so the macOS job is unaffected
  • Every E2E Dockerfile already installs curl and git

Note on E2E

e2e_test.sh runs --shell=zsh three times and its cleanup never removes $HOME/.oh-my-zsh, so the first run exercises the install path and the later runs exercise the guard — both branches get covered.

One deliberate trade-off: the zsh E2E now needs to reach GitHub to install Oh My Zsh, where it previously only copied files. I did not soften the failure into a warning, because .zshrc runs source $ZSH/oh-my-zsh.sh — silently skipping the install would leave the user with a shell that breaks at startup. Failing the step is the safer behavior, but it is a new network dependency in that job and worth knowing about.

The installer copied a vendored oh-my-zsh snapshot over ~/.oh-my-zsh
unconditionally. On a real oh-my-zsh git clone that dirties tracked
files, so `omz update` (git pull --rebase with autostash) fails on the
stash pop. On a clean system the snapshot has no .git, leaving an
oh-my-zsh that can never update and fails silently.

Install oh-my-zsh through its official installer only when ~/.oh-my-zsh
is missing, and never write into an existing one. KEEP_ZSHRC=yes keeps
the .zshrc the installer copies, RUNZSH/CHSH keep it non-interactive.

The vendored copy carried no project-owned code: 1015 of its 1019 files
matched upstream blobs verbatim and the rest were upstream-deleted
plugins, so it is removed instead of kept in sync.

Refs Gentleman-Programming#187
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Installer overwrites ~/.oh-my-zsh with a static copy, breaking omz update

1 participant