2 Commits

5 changed files with 25 additions and 69 deletions

View File

@@ -55,7 +55,7 @@ lnk add ~/.vimrc ~/.config/nvim ~/.gitconfig
# Add host-specific files # Add host-specific files
lnk add --host laptop ~/.ssh/config lnk add --host laptop ~/.ssh/config
lnk add --host work ~/.aws/credentials lnk add --host work ~/.gitconfig
# List managed files # List managed files
lnk list # Common config only lnk list # Common config only
@@ -101,13 +101,11 @@ Lnk supports both **common configurations** (shared across all machines) and **h
├── laptop.lnk/ # Laptop-specific storage ├── laptop.lnk/ # Laptop-specific storage
│ ├── .ssh/ │ ├── .ssh/
│ │ └── config │ │ └── config
│ └── .aws/ │ └── .tmux.conf
│ └── credentials
└── work.lnk/ # Work-specific storage └── work.lnk/ # Work-specific storage
├── .ssh/ ├── .ssh/
│ └── config │ └── config
└── .company/ └── .gitconfig
└── config
``` ```
### Usage Patterns ### Usage Patterns
@@ -118,7 +116,7 @@ lnk add ~/.vimrc ~/.bashrc ~/.gitconfig
# Host-specific config (unique per machine) # Host-specific config (unique per machine)
lnk add --host $(hostname) ~/.ssh/config lnk add --host $(hostname) ~/.ssh/config
lnk add --host work ~/.aws/credentials lnk add --host work ~/.gitconfig
# List configurations # List configurations
lnk list # Common only lnk list # Common only
@@ -150,7 +148,7 @@ lnk init -r git@github.com:you/dotfiles.git
lnk add ~/.bashrc ~/.vimrc ~/.gitconfig lnk add ~/.bashrc ~/.vimrc ~/.gitconfig
# Add host-specific config # Add host-specific config
lnk add --host $(hostname) ~/.ssh/config ~/.aws/credentials lnk add --host $(hostname) ~/.ssh/config ~/.tmux.conf
lnk push "initial setup" lnk push "initial setup"
``` ```
@@ -186,10 +184,10 @@ lnk add --host laptop ~/.ssh/config
lnk add ~/.vimrc # Common config lnk add ~/.vimrc # Common config
lnk push "laptop ssh config" lnk push "laptop ssh config"
# On your work machine # On your work machine
lnk pull # Get common config lnk pull # Get common config
lnk add --host work ~/.aws/credentials lnk add --host work ~/.gitconfig
lnk push "work aws config" lnk push "work git config"
# Back on laptop # Back on laptop
lnk pull # Get updates (work config won't affect laptop) lnk pull # Get updates (work config won't affect laptop)

View File

