From 008513f984cf815884e45fad1eaf2563c0c46b86 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 9 Jun 2019 19:02:43 +0200 Subject: [PATCH] Add --follow option to add command --- cmd/add.go | 1 + cmd/add_test.go | 43 ++++++++++++++++++++++++++++++++++++++ cmd/helps.gen.go | 6 ++++++ docs/HOWTO.md | 16 ++++++++++++++ docs/REFERENCE.md | 6 ++++++ lib/chezmoi/targetstate.go | 12 ++++++++++- 6 files changed, 83 insertions(+), 1 deletion(-) diff --git a/cmd/add.go b/cmd/add.go index 9a2169b2414..91e6f5bc684 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -35,6 +35,7 @@ func init() { persistentFlags.BoolVarP(&config.add.options.Empty, "empty", "e", false, "add empty files") persistentFlags.BoolVar(&config.add.options.Encrypt, "encrypt", false, "encrypt files") persistentFlags.BoolVarP(&config.add.options.Exact, "exact", "x", false, "add directories exactly") + persistentFlags.BoolVarP(&config.add.options.Follow, "follow", "f", false, "follow last symlink") persistentFlags.BoolVarP(&config.add.prompt, "prompt", "p", false, "prompt before adding") persistentFlags.BoolVarP(&config.add.recursive, "recursive", "r", false, "recurse in to subdirectories") persistentFlags.BoolVarP(&config.add.options.Template, "template", "T", false, "add files as templates") diff --git a/cmd/add_test.go b/cmd/add_test.go index 5b38ba5734a..e934466e508 100644 --- a/cmd/add_test.go +++ b/cmd/add_test.go @@ -200,6 +200,49 @@ func TestAddCommand(t *testing.T) { ), }, }, + { + name: "add_symlink_follow", + args: []string{"/home/user/foo"}, + add: addCmdConfig{ + options: chezmoi.AddOptions{ + Follow: true, + }, + }, + root: map[string]interface{}{ + "/home/user": &vfst.Dir{Perm: 0755}, + "/home/user/.chezmoi": &vfst.Dir{Perm: 0700}, + "/home/user/.dotfiles/foo": "bar", + "/home/user/foo": &vfst.Symlink{Target: ".dotfiles/foo"}, + }, + tests: []vfst.Test{ + vfst.TestPath("/home/user/.chezmoi/foo", + vfst.TestModeIsRegular, + vfst.TestContentsString("bar"), + ), + }, + }, + { + name: "add_symlink_follow_double", + args: []string{"/home/user/foo"}, + add: addCmdConfig{ + options: chezmoi.AddOptions{ + Follow: true, + }, + }, + root: map[string]interface{}{ + "/home/user": &vfst.Dir{Perm: 0755}, + "/home/user/.chezmoi": &vfst.Dir{Perm: 0700}, + "/home/user/.dotfiles/baz": "qux", + "/home/user/foo": &vfst.Symlink{Target: "bar"}, + "/home/user/bar": &vfst.Symlink{Target: ".dotfiles/baz"}, + }, + tests: []vfst.Test{ + vfst.TestPath("/home/user/.chezmoi/foo", + vfst.TestModeIsRegular, + vfst.TestContentsString("qux"), + ), + }, + }, { name: "add_symlink_in_dir_recursive", args: []string{"/home/user/foo"}, diff --git a/cmd/helps.gen.go b/cmd/helps.gen.go index c7dd2c5eeab..9beb7c6e260 100644 --- a/cmd/helps.gen.go +++ b/cmd/helps.gen.go @@ -22,6 +22,12 @@ var helps = map[string]help{ "\n" + "Set the \"exact\" attribute on added directories.\n" + "\n" + + "\"-f\", \"--follow\"\n" + + "\n" + + "If the target is a symlink, add what it points to, rather than the symlink\n" + + "itself. This is useful when migrating your dotfiles from a system that uses\n" + + "symlinks.\n" + + "\n" + "\"-p\", \"--prompt\"\n" + "\n" + "Interactively prompt before adding each file.\n" + diff --git a/docs/HOWTO.md b/docs/HOWTO.md index 740872df721..3898efc9aa9 100644 --- a/docs/HOWTO.md +++ b/docs/HOWTO.md @@ -21,6 +21,7 @@ * [Export archives](#export-archives) * [Use a non-git version control system](#use-a-non-git-version-control-system) * [Use a merge tool other than vimdiff](#use-a-merge-tool-other-than-vimdiff) +* [Migrate from a dotfile manager that uses symlinks](#migrate-from-a-dotfile-manager-that-uses-symlinks) ## Use a hosted repo to manage your dotfiles across multiple machines @@ -523,3 +524,18 @@ neovim's diff mode specify: [merge] command = "nvim" args = "-d" + + +## Migrate from a dotfile manager that uses symlinks + +Many dotfile managers replace dotfiles with symbolic links to files in a common +directory. If you `chezmoi add` such a symlink, chezmoi will add the symlink, +not the file. To assist with migrating from symlink-based systems, use the +`--follow` / `-f` option to `chezmoi add`, for example: + + chezmoi add --follow ~/.bashrc + +This will tell `chezmoi add` that the target state of `~/.bashrc` is the target +of the `~/.bashrc` symlink, rather than the symlink itself. When you run +`chezmoi apply`, chezmoi will replace the `~/.bashrc` symlink with the file +contents. \ No newline at end of file diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 0a92742e4d6..f7f105b77a2 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -290,6 +290,12 @@ Set the `empty` attribute on added files. Set the `exact` attribute on added directories. +#### `-f`, `--follow` + +If the target is a symlink, add what it points to, rather than the symlink +itself. This is useful when migrating your dotfiles from a system that uses +symlinks. + #### `-p`, `--prompt` Interactively prompt before adding each file. diff --git a/lib/chezmoi/targetstate.go b/lib/chezmoi/targetstate.go index 8fe3274163a..20a564c3d97 100644 --- a/lib/chezmoi/targetstate.go +++ b/lib/chezmoi/targetstate.go @@ -30,6 +30,7 @@ type AddOptions struct { Empty bool Encrypt bool Exact bool + Follow bool Template bool } @@ -83,7 +84,16 @@ func (ts *TargetState) Add(fs vfs.FS, addOptions AddOptions, targetPath string, } if info == nil { var err error - info, err = fs.Lstat(targetPath) + if addOptions.Follow { + info, err = fs.Stat(targetPath) + } else { + info, err = fs.Lstat(targetPath) + } + if err != nil { + return err + } + } else if addOptions.Follow && info.Mode()&os.ModeType == os.ModeSymlink { + info, err = fs.Stat(targetPath) if err != nil { return err }