swapDirectoryContents replaces the entries inside dest with the entries inside src, preserving dest's inode. It first moves every existing entry into a sibling backup directory, then moves the staged entries into dest. If any step fails, the original contents are restored from the backup. src and d
(dest, src string)
| 468 | // |
| 469 | // src and dest must live on the same filesystem so renames are atomic. |
| 470 | func swapDirectoryContents(dest, src string) error { |
| 471 | backup, err := os.MkdirTemp(filepath.Dir(dest), "."+filepath.Base(dest)+".gh-skill-backup-") |
| 472 | if err != nil { |
| 473 | return fmt.Errorf("could not create backup directory: %w", err) |
| 474 | } |
| 475 | |
| 476 | existing, err := os.ReadDir(dest) |
| 477 | if err != nil { |
| 478 | _ = os.RemoveAll(backup) |
| 479 | return fmt.Errorf("could not read skill directory %s: %w", dest, err) |
| 480 | } |
| 481 | var movedOut []string |
| 482 | for _, entry := range existing { |
| 483 | if err := os.Rename(filepath.Join(dest, entry.Name()), filepath.Join(backup, entry.Name())); err != nil { |
| 484 | restoreBackup(dest, backup, movedOut, nil) |
| 485 | return fmt.Errorf("could not move %s aside: %w", entry.Name(), err) |
| 486 | } |
| 487 | movedOut = append(movedOut, entry.Name()) |
| 488 | } |
| 489 | |
| 490 | staged, err := os.ReadDir(src) |
| 491 | if err != nil { |
| 492 | restoreBackup(dest, backup, movedOut, nil) |
| 493 | return fmt.Errorf("could not read staged skill directory %s: %w", src, err) |
| 494 | } |
| 495 | var movedIn []string |
| 496 | for _, entry := range staged { |
| 497 | from := filepath.Join(src, entry.Name()) |
| 498 | to := filepath.Join(dest, entry.Name()) |
| 499 | if err := os.Rename(from, to); err != nil { |
| 500 | restoreBackup(dest, backup, movedOut, movedIn) |
| 501 | return fmt.Errorf("could not move %s into place: %w", entry.Name(), err) |
| 502 | } |
| 503 | movedIn = append(movedIn, entry.Name()) |
| 504 | } |
| 505 | |
| 506 | _ = os.RemoveAll(backup) |
| 507 | return nil |
| 508 | } |
| 509 | |
| 510 | // restoreBackup undoes a partial swap by removing any freshly installed |
| 511 | // entries and moving the original entries back from backup into dest. |