@@ -33,10 +33,10 @@ func newAddCmd() *cobra.Command {
basename := filepath.Base(filePath) basename := filepath.Base(filePath)
if host != "" { if host != "" {
printf(cmd, "✨ \033[1mAdded %s to lnk (host: %s)\033[0m\n", basename, host) printf(cmd, "✨ \033[1mAdded %s to lnk (host: %s)\033[0m\n", basename, host)
printf(cmd, " 🔗 \033[90m%s\033[0m → \033[36m~/.config/lnk/%s.lnk/%s\033[0m\n", filePath, host, basename) printf(cmd, " 🔗 \033[90m%s\033[0m → \033[36m~/.config/lnk/%s.lnk/%s\033[0m\n", filePath, host, filePath)
} else { } else {
printf(cmd, "✨ \033[1mAdded %s to lnk\033[0m\n", basename) printf(cmd, "✨ \033[1mAdded %s to lnk\033[0m\n", basename)
printf(cmd, " 🔗 \033[90m%s\033[0m → \033[36m~/.config/lnk/%s\033[0m\n", filePath, basename) printf(cmd, " 🔗 \033[90m%s\033[0m → \033[36m~/.config/lnk/%s\033[0m\n", filePath, filePath)
} }
printf(cmd, " 📝 Use \033[1mlnk push\033[0m to sync to remote\n") printf(cmd, " 📝 Use \033[1mlnk push\033[0m to sync to remote\n")
return nil return nil

View File

@@ -5,7 +5,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@@ -107,19 +106,10 @@ func (suite *CLITestSuite) TestAddCommand() {
suite.NoError(err) suite.NoError(err)
suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink) suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink)
// Verify some file exists in repo with .bashrc in the name // Verify the file exists in repo with preserved directory structure
lnkDir := filepath.Join(suite.tempDir, "lnk") lnkDir := filepath.Join(suite.tempDir, "lnk")
entries, err := os.ReadDir(lnkDir) repoFile := filepath.Join(lnkDir, suite.tempDir, ".bashrc")
suite.NoError(err) suite.FileExists(repoFile)
found := false
for _, entry := range entries {
if strings.Contains(entry.Name(), ".bashrc") && entry.Name() != ".lnk" {
found = true
break
}
}
suite.True(found, "Repository should contain a file with .bashrc in the name")
} }
func (suite *CLITestSuite) TestRemoveCommand() { func (suite *CLITestSuite) TestRemoveCommand() {
@@ -397,19 +387,10 @@ func (suite *CLITestSuite) TestAddDirectory() {
suite.NoError(err) suite.NoError(err)
suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink) suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink)
// Verify some directory exists in repo with .config in the name // Verify the directory exists in repo with preserved directory structure
lnkDir := filepath.Join(suite.tempDir, "lnk") lnkDir := filepath.Join(suite.tempDir, "lnk")
entries, err := os.ReadDir(lnkDir) repoDir := filepath.Join(lnkDir, suite.tempDir, ".config")
suite.NoError(err) suite.DirExists(repoDir)
found := false
for _, entry := range entries {
if strings.Contains(entry.Name(), ".config") && entry.Name() != ".lnk" {
found = true
break
}
}
suite.True(found, "Repository should contain a directory with .config in the name")
} }
func (suite *CLITestSuite) TestSameBasenameFilesBug() { func (suite *CLITestSuite) TestSameBasenameFilesBug() {

View File

@@ -67,16 +67,9 @@ func getRepoPath() string {
// generateRepoName creates a repository path from a relative path // generateRepoName creates a repository path from a relative path
func generateRepoName(relativePath string, host string) string { func generateRepoName(relativePath string, host string) string {
if host != "" { // Always preserve the directory structure for consistency
// For host-specific files, preserve the directory structure // Both common and host-specific files should maintain their path structure
return relativePath return relativePath
}
// For common files, replace slashes and backslashes with underscores to create valid filename
repoName := strings.ReplaceAll(relativePath, "/", "_")
repoName = strings.ReplaceAll(repoName, "\\", "_")
return repoName
} }
// getHostStoragePath returns the storage path for host-specific or common files // getHostStoragePath returns the storage path for host-specific or common files

View File

@@ -82,19 +82,11 @@ func (suite *CoreTestSuite) TestCoreFileOperations() {
suite.Require().NoError(err) suite.Require().NoError(err)
suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink) suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink)
// The repository file will have a generated name based on the relative path // The repository file will preserve the directory structure
lnkDir := filepath.Join(suite.tempDir, "lnk") lnkDir := filepath.Join(suite.tempDir, "lnk")
entries, err := os.ReadDir(lnkDir)
suite.Require().NoError(err)
var repoFile string // Find the .bashrc file in the repository (it should be at the relative path)
for _, entry := range entries { repoFile := filepath.Join(lnkDir, suite.tempDir, ".bashrc")
if strings.Contains(entry.Name(), ".bashrc") && entry.Name() != ".lnk" {
repoFile = filepath.Join(lnkDir, entry.Name())
break
}
}
suite.NotEmpty(repoFile, "Repository should contain a file with .bashrc in the name")
suite.FileExists(repoFile) suite.FileExists(repoFile)
// Verify content is preserved // Verify content is preserved
@@ -141,19 +133,11 @@ func (suite *CoreTestSuite) TestCoreDirectoryOperations() {
suite.Require().NoError(err) suite.Require().NoError(err)
suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink) suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink)
// Check that some repository directory exists with testdir in the name // Check that the repository directory preserves the structure
lnkDir := filepath.Join(suite.tempDir, "lnk") lnkDir := filepath.Join(suite.tempDir, "lnk")
entries, err := os.ReadDir(lnkDir)
suite.Require().NoError(err)
var repoDir string // The directory should be at the relative path
for _, entry := range entries { repoDir := filepath.Join(lnkDir, suite.tempDir, "testdir")
if strings.Contains(entry.Name(), "testdir") && entry.Name() != ".lnk" {
repoDir = filepath.Join(lnkDir, entry.Name())
break
}
}
suite.NotEmpty(repoDir, "Repository should contain a directory with testdir in the name")
suite.DirExists(repoDir) suite.DirExists(repoDir)
// Remove the directory // Remove the directory