Merge pull request #39 from technicalpickles/feature/merge_directory
Merge directories
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,3 +39,7 @@ pkg
|
||||
#
|
||||
# For vim:
|
||||
*.swp
|
||||
#
|
||||
# For IDEA:
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.0.0
|
||||
- 1.9.3
|
||||
- 1.8.7
|
||||
|
||||
@@ -51,6 +51,84 @@ Not sure what else homesick has up its sleeve? There's always the built in help:
|
||||
|
||||
homesick help
|
||||
|
||||
## .homesick_subdir
|
||||
|
||||
`homesick symlink` basically makes symlink to only first depth in `castle/home`. If you want to link nested files/directories, please use .homesick_subdir.
|
||||
|
||||
For example, when you have castle like this:
|
||||
|
||||
castle/home
|
||||
`-- .config
|
||||
`-- fooapp
|
||||
|-- config1
|
||||
|-- config2
|
||||
`-- config3
|
||||
|
||||
and have home like this:
|
||||
|
||||
$ tree -a
|
||||
~
|
||||
|-- .config
|
||||
| `-- barapp
|
||||
| |-- config1
|
||||
| |-- config2
|
||||
| `-- config3
|
||||
`-- .emacs.d
|
||||
|-- elisp
|
||||
`-- inits
|
||||
|
||||
You may want to symlink only to `castle/home/.config/fooapp` instead of `castle/home/.config` because you already have `~/.config/barapp`. In this case, you can use .homesick_subdir. Please write "directories you want to look up sub direcoties (instead of just first depth)" in this file.
|
||||
|
||||
castle/home/.homesick_subdir
|
||||
|
||||
.config
|
||||
|
||||
and run `homesick symlink CASTLE`. The result is:
|
||||
|
||||
~
|
||||
|-- .config
|
||||
| |-- barapp
|
||||
| | |-- config1
|
||||
| | |-- config2
|
||||
| | `-- config3
|
||||
| `-- fooapp -> castle/home/.config/fooapp
|
||||
`-- .emacs.d
|
||||
|-- elisp
|
||||
`-- inits
|
||||
|
||||
Or `homesick track NESTED_FILE CASTLE` adds a line automatically. For example:
|
||||
|
||||
homesick track .emacs.d/elisp castle
|
||||
|
||||
castle/home/.homesick_subdir
|
||||
|
||||
.config
|
||||
.emacs.d
|
||||
|
||||
home directory
|
||||
|
||||
~
|
||||
|-- .config
|
||||
| |-- barapp
|
||||
| | |-- config1
|
||||
| | |-- config2
|
||||
| | `-- config3
|
||||
| `-- fooapp -> castle/home/.config/fooapp
|
||||
`-- .emacs.d
|
||||
|-- elisp -> castle/home/.emacs.d/elisp
|
||||
`-- inits
|
||||
|
||||
and castle
|
||||
|
||||
castle/home
|
||||
|-- .config
|
||||
| `-- fooapp
|
||||
| |-- config1
|
||||
| |-- config2
|
||||
| `-- config3
|
||||
`-- .emacs.d
|
||||
`-- elisp
|
||||
|
||||
## Note on Patches/Pull Requests
|
||||
|
||||
* Fork the project.
|
||||
|
||||
143
lib/homesick.rb
143
lib/homesick.rb
@@ -10,6 +10,7 @@ class Homesick < Thor
|
||||
add_runtime_options!
|
||||
|
||||
GITHUB_NAME_REPO_PATTERN = /\A([A-Za-z_-]+\/[A-Za-z_-]+)\Z/
|
||||
SUBDIR_FILENAME = ".homesick_subdir"
|
||||
|
||||
def initialize(args=[], options={}, config={})
|
||||
super
|
||||
@@ -96,15 +97,14 @@ class Homesick < Thor
|
||||
check_castle_existance(name, "symlink")
|
||||
|
||||
inside castle_dir(name) do
|
||||
files = Pathname.glob('{.*,*}').reject{|a| [".",".."].include?(a.to_s)}
|
||||
files.each do |path|
|
||||
absolute_path = path.expand_path
|
||||
subdirs = subdirs(name)
|
||||
|
||||
inside home_dir do
|
||||
adjusted_path = (home_dir + path).basename
|
||||
# link files
|
||||
symlink_each(name, castle_dir(name), subdirs)
|
||||
|
||||
ln_s absolute_path, adjusted_path
|
||||
end
|
||||
# link files in subdirs
|
||||
subdirs.each do |subdir|
|
||||
symlink_each(name, subdir, subdirs)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -112,22 +112,46 @@ class Homesick < Thor
|
||||
desc "track FILE CASTLE", "add a file to a castle"
|
||||
def track(file, castle)
|
||||
castle = Pathname.new(castle)
|
||||
file = Pathname.new(file)
|
||||
file = Pathname.new(file.chomp('/'))
|
||||
check_castle_existance(castle, 'track')
|
||||
|
||||
absolute_path = file.expand_path
|
||||
castle_path = castle_dir(castle)
|
||||
mv absolute_path, castle_path
|
||||
relative_dir = absolute_path.relative_path_from(home_dir).dirname
|
||||
castle_path = Pathname.new(castle_dir(castle)).join(relative_dir)
|
||||
FileUtils.mkdir_p castle_path
|
||||
|
||||
# Are we already tracking this or anything inside it?
|
||||
target = Pathname.new(castle_path.join(file.basename))
|
||||
if target.exist?
|
||||
if absolute_path.directory?
|
||||
move_dir_contents(target, absolute_path)
|
||||
absolute_path.rmtree
|
||||
subdir_remove(castle, relative_dir + file.basename)
|
||||
|
||||
elsif more_recent? absolute_path, target
|
||||
target.delete
|
||||
mv absolute_path, castle_path
|
||||
else
|
||||
shell.say_status(:track, "#{target} already exists, and is more recent than #{file}. Run 'homesick SYMLINK CASTLE' to create symlinks.", :blue) unless options[:quiet]
|
||||
end
|
||||
else
|
||||
mv absolute_path, castle_path
|
||||
end
|
||||
|
||||
inside home_dir do
|
||||
absolute_path = castle_dir(castle) + file.basename
|
||||
home_path = home_dir + file
|
||||
absolute_path = castle_path + file.basename
|
||||
home_path = home_dir + relative_dir + file.basename
|
||||
ln_s absolute_path, home_path
|
||||
end
|
||||
|
||||
inside castle_path do
|
||||
git_add absolute_path
|
||||
end
|
||||
|
||||
# are we tracking something nested? Add the parent dir to the manifest
|
||||
unless relative_dir.eql?(Pathname.new('.'))
|
||||
subdir_add(castle, relative_dir)
|
||||
end
|
||||
end
|
||||
|
||||
desc "list", "List cloned castles"
|
||||
@@ -219,4 +243,99 @@ class Homesick < Thor
|
||||
git_push
|
||||
end
|
||||
end
|
||||
|
||||
def subdir_file(castle)
|
||||
repos_dir.join(castle, SUBDIR_FILENAME)
|
||||
end
|
||||
|
||||
def subdirs(castle)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
subdirs = []
|
||||
if subdir_filepath.exist?
|
||||
subdir_filepath.readlines.each do |subdir|
|
||||
subdirs.push(subdir.chomp)
|
||||
end
|
||||
end
|
||||
subdirs
|
||||
end
|
||||
|
||||
def subdir_add(castle, path)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
File.open(subdir_filepath, 'a+') do |subdir|
|
||||
subdir.puts path unless subdir.readlines.inject(false) { |memo, line| line.eql?("#{path.to_s}\n") || memo }
|
||||
end
|
||||
|
||||
inside castle_dir(castle) do
|
||||
git_add subdir_filepath
|
||||
end
|
||||
end
|
||||
|
||||
def subdir_remove(castle, path)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
if subdir_filepath.exist?
|
||||
lines = IO.readlines(subdir_filepath).delete_if { |line| line == "#{path}\n" }
|
||||
File.open(subdir_filepath, 'w') { |manfile| manfile.puts lines }
|
||||
end
|
||||
|
||||
inside castle_dir(castle) do
|
||||
git_add subdir_filepath
|
||||
end
|
||||
end
|
||||
|
||||
def move_dir_contents(target, dir_path)
|
||||
child_files = dir_path.children
|
||||
child_files.each do |child|
|
||||
|
||||
target_path = target.join(child.basename)
|
||||
if target_path.exist?
|
||||
if more_recent?(child, target_path) && target.file?
|
||||
target_path.delete
|
||||
mv child, target
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
mv child, target
|
||||
end
|
||||
end
|
||||
|
||||
def more_recent?(first, second)
|
||||
first_p = Pathname.new(first)
|
||||
second_p = Pathname.new(second)
|
||||
first_p.mtime > second_p.mtime && !first_p.symlink?
|
||||
end
|
||||
|
||||
def symlink_each(castle, basedir, subdirs)
|
||||
absolute_basedir = Pathname.new(basedir).expand_path
|
||||
inside basedir do
|
||||
files = Pathname.glob('{.*,*}').reject{|a| [".", ".."].include?(a.to_s)}
|
||||
files.each do |path|
|
||||
absolute_path = path.expand_path
|
||||
castle_home = castle_dir(castle)
|
||||
|
||||
# make ignore dirs
|
||||
ignore_dirs = []
|
||||
subdirs.each do |subdir|
|
||||
# ignore all parent of each line in subdir file
|
||||
Pathname.new(subdir).ascend do |p|
|
||||
ignore_dirs.push(p)
|
||||
end
|
||||
end
|
||||
|
||||
# ignore dirs written in subdir file
|
||||
matched = false
|
||||
ignore_dirs.uniq.each do |ignore_dir|
|
||||
if absolute_path == castle_home.join(ignore_dir)
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
next if matched
|
||||
|
||||
relative_dir = absolute_basedir.relative_path_from(castle_home)
|
||||
home_path = home_dir.join(relative_dir).join(path)
|
||||
ln_s absolute_path, home_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,6 +93,7 @@ class Homesick
|
||||
def ln_s(source, destination, config = {})
|
||||
source = Pathname.new(source)
|
||||
destination = Pathname.new(destination)
|
||||
FileUtils.mkdir_p destination.dirname
|
||||
|
||||
if destination.symlink?
|
||||
if destination.readlink == source
|
||||
|
||||
@@ -120,12 +120,59 @@ describe "homesick" do
|
||||
existing_dotdir_link.readlink.should == dotdir
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' in .homesick_subdir" do
|
||||
let(:castle) { given_castle("glencairn", [".config"]) }
|
||||
it "can symlink in sub directory" do
|
||||
dotdir = castle.directory(".config")
|
||||
dotfile = dotdir.file(".some_dotfile")
|
||||
|
||||
homesick.symlink("glencairn")
|
||||
|
||||
home_dotdir = home.join(".config")
|
||||
home_dotdir.symlink?.should == false
|
||||
home_dotdir.join(".some_dotfile").readlink.should == dotfile
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config/appA' in .homesick_subdir" do
|
||||
let(:castle) { given_castle("glencairn", [".config/appA"]) }
|
||||
it "can symlink in nested sub directory" do
|
||||
dotdir = castle.directory(".config").directory("appA")
|
||||
dotfile = dotdir.file(".some_dotfile")
|
||||
|
||||
homesick.symlink("glencairn")
|
||||
|
||||
home_dotdir = home.join(".config").join("appA")
|
||||
home_dotdir.symlink?.should == false
|
||||
home_dotdir.join(".some_dotfile").readlink.should == dotfile
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' and '.config/appA' in .homesick_subdir" do
|
||||
let(:castle) { given_castle("glencairn", [".config", ".config/appA"]) }
|
||||
it "can symlink under both of .config and .config/appA" do
|
||||
config_dir = castle.directory(".config")
|
||||
config_dotfile = config_dir.file(".some_dotfile")
|
||||
appA_dir = config_dir.directory("appA")
|
||||
appA_dotfile = appA_dir.file(".some_appfile")
|
||||
|
||||
homesick.symlink("glencairn")
|
||||
|
||||
home_config_dir = home.join(".config")
|
||||
home_appA_dir = home_config_dir.join("appA")
|
||||
home_config_dir.symlink?.should == false
|
||||
home_config_dir.join(".some_dotfile").readlink.should == config_dotfile
|
||||
home_appA_dir.symlink?.should == false
|
||||
home_appA_dir.join(".some_appfile").readlink.should == appA_dotfile
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "list" do
|
||||
it "should say each castle in the castle directory" do
|
||||
given_castle('zomg')
|
||||
given_castle('zomg', 'wtf/zomg')
|
||||
given_castle('wtf/zomg')
|
||||
|
||||
homesick.should_receive(:say_status).with("zomg", "git://github.com/technicalpickles/zomg.git", :cyan)
|
||||
homesick.should_receive(:say_status).with("wtf/zomg", "git://github.com/technicalpickles/zomg.git", :cyan)
|
||||
@@ -169,5 +216,70 @@ describe "homesick" do
|
||||
|
||||
some_rc_file.readlink.should == tracked_file
|
||||
end
|
||||
|
||||
it 'should track a file in nested folder structure' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('some/nested/file.txt')
|
||||
tracked_file.should exist
|
||||
some_nested_file.readlink.should == tracked_file
|
||||
end
|
||||
|
||||
it 'should track a nested directory' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_dir = home.directory('some/nested/directory/')
|
||||
homesick.track(some_nested_dir.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('some/nested/directory/')
|
||||
tracked_file.should exist
|
||||
some_nested_dir.realpath.should == tracked_file.realpath
|
||||
end
|
||||
|
||||
describe "subdir_file" do
|
||||
|
||||
it 'should add the nested files parent to the subdir_file' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
f.readline.should == "some/nested\n"
|
||||
end
|
||||
end
|
||||
|
||||
it 'should NOT add anything if the files parent is already listed' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
other_nested_file = home.file('some/nested/other.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
homesick.track(other_nested_file.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
f.readlines.size.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
it 'should remove the parent of a tracked file from the subdir_file if the parent itself is tracked' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
nested_parent = home.directory('some/nested/')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
homesick.track(nested_parent.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
f.each_line { |line| line.should_not == "some/nested\n" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,11 +16,18 @@ RSpec.configure do |config|
|
||||
homesick.stub(:say_status)
|
||||
end
|
||||
|
||||
def given_castle(name, path=name)
|
||||
def given_castle(path, subdirs=[])
|
||||
name = Pathname.new(path).basename
|
||||
castles.directory(path) do |castle|
|
||||
Dir.chdir(castle) do
|
||||
system "git init >/dev/null 2>&1"
|
||||
system "git remote add origin git://github.com/technicalpickles/#{name}.git >/dev/null 2>&1"
|
||||
if subdirs then
|
||||
subdir_file = castle.join(Homesick::SUBDIR_FILENAME)
|
||||
subdirs.each do |subdir|
|
||||
system "echo #{subdir} >> #{subdir_file}"
|
||||
end
|
||||
end
|
||||
return castle.directory("home")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user