Skip to content

Commit 22aca1e

Browse files
committed
always link
1 parent c0ac27d commit 22aca1e

File tree

4 files changed

+51
-36
lines changed

4 files changed

+51
-36
lines changed

internal/component/component.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (m *Manager) installComponent(comp profile.ComponentInfo, forceInstall bool
152152
// Check if component needs install work
153153
needsInstall := forceInstall || !m.stateManager.IsComponentInstalled(comp) || m.stateManager.HasInstallChanged(comp, comp.Component.Install)
154154
hasLinks := len(comp.Component.Link) > 0
155-
needsLinking := hasLinks && (forceInstall || !m.stateManager.IsComponentInstalled(comp) || m.stateManager.HasChangedSince(comp, comp.Component.Link))
155+
// Links always need to be processed - they're fast and ensure files are properly linked
156156

157157
// Check if defaults need updating (only on macOS)
158158
hasDefaults := len(comp.Component.Defaults) > 0 && system.IsMacOS()
@@ -176,8 +176,8 @@ func (m *Manager) installComponent(comp profile.ComponentInfo, forceInstall bool
176176
}
177177
}
178178

179-
// If no install needed and no linking needed and no defaults needed, mark as skipped
180-
if !needsInstall && !needsLinking && !needsDefaults {
179+
// If no install needed and no links and no defaults needed, mark as skipped
180+
if !needsInstall && !hasLinks && !needsDefaults {
181181
result.Skipped = true
182182
return result
183183
}
@@ -213,8 +213,8 @@ func (m *Manager) installComponent(comp profile.ComponentInfo, forceInstall bool
213213
}
214214
}
215215

