Merge pull request #39 from technicalpickles/feature/merge_directory

Merge directories
This commit is contained in:
Yusuke Murata
2013-06-06 05:47:43 -07:00
7 changed files with 336 additions and 14 deletions

4
.gitignore vendored
View File

@@ -39,3 +39,7 @@ pkg
#
# For vim:
*.swp
#
# For IDEA:
.idea/
*.iml

View File

@@ -1,4 +1,5 @@
language: ruby
rvm:
- 2.0.0
- 1.9.3
- 1.8.7

View File

@@ -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.

View File

@@ -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)
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

View File

@@ -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

View File

@@ -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

View File

@@ -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