fix: detect local dotfile changes and merge profile+global dotfiles#8
Merged
fix: detect local dotfile changes and merge profile+global dotfiles#8
Conversation
6 tasks
… dotfiles Two bugs that prevented locally-edited dotfiles from being pushed: 1. When a dotfile has no sync history (newly added to config) and local differs from remote, is_true_conflict() returned true. In daemon mode, conflicts can't be resolved interactively, so the file was skipped in both import AND export — stuck indefinitely. Fix: treat no-sync-history as "local is authoritative" (not a conflict), letting the export step push to establish a baseline. 2. effective_dotfiles() returned ONLY profile dotfiles when a profile existed, silently excluding any dotfiles only in the global [dotfiles] config. Fix: merge profile entries with global entries (profile takes priority for duplicates). Closes #7
- Fix test_detect_conflict_returns_some_when_differ_no_history: was asserting is_some() but fix makes it return None. Renamed and updated. - Add test for profile-overrides-global semantics (create_if_missing) - Add test for disjoint profile/global dotfile sets (union behavior)
When a dotfile has create_if_missing=true and no sync history, skip
conflict detection and let remote content win. This handles the case
where an app creates a default file before tether runs (e.g. Claude Code
writing {} to settings.json on startup).
For files without create_if_missing, local remains authoritative when
there's no sync history (from the is_true_conflict fix).
Incorporates the fix from PR #6.
KeepLocal resolution wasn't calling remove_conflict, causing the user to be re-prompted on every sync until the export step ran.
e545b25 to
70b67eb
Compare
- Extract backup_and_write_dotfile helper to remove two identical backup+ write+preserve_executable_bit blocks in decrypt_from_repo - detect_conflict now takes pre-computed local content and hashes so the caller can read and hash the file once instead of twice per sync - effective_dirs merges profile + global dirs like effective_dotfiles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes four bugs that prevent dotfile sync from working correctly when files are added to the config or edited locally. Closes #7. Supersedes #6.
Root Cause Analysis
Bug 1: No-sync-history treated as conflict (
conflict.rs)When a dotfile has no sync history (
last_synced_hashisNone) and local differs from remote,is_true_conflict()returnstrue. In daemon mode (non-interactive), conflicts can't be resolved, so the file is skipped in both import AND export — stuck indefinitely.Fix: When
last_synced_hashisNone, returnfalse— local is authoritative and the export step will push to establish a baseline.Bug 2: Remote content not applied on first sync for
create_if_missingfiles (sync.rs)When a dotfile with
create_if_missing = trueis synced to a new machine, an app may create a default file (e.g. Claude Code writes{}tosettings.json) before tether runs. Tether sees the locally-created default as a "local edit" and refuses to overwrite it with the remote version.Fix: When
last_synced_hashisNoneANDcreate_if_missingistrue, bypass conflict detection entirely and let remote win. The local default is backed up before overwriting.Bug 3: Profile dotfiles override global dotfiles (
config.rs)effective_dotfiles()returns only profile dotfiles when a profile exists, silently excluding any dotfiles defined only in the global[dotfiles].filesconfig.Fix: Merge profile dotfiles with global dotfiles. Profile entries take priority for duplicates (same path).
Bug 4: KeepLocal resolution doesn't clear conflict state (
sync.rs)When the user chooses "Keep Local" during interactive conflict resolution, the conflict isn't removed from state. It re-prompts on every subsequent sync.
Fix: Call
conflict_state.remove_conflict(&file)in theKeepLocalbranch.Combined first-sync behavior
create_if_missinglast_synced_hashtrueNonefalseNoneSome(hash)Test plan
cargo clippy --all-targets -- -D warningspassescargo test— 216 tests passcreate_if_missingdotfiles on first run