216-
// Create links only if linking is needed
217-
if needsLinking {
216+
// Create links if component has any links (always run - it's fast and ensures proper linking)
217+
if hasLinks {
218218
linkResults, err := m.linkManager.CreateLinks(comp.Component.Link)
219219
result.LinkResults = linkResults
220220

@@ -292,7 +292,7 @@ func (m *Manager) installComponentWithProgress(comp profile.ComponentInfo, force
292292
// Check if component needs install work
293293
needsInstall := forceInstall || !m.stateManager.IsComponentInstalled(comp) || m.stateManager.HasInstallChanged(comp, comp.Component.Install)
294294
hasLinks := len(comp.Component.Link) > 0
295-
needsLinking := hasLinks && (forceInstall || !m.stateManager.IsComponentInstalled(comp) || m.stateManager.HasChangedSince(comp, comp.Component.Link))
295+
// Links always need to be processed - they're fast and ensure files are properly linked
296296

297297
// Check if defaults need updating (only on macOS)
298298
hasDefaults := len(comp.Component.Defaults) > 0 && system.IsMacOS()
@@ -316,8 +316,8 @@ func (m *Manager) installComponentWithProgress(comp profile.ComponentInfo, force
316316
}
317317
}
318318

319-
// If no install needed and no linking needed and no defaults needed, mark as skipped
320-
if !needsInstall && !needsLinking && !needsDefaults {
319+
// If no install needed and no links and no defaults needed, mark as skipped
320+
if !needsInstall && !hasLinks && !needsDefaults {
321321
result.Skipped = true
322322
progress.CompleteSuccess()
323323
return result
@@ -354,8 +354,8 @@ func (m *Manager) installComponentWithProgress(comp profile.ComponentInfo, force
354354
}
355355
}
356356

357-
// Create links only if linking is needed
358-
if needsLinking {
357+
// Create links if component has any links (always run - it's fast and ensures proper linking)
358+
if hasLinks {
359359
progress.StartLinking()
360360
linkResults, err := m.linkManager.CreateLinks(comp.Component.Link)
361361
result.LinkResults = linkResults

internal/component/component_test.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,18 @@ func TestInstallComponentsForceInstall(t *testing.T) {
215215
t.Fatalf("Second install result count = %v, want 1", len(results2))
216216
}
217217

218-
// The component should be skipped because no changes detected
219-
if !results2[0].Skipped {
220-
t.Errorf("Second install should be skipped when no changes detected. Error: %v, LinkResults: %d",
221-
results2[0].Error, len(results2[0].LinkResults))
218+
// The component should not be skipped because links always run (they're fast and ensure proper linking)
219+
if results2[0].Skipped {
220+
t.Errorf("Second install should not be skipped because links always need to run. Error: %v", results2[0].Error)
221+
}
222+
223+
// But it should have succeeded and run links
224+
if results2[0].Error != nil {
225+
t.Errorf("Second install should succeed but had error: %v", results2[0].Error)
226+
}
227+
228+
if len(results2[0].LinkResults) == 0 {
229+
t.Errorf("Second install should have run links but LinkResults was empty")
222230
}
223231

224232
// Install again with force - should not be skipped
@@ -263,6 +271,11 @@ func TestUninstallRemovedComponents(t *testing.T) {
263271
testComponent := profile.ComponentInfo{
264272
ProfileName: "work",
265273
ComponentName: "removed",
274+
Component: config.Component{
275+
Install: map[string]string{
276+
"test": "echo 'this is a unique install command for removed component'",
277+
},
278+
},
266279
}
267280
manager.stateManager.MarkComponentInstalled(testComponent, "echo", "echo test", map[string]string{})
268281

@@ -385,10 +398,18 @@ func TestInstallComponentWithProgress(t *testing.T) {
385398
t.Errorf("Symlink should exist at %s: %v", targetPath, err)
386399
}
387400

388-
// Test skipping with progress (install again without force)
401+
// Test second install with progress (links should always run)
389402
result2 := manager.installComponentWithProgress(comp, false, progressManager)
390-
if !result2.Skipped {
391-
t.Error("Second install should be skipped")
403+
if result2.Skipped {
404+
t.Error("Second install should not be skipped because links always run")
405+
}
406+
407+
if result2.Error != nil {
408+
t.Errorf("Second install should succeed but had error: %v", result2.Error)
409+
}
410+
411+
if len(result2.LinkResults) == 0 {
412+
t.Error("Second install should have run links but LinkResults was empty")
392413
}
393414

394415
// Test force install with progress

internal/config/config.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,9 @@ func (c *Component) MatchesOS(currentOS string) bool {
184184
return false
185185
}
186186

187-
// ContentHash generates a hash of the component's actual content (install, link, etc.)
188-
// excluding the component name/path. This allows detecting renames/moves.
187+
// ContentHash generates a hash of the component's core functionality
188+
// This focuses on install/uninstall commands and hooks, but excludes links and paths
189+
// to better detect renames where the component functionality is the same
189190
func (c *Component) ContentHash() string {
190191
h := sha256.New()
191192

@@ -213,18 +214,6 @@ func (c *Component) ContentHash() string {
213214
}
214215
}
215216

216-
// Sort and hash link commands
217-
if len(c.Link) > 0 {
218-
var linkKeys []string
219-
for k := range c.Link {
220-
linkKeys = append(linkKeys, k)
221-
}
222-
sort.Strings(linkKeys)
223-
for _, k := range linkKeys {
224-
h.Write([]byte(fmt.Sprintf("link:%s:%s;", k, c.Link[k])))
225-
}
226-
}
227-
228217
// Hash post-install and post-link hooks
229218
if c.PostInstall != "" {
230219
h.Write([]byte(fmt.Sprintf("postinstall:%s;", c.PostInstall)))
@@ -255,5 +244,9 @@ func (c *Component) ContentHash() string {
255244
}
256245
}
257246

247+
// NOTE: We intentionally exclude Link mapping from the hash because
248+
// component moves/renames often involve path changes in links, but
249+
// the core functionality (install commands, hooks) remains the same
250+
258251
return fmt.Sprintf("%x", h.Sum(nil))
259252
}

internal/state/state.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,21 +269,22 @@ func (m *Manager) GetLockFilePath() string {
269269
}
270270

271271
// migrateRenamedComponent moves a component from old name to new name in the state
272-
// This handles the case where a component was renamed/moved but has identical content
272+
// This handles the case where a component was renamed/moved but has identical core functionality
273273
func (m *Manager) migrateRenamedComponent(oldKey string, newComponent profile.ComponentInfo, oldState ComponentState) {
274274
newKey := newComponent.FullName()
275275

276-
// Create new state with updated names but preserve everything else
276+
// Create new state preserving installation state but resetting link state
277+
// This ensures that links will be re-created for the new component location
277278
newState := ComponentState{
278279
ProfileName: newComponent.ProfileName,
279280
ComponentName: newComponent.ComponentName,
280281
InstalledAt: oldState.InstalledAt,
281282
PackageManager: oldState.PackageManager,
282283
InstallCommand: oldState.InstallCommand,
283-
Links: oldState.Links,
284+
Links: map[string]string{}, // Reset links so they get re-created
284285
PostInstallRan: oldState.PostInstallRan,
285-
PostLinkRan: oldState.PostLinkRan,
286-
ContentHash: oldState.ContentHash, // Keep the same content hash
286+
PostLinkRan: false, // Reset post-link state since links will be re-created
287+
ContentHash: newComponent.Component.ContentHash(),
287288
}
288289

289290
// Remove old entry and add new one

0 commit comments

Comments
 (